diff --git a/docs/src/develop/extensions.md b/docs/src/develop/extensions.md index 960334e6..d0c7609c 100644 --- a/docs/src/develop/extensions.md +++ b/docs/src/develop/extensions.md @@ -23,10 +23,11 @@ template is provided in [`./test/extensions/infinite_domain.jl`](https://github.com/infiniteopt/InfiniteOpt.jl/blob/master/test/extensions/infinite_domain.jl). The extension steps employed are: 1. Define the new `struct` infinite domain type (only thing required as bare minimum) -2. Extend [`InfiniteOpt.supports_in_domain`](@ref) (enables error checking of supports) -3. Extend [`InfiniteOpt.generate_support_values`](@ref) (enables support generation via `num_supports` keyword arguments) -4. If a lower bound and upper bound can be reported, extend `JuMP` lower bound and upper bound methods (enables automatic bound detection in `integral`) -5. Extend [`InfiniteOpt.MeasureToolbox.generate_expect_data`](@ref) (enables the use of `expect`) +2. Extend[`InfiniteOpt.round_domain`](@ref) (enables safe use of significant digit rounding) +3. Extend [`InfiniteOpt.supports_in_domain`](@ref) (enables error checking of supports) +4. Extend [`InfiniteOpt.generate_support_values`](@ref) (enables support generation via `num_supports` keyword arguments) +5. If a lower bound and upper bound can be reported, extend `JuMP` lower bound and upper bound methods (enables automatic bound detection in `integral`) +6. Extend [`InfiniteOpt.MeasureToolbox.generate_expect_data`](@ref) (enables the use of `expect`) As an example, let's create a univariate disjoint interval domain as an infinite domain type. This corresponds to the domain ``[lb_1, ub_1] \cup [lb_2, ub_2]`` @@ -82,13 +83,25 @@ to extend [`generate_integral_data`](@ref). See [`Measure Evaluation Techniques` for details. To enable support domain checking which is useful to avoid strange bugs, we will -extend [`InfiniteOpt.supports_in_domain`](@ref). This returns a `Bool` to -indicate if a vector of supports are in the domain: +extend [`InfiniteOpt.round_domain`](@ref) which rounds the domain to use proper +significant digits and [`InfiniteOpt.supports_in_domain`](@ref) which returns a +`Bool` whether a vector of supports is in the domain: ```jldoctest domain_ext; output = false +function InfiniteOpt.round_domain( + domain::DisjointDomain, + sig_digits::Int + ) + lb1 = round(domain.lb1, sigdigits = sig_digits) + ub1 = round(domain.ub1, sigdigits = sig_digits) + lb2 = round(domain.lb2, sigdigits = sig_digits) + ub2 = round(domain.ub2, sigdigits = sig_digits) + return DisjointDomain(lb1, ub1, lb2, ub2) +end + function InfiniteOpt.supports_in_domain( supports::Union{Number, Vector{<:Number}}, domain::DisjointDomain - )::Bool + ) return all((domain.lb1 .<= supports .<= domain.ub1) .| (domain.lb2 .<= supports .<= domain.ub2)) end @@ -113,7 +126,7 @@ function InfiniteOpt.generate_support_values( domain::DisjointDomain; num_supports::Int = InfiniteOpt.DefaultNumSupports, sig_digits::Int = InfiniteOpt.DefaultSigDigits - )::Tuple{Vector{Float64}, DataType} + ) length_ratio = (domain.ub1 - domain.lb1) / (domain.ub1 - domain.lb1 + domain.ub2 - domain.lb2) num_supports1 = Int64(ceil(length_ratio * num_supports)) num_supports2 = num_supports - num_supports1 diff --git a/docs/src/guide/constraint.md b/docs/src/guide/constraint.md index ebae6553..5a026fcb 100644 --- a/docs/src/guide/constraint.md +++ b/docs/src/guide/constraint.md @@ -242,7 +242,7 @@ argument. This includes a wealth of constraint types including: For example, we could define the following semi-definite constraint: ```jldoctest constrs julia> @constraint(model, [yb 2yb; 3yb 4yb] >= ones(2, 2), PSDCone()) -[yb(t) - 1 2 yb(t) - 1; +[yb(t) - 1 2 yb(t) - 1 3 yb(t) - 1 4 yb(t) - 1] ∈ PSDCone(), ∀ t ∈ [0, 10] ``` See [`JuMP`'s constraint documentation](https://jump.dev/JuMP.jl/v1/manual/constraints/) diff --git a/docs/src/guide/derivative.md b/docs/src/guide/derivative.md index a6245598..3bef56ee 100644 --- a/docs/src/guide/derivative.md +++ b/docs/src/guide/derivative.md @@ -1,6 +1,6 @@ ```@meta DocTestFilters = [r"≥|>=", r" == | = ", r" ∈ | in ", r" for all | ∀ ", r"d|∂", - r"integral|∫", r".*scalar_parameters.jl:783"] + r"integral|∫", r".*scalar_parameters.jl:790"] ``` # [Derivative Operators](@id deriv_docs) @@ -502,7 +502,7 @@ julia> derivative_constraints(d1) julia> add_supports(t, 0.2) ┌ Warning: Support/method changes will invalidate existing derivative evaluation constraints that have been added to the InfiniteModel. Thus, these are being deleted. -└ @ InfiniteOpt ~/work/infiniteopt/InfiniteOpt.jl/src/scalar_parameters.jl:783 +└ @ InfiniteOpt ~/work/infiniteopt/InfiniteOpt.jl/src/scalar_parameters.jl:790 julia> has_derivative_constraints(d1) false diff --git a/docs/src/manual/domains.md b/docs/src/manual/domains.md index 449b360f..229a9924 100644 --- a/docs/src/manual/domains.md +++ b/docs/src/manual/domains.md @@ -22,6 +22,7 @@ JuMP.set_lower_bound(::AbstractInfiniteDomain, ::Real) JuMP.has_upper_bound(::AbstractInfiniteDomain) JuMP.upper_bound(::AbstractInfiniteDomain) JuMP.set_upper_bound(::AbstractInfiniteDomain, ::Real) +InfiniteOpt.round_domain ``` ## Support Point Labels diff --git a/src/array_parameters.jl b/src/array_parameters.jl index a04154b3..0a20a3d9 100644 --- a/src/array_parameters.jl +++ b/src/array_parameters.jl @@ -2,34 +2,38 @@ # CORE DISPATCHVARIABLEREF METHOD EXTENSIONS ################################################################################ # Extend dispatch_variable_ref -function dispatch_variable_ref(model::InfiniteModel, - index::DependentParameterIndex - )::DependentParameterRef +function dispatch_variable_ref( + model::InfiniteModel, + index::DependentParameterIndex + ) return DependentParameterRef(model, index) end # Extend _add_data_object -function _add_data_object(model::InfiniteModel, - object::MultiParameterData - )::DependentParametersIndex +function _add_data_object( + model::InfiniteModel, + object::MultiParameterData + ) index = MOIUC.add_item(model.dependent_params, object) push!(model.param_object_indices, index) return index end # Extend _data_dictionary (type based) -function _data_dictionary(model::InfiniteModel, - ::Type{DependentParameters})::MOIUC.CleverDict +function _data_dictionary( + model::InfiniteModel, + ::Type{DependentParameters} + ) return model.dependent_params end # Extend _data_dictionary (ref based) -function _data_dictionary(pref::DependentParameterRef)::MOIUC.CleverDict +function _data_dictionary(pref::DependentParameterRef) return JuMP.owner_model(pref).dependent_params end # Extend _data_object -function _data_object(pref::DependentParameterRef)::MultiParameterData +function _data_object(pref::DependentParameterRef) object = get(_data_dictionary(pref), JuMP.index(pref).object_index, nothing) if isnothing(object) error("Invalid dependent parameter reference, cannot find ", @@ -40,17 +44,17 @@ function _data_object(pref::DependentParameterRef)::MultiParameterData end # Extend _core_variable_object -function _core_variable_object(pref::DependentParameterRef)::DependentParameters +function _core_variable_object(pref::DependentParameterRef) return _data_object(pref).parameters end # Return the number of dependent parameters involved -function _num_parameters(pref::DependentParameterRef)::Int +function _num_parameters(pref::DependentParameterRef) return length(_data_object(pref).names) end # Extend _delete_data_object -function _delete_data_object(vref::DependentParameterRef)::Nothing +function _delete_data_object(vref::DependentParameterRef) delete!(_data_dictionary(vref), JuMP.index(vref).object_index) return end @@ -59,7 +63,7 @@ end # PARAMETER DEFINITION ################################################################################ # Check that multi-dimensional domains are all the same -function _check_same_domain(_error::Function, domains)::Nothing +function _check_same_domain(_error::Function, domains) if !_allequal(domains) _error("Conflicting infinite domains. Only one multi-dimensional ", "can be specified. Otherwise, scalar domains can be used ", @@ -74,7 +78,7 @@ function _make_array_domain( _error::Function, domains::Vector{T}, inds::Collections.ContainerIndices - )::T where {T <: MultiDistributionDomain} + ) where {T <: MultiDistributionDomain} _check_same_domain(_error, domains) dist = first(domains).distribution if size(dist) != size(inds) @@ -99,7 +103,7 @@ function _make_array_domain( _error::Function, domains::Vector{T}, inds::Collections.ContainerIndices - )::T where {T <: CollectionDomain} + ) where {T <: CollectionDomain} _check_same_domain(_error, domains) if length(collection_domains(first(domains))) != length(inds) _error("The dimensions of the parameters and the specified ", @@ -125,7 +129,7 @@ function _make_array_domain( _error::Function, domains::Vector{T}, inds::Collections.ContainerIndices - )::T where {T <: InfiniteArrayDomain} + ) where {T <: InfiniteArrayDomain} _check_same_domain(_error, domains) return first(domains) end @@ -135,7 +139,7 @@ function _make_array_domain( _error::Function, domains::Vector{T}, inds::Collections.ContainerIndices - )::CollectionDomain{T} where {T <: InfiniteScalarDomain} + ) where {T <: InfiniteScalarDomain} return CollectionDomain(domains) end @@ -151,7 +155,7 @@ function _process_supports( supps::Vector{<:Real}, domain, sig_digits - )::Dict{Vector{Float64}, Set{DataType}} + ) if !supports_in_domain(reshape(supps, length(supps), 1), domain) _error("Support violates the infinite domain.") end @@ -165,7 +169,7 @@ function _process_supports( vect_supps::Vector{<:Vector{<:Real}}, domain, sig_digits - )::Dict{Vector{Float64}, Set{DataType}} + ) len = length(first(vect_supps)) if any(length(s) != len for s in vect_supps) _error("Inconsistent support dimensions.") @@ -185,7 +189,7 @@ function _process_derivative_methods( _error::Function, methods::V, domains - )::V where {V <: Vector{<:NonGenerativeDerivativeMethod}} + ) where {V <: Vector{<:NonGenerativeDerivativeMethod}} return methods end @@ -219,6 +223,7 @@ function _build_parameters( end # process the infinite domain domain = _make_array_domain(_error, domains, orig_inds) + domain = round_domain(domain, sig_digits) # we have supports if !isempty(supports) supp_dict = _process_supports(_error, supports, domain, sig_digits) @@ -272,7 +277,7 @@ function add_parameters( model::InfiniteModel, params::DependentParameters, names::Vector{String} - )::Vector{GeneralVariableRef} + ) # get the number of parameters num_params = length(params.domain) # process the names @@ -297,7 +302,7 @@ end # NAMING ################################################################################ # Get the parameter index in the DependentParameters object -_param_index(pref::DependentParameterRef)::Int = JuMP.index(pref).param_index +_param_index(pref::DependentParameterRef) = JuMP.index(pref).param_index """ JuMP.name(pref::DependentParameterRef)::String @@ -311,7 +316,7 @@ julia> name(pref) "par_name" ``` """ -function JuMP.name(pref::DependentParameterRef)::String +function JuMP.name(pref::DependentParameterRef) object = get(_data_dictionary(pref), JuMP.index(pref).object_index, nothing) return isnothing(object) ? "" : object.names[_param_index(pref)] end @@ -330,7 +335,7 @@ julia> name(vref) "para_name" ``` """ -function JuMP.set_name(pref::DependentParameterRef, name::String)::Nothing +function JuMP.set_name(pref::DependentParameterRef, name::String) _data_object(pref).names[_param_index(pref)] = name JuMP.owner_model(pref).name_to_param = nothing return diff --git a/src/infinite_domains.jl b/src/infinite_domains.jl index 5378b9c2..68c5ecf5 100644 --- a/src/infinite_domains.jl +++ b/src/infinite_domains.jl @@ -11,24 +11,50 @@ function collection_domains(domain::AbstractInfiniteDomain) type = typeof(domain) error("`collection_domains` not defined for infinite domain of type $type.") end -function collection_domains(domain::CollectionDomain{S} - )::Vector{S} where {S <: InfiniteScalarDomain} +function collection_domains(domain::CollectionDomain) return domain.domains end ################################################################################ # BASIC EXTENSIONS ################################################################################ -Base.length(s::CollectionDomain)::Int = length(collection_domains(s)) -Base.length(s::MultiDistributionDomain)::Int = length(s.distribution) -Base.length(s::InfiniteScalarDomain)::Int = 1 +Base.length(s::CollectionDomain) = length(collection_domains(s)) +Base.length(s::MultiDistributionDomain) = length(s.distribution) +Base.length(s::InfiniteScalarDomain) = 1 + +################################################################################ +# ENFORCE SIG FIGS ON DOMAINS +################################################################################ +""" + round_domain(domain::AbstractInfiniteDomain, sig_digits::Int) + +Return a rounded domain of `domain` where its bounds use a number of +significant digits equal to `sig_digs`. This is intended as an internal +method and should be extended by those adding a new kind of infinite domain +that has bounds. +""" +round_domain(domain::AbstractInfiniteDomain, sig_digits::Int) = domain + +# IntervalDomain +function round_domain(domain::IntervalDomain, sig_digits::Int) + lb = round(domain.lower_bound, sigdigits = sig_digits) + ub = round(domain.upper_bound, sigdigits = sig_digits) + return IntervalDomain(lb, ub) +end + +# CollectionDomain +function round_domain(domain::CollectionDomain, sig_digits::Int) + return CollectionDomain([round_domain(d, sig_digits) for d in domain.domains]) +end ################################################################################ # SUPPORT VALIDITY METHODS ################################################################################ """ - supports_in_domain(supports::Union{Real, Vector{<:Real}, Array{<:Real, 2}}, - domain::AbstractInfiniteDomain)::Bool + supports_in_domain( + supports::Union{Real, Vector{<:Real}, Array{<:Real, 2}}, + domain::AbstractInfiniteDomain + )::Bool Used to check if `supports` are in the domain of `domain`. Returns `true` if `supports` are in domain of `domain` and returns `false` otherwise. @@ -40,8 +66,10 @@ that an error won't be thrown. function supports_in_domain end # IntervalDomain -function supports_in_domain(supports::Union{Real, Vector{<:Real}}, - domain::IntervalDomain)::Bool +function supports_in_domain( + supports::Union{Real, Vector{<:Real}}, + domain::IntervalDomain + ) min_support = minimum(supports) max_support = maximum(supports) if min_support < JuMP.lower_bound(domain) || max_support > JuMP.upper_bound(domain) @@ -51,8 +79,10 @@ function supports_in_domain(supports::Union{Real, Vector{<:Real}}, end # UnivariateDistribution domain -function supports_in_domain(supports::Union{Real, Vector{<:Real}}, - domain::UniDistributionDomain)::Bool +function supports_in_domain( + supports::Union{Real, Vector{<:Real}}, + domain::UniDistributionDomain + ) return all(Distributions.insupport(domain.distribution, supports)) end @@ -60,7 +90,7 @@ end function supports_in_domain( supports::Array{<:Real}, domain::MultiDistributionDomain{<:Distributions.MultivariateDistribution{S}} - )::Bool where {S <: Distributions.ValueSupport} + ) where {S <: Distributions.ValueSupport} if length(domain.distribution) != size(supports, 1) error("Support dimensions does not match distribution dimensions.") end @@ -68,7 +98,7 @@ function supports_in_domain( end # CollectionDomain -function supports_in_domain(supports::Array{<:Real, 2}, domain::CollectionDomain)::Bool +function supports_in_domain(supports::Array{<:Real, 2}, domain::CollectionDomain) domains = collection_domains(domain) if length(domains) != size(supports, 1) error("Support dimensions does not match CollectionDomain dimensions.") @@ -82,7 +112,7 @@ function supports_in_domain(supports::Array{<:Real, 2}, domain::CollectionDomain end # Fallback -function supports_in_domain(supports, domain::AbstractInfiniteDomain)::Bool +function supports_in_domain(supports, domain::AbstractInfiniteDomain) return true end @@ -104,18 +134,18 @@ julia> has_lower_bound(domain) true ``` """ -function JuMP.has_lower_bound(domain::AbstractInfiniteDomain)::Bool # fallback +function JuMP.has_lower_bound(domain::AbstractInfiniteDomain) # fallback return false end # IntervalDomain -JuMP.has_lower_bound(domain::IntervalDomain)::Bool = true +JuMP.has_lower_bound(domain::IntervalDomain) = true # DistributionDomain (Univariate) -JuMP.has_lower_bound(domain::UniDistributionDomain)::Bool = true +JuMP.has_lower_bound(domain::UniDistributionDomain) = true # CollectionDomain -function JuMP.has_lower_bound(domain::CollectionDomain)::Bool +function JuMP.has_lower_bound(domain::CollectionDomain) for s in collection_domains(domain) if !JuMP.has_lower_bound(s) return false @@ -146,15 +176,15 @@ function JuMP.lower_bound(domain::AbstractInfiniteDomain) # fallback end # IntervalDomain -JuMP.lower_bound(domain::IntervalDomain)::Float64 = domain.lower_bound +JuMP.lower_bound(domain::IntervalDomain) = domain.lower_bound # DistributionDomain (Univariate) -function JuMP.lower_bound(domain::UniDistributionDomain)::Real +function JuMP.lower_bound(domain::UniDistributionDomain) return minimum(domain.distribution) end # CollectionDomain -function JuMP.lower_bound(domain::CollectionDomain)::Vector{<:Real} +function JuMP.lower_bound(domain::CollectionDomain) return [JuMP.lower_bound(i) for i in collection_domains(domain)] end @@ -174,27 +204,30 @@ julia> set_lower_bound(domain, 0.5) [0.5, 1] ``` """ -function JuMP.set_lower_bound(domain::AbstractInfiniteDomain, - lower::Union{Real, Vector{<:Real}}) # fallback +function JuMP.set_lower_bound( + domain::AbstractInfiniteDomain, + lower::Union{Real, Vector{<:Real}} + ) # fallback type = typeof(domain) error("`JuMP.set_lower_bound` not defined for infinite domain of type $type.") end # IntervalDomain -function JuMP.set_lower_bound(domain::IntervalDomain, lower::Real)::IntervalDomain +function JuMP.set_lower_bound(domain::IntervalDomain, lower::Real) return IntervalDomain(lower, domain.upper_bound) end # DistributionDomain -function JuMP.set_lower_bound(domain::Union{UniDistributionDomain, - MultiDistributionDomain}, - lower::Union{Real, Vector{<:Real}}) +function JuMP.set_lower_bound( + domain::Union{UniDistributionDomain,MultiDistributionDomain}, + lower::Union{Real, Vector{<:Real}} + ) error("Cannot set the lower bound of a distribution, try using " * "`Distributions.Truncated` instead.") end # CollectionDomain -function JuMP.set_lower_bound(domain::CollectionDomain, lower::Vector{<:Real})::CollectionDomain +function JuMP.set_lower_bound(domain::CollectionDomain, lower::Vector{<:Real}) domains = collection_domains(domain) new_domains = [JuMP.set_lower_bound(domains[i], lower[i]) for i in eachindex(domains)] return CollectionDomain(new_domains) @@ -218,18 +251,18 @@ julia> has_upper_bound(domain) true ``` """ -function JuMP.has_upper_bound(domain::AbstractInfiniteDomain)::Bool # fallback +function JuMP.has_upper_bound(domain::AbstractInfiniteDomain) # fallback return false end # IntervalDomain -JuMP.has_upper_bound(domain::IntervalDomain)::Bool = true +JuMP.has_upper_bound(domain::IntervalDomain) = true # DistributionDomain (Univariate) -JuMP.has_upper_bound(domain::UniDistributionDomain)::Bool = true +JuMP.has_upper_bound(domain::UniDistributionDomain) = true # CollectionDomain -function JuMP.has_upper_bound(domain::CollectionDomain)::Bool +function JuMP.has_upper_bound(domain::CollectionDomain) for i in collection_domains(domain) if !JuMP.has_upper_bound(i) return false @@ -260,15 +293,15 @@ function JuMP.upper_bound(domain::AbstractInfiniteDomain) # fallback end # IntervalDomain -JuMP.upper_bound(domain::IntervalDomain)::Float64 = domain.upper_bound +JuMP.upper_bound(domain::IntervalDomain) = domain.upper_bound # DistributionDomain (Univariate) -function JuMP.upper_bound(domain::UniDistributionDomain)::Real +function JuMP.upper_bound(domain::UniDistributionDomain) return maximum(domain.distribution) end # CollectionDomain -function JuMP.upper_bound(domain::CollectionDomain)::Vector{<:Real} +function JuMP.upper_bound(domain::CollectionDomain) return [JuMP.upper_bound(i) for i in collection_domains(domain)] end @@ -295,19 +328,21 @@ function JuMP.set_upper_bound(domain::AbstractInfiniteDomain, upper::Real) # fal end # IntervalDomain -function JuMP.set_upper_bound(domain::IntervalDomain, upper::Real)::IntervalDomain +function JuMP.set_upper_bound(domain::IntervalDomain, upper::Real) return IntervalDomain(domain.lower_bound, upper) end # DistributionDomain -function JuMP.set_upper_bound(domain::Union{UniDistributionDomain, - MultiDistributionDomain}, lower::Real) +function JuMP.set_upper_bound( + domain::Union{UniDistributionDomain,MultiDistributionDomain}, + lower::Real + ) error("Cannot set the upper bound of a distribution, try using " * "`Distributions.Truncated` instead.") end # CollectionDomain -function JuMP.set_upper_bound(domain::CollectionDomain, lower::Vector{<:Real})::CollectionDomain +function JuMP.set_upper_bound(domain::CollectionDomain, lower::Vector{<:Real}) domains = collection_domains(domain) new_domains = [JuMP.set_upper_bound(domains[i], lower[i]) for i in eachindex(domains)] return CollectionDomain(new_domains) @@ -451,20 +486,22 @@ should extend [`generate_support_values`](@ref) to enable this. Errors if the `domain` type and /or methods are unrecognized. This is intended as an internal method to be used by methods such as [`generate_and_add_supports!`](@ref). """ -function generate_supports(domain::AbstractInfiniteDomain; - num_supports::Int = DefaultNumSupports, - sig_digits::Int = DefaultSigDigits - )::Tuple +function generate_supports( + domain::AbstractInfiniteDomain; + num_supports::Int = DefaultNumSupports, + sig_digits::Int = DefaultSigDigits + ) return generate_support_values(domain, num_supports = num_supports, sig_digits = sig_digits) end # 2 arguments -function generate_supports(domain::AbstractInfiniteDomain, - method::Type{<:AbstractSupportLabel}; - num_supports::Int = DefaultNumSupports, - sig_digits::Int = DefaultSigDigits - )::Tuple +function generate_supports( + domain::AbstractInfiniteDomain, + method::Type{<:AbstractSupportLabel}; + num_supports::Int = DefaultNumSupports, + sig_digits::Int = DefaultSigDigits + ) return generate_support_values(domain, method, num_supports = num_supports, sig_digits = sig_digits) @@ -486,8 +523,7 @@ method dispatches for a given domain must ensure that `method` is positional argument without a default value (contrary to the definition above). Note that the `method` must be a subtype of either [`PublicLabel`](@ref) or [`InternalLabel`](@ref). """ -function generate_support_values(domain::AbstractInfiniteDomain, - args...; kwargs...) +function generate_support_values(domain::AbstractInfiniteDomain, args...; kwargs...) if isempty(args) error("`generate_support_values` has not been extended for infinite domains " * "of type `$(typeof(domain))`. This automatic support generation is not " * @@ -500,11 +536,12 @@ function generate_support_values(domain::AbstractInfiniteDomain, end # IntervalDomain and UniformGrid -function generate_support_values(domain::IntervalDomain, - method::Type{UniformGrid} = UniformGrid; - num_supports::Int = DefaultNumSupports, - sig_digits::Int = DefaultSigDigits, - )::Tuple{Vector{<:Real}, DataType} +function generate_support_values( + domain::IntervalDomain, + method::Type{UniformGrid} = UniformGrid; + num_supports::Int = DefaultNumSupports, + sig_digits::Int = DefaultSigDigits, + ) lb = JuMP.lower_bound(domain) ub = JuMP.upper_bound(domain) new_supports = round.(range(lb, stop = ub, length = num_supports), @@ -513,11 +550,12 @@ function generate_support_values(domain::IntervalDomain, end # IntervalDomain and MCSample -function generate_support_values(domain::IntervalDomain, - method::Type{MCSample}; - num_supports::Int = DefaultNumSupports, - sig_digits::Int = DefaultSigDigits, - )::Tuple{Vector{<:Real}, DataType} +function generate_support_values( + domain::IntervalDomain, + method::Type{MCSample}; + num_supports::Int = DefaultNumSupports, + sig_digits::Int = DefaultSigDigits, + ) lb = JuMP.lower_bound(domain) ub = JuMP.upper_bound(domain) dist = Distributions.Uniform(lb, ub) @@ -532,7 +570,7 @@ function generate_support_values( method::Type{WeightedSample} = WeightedSample; num_supports::Int = DefaultNumSupports, sig_digits::Int = DefaultSigDigits - )::Tuple{Array{<:Real}, DataType} + ) dist = domain.distribution new_supports = round.(Distributions.rand(dist, num_supports), sigdigits = sig_digits) @@ -545,7 +583,7 @@ function generate_support_values( method::Type{MCSample}; num_supports::Int = DefaultNumSupports, sig_digits::Int = DefaultSigDigits - )::Tuple{Vector{Float64}, DataType} + ) return generate_support_values(domain, WeightedSample; num_supports = num_supports, sig_digits = sig_digits)[1], method # TODO use an unwieghted sample... end @@ -556,7 +594,7 @@ function generate_support_values( method::Type{WeightedSample} = WeightedSample; num_supports::Int = DefaultNumSupports, sig_digits::Int = DefaultSigDigits - )::Tuple{Array{Float64, 2}, DataType} + ) dist = domain.distribution raw_supports = Distributions.rand(dist, num_supports) new_supports = Array{Float64}(undef, length(dist), num_supports) @@ -568,8 +606,10 @@ function generate_support_values( end # Generate the supports for a collection domain -function _generate_collection_supports(domain::CollectionDomain, num_supports::Int, - sig_digits::Int)::Array{Float64, 2} +function _generate_collection_supports( + domain::CollectionDomain, num_supports::Int, + sig_digits::Int + ) domains = collection_domains(domain) # build the support array transpose to fill in column order (leverage locality) trans_supports = Array{Float64, 2}(undef, num_supports, length(domains)) @@ -581,10 +621,12 @@ function _generate_collection_supports(domain::CollectionDomain, num_supports::I return permutedims(trans_supports) end -function _generate_collection_supports(domain::CollectionDomain, - method::Type{<:AbstractSupportLabel}, - num_supports::Int, - sig_digits::Int)::Array{Float64, 2} +function _generate_collection_supports( + domain::CollectionDomain, + method::Type{<:AbstractSupportLabel}, + num_supports::Int, + sig_digits::Int + ) domains = collection_domains(domain) # build the support array transpose to fill in column order (leverage locality) trans_supports = Array{Float64, 2}(undef, num_supports, length(domains)) @@ -598,59 +640,66 @@ function _generate_collection_supports(domain::CollectionDomain, end # CollectionDomain (IntervalDomains) -function generate_support_values(domain::CollectionDomain{IntervalDomain}, - method::Type{UniformGrid} = UniformGrid; - num_supports::Int = DefaultNumSupports, - sig_digits::Int = DefaultSigDigits - )::Tuple{Array{<:Real}, DataType} +function generate_support_values( + domain::CollectionDomain{IntervalDomain}, + method::Type{UniformGrid} = UniformGrid; + num_supports::Int = DefaultNumSupports, + sig_digits::Int = DefaultSigDigits + ) new_supports = _generate_collection_supports(domain, num_supports, sig_digits) return new_supports, method end -function generate_support_values(domain::CollectionDomain{IntervalDomain}, - method::Type{MCSample}; - num_supports::Int = DefaultNumSupports, - sig_digits::Int = DefaultSigDigits - )::Tuple{Array{<:Real}, DataType} +function generate_support_values( + domain::CollectionDomain{IntervalDomain}, + method::Type{MCSample}; + num_supports::Int = DefaultNumSupports, + sig_digits::Int = DefaultSigDigits + ) new_supports = _generate_collection_supports(domain, method, num_supports, sig_digits) return new_supports, method end # CollectionDomain (UniDistributionDomains) -function generate_support_values(domain::CollectionDomain{<:UniDistributionDomain}, - method::Type{WeightedSample} = WeightedSample; - num_supports::Int = DefaultNumSupports, - sig_digits::Int = DefaultSigDigits - )::Tuple{Array{<:Real}, DataType} +function generate_support_values( + domain::CollectionDomain{<:UniDistributionDomain}, + method::Type{WeightedSample} = WeightedSample; + num_supports::Int = DefaultNumSupports, + sig_digits::Int = DefaultSigDigits + ) new_supports = _generate_collection_supports(domain, num_supports, sig_digits) return new_supports, method end # CollectionDomain (InfiniteScalarDomains) -function generate_support_values(domain::CollectionDomain, - method::Type{Mixture} = Mixture; - num_supports::Int = DefaultNumSupports, - sig_digits::Int = DefaultSigDigits - )::Tuple{Array{<:Real}, DataType} +function generate_support_values( + domain::CollectionDomain, + method::Type{Mixture} = Mixture; + num_supports::Int = DefaultNumSupports, + sig_digits::Int = DefaultSigDigits + ) new_supports = _generate_collection_supports(domain, num_supports, sig_digits) return new_supports, method end # CollectionDomain (InfiniteScalarDomains) using purely MC sampling # this is useful for measure support generation -function generate_support_values(domain::CollectionDomain, - method::Type{MCSample}; - num_supports::Int = DefaultNumSupports, - sig_digits::Int = DefaultSigDigits - )::Tuple{Array{<:Real}, DataType} +function generate_support_values( + domain::CollectionDomain, + method::Type{MCSample}; + num_supports::Int = DefaultNumSupports, + sig_digits::Int = DefaultSigDigits + ) new_supports = _generate_collection_supports(domain, method, num_supports, sig_digits) return new_supports, method end # For label All: dispatch to default methods -function generate_support_values(domain::AbstractInfiniteDomain, ::Type{All}; - num_supports::Int = DefaultNumSupports, - sig_digits::Int = DefaultSigDigits) +function generate_support_values( + domain::AbstractInfiniteDomain, ::Type{All}; + num_supports::Int = DefaultNumSupports, + sig_digits::Int = DefaultSigDigits + ) return generate_support_values(domain, num_supports = num_supports, sig_digits = sig_digits) end diff --git a/src/scalar_parameters.jl b/src/scalar_parameters.jl index 6ba98531..2c59bd17 100644 --- a/src/scalar_parameters.jl +++ b/src/scalar_parameters.jl @@ -2,55 +2,57 @@ # CORE DISPATCHVARIABLEREF METHOD EXTENSIONS ################################################################################ # Extend dispatch_variable_ref -function dispatch_variable_ref(model::InfiniteModel, - index::IndependentParameterIndex - )::IndependentParameterRef +function dispatch_variable_ref( + model::InfiniteModel, + index::IndependentParameterIndex + ) return IndependentParameterRef(model, index) end -function dispatch_variable_ref(model::InfiniteModel, - index::FiniteParameterIndex - )::FiniteParameterRef +function dispatch_variable_ref( + model::InfiniteModel, + index::FiniteParameterIndex + ) return FiniteParameterRef(model, index) end # Extend _add_data_object -function _add_data_object(model::InfiniteModel, - object::ScalarParameterData{<:IndependentParameter} - )::IndependentParameterIndex +function _add_data_object( + model::InfiniteModel, + object::ScalarParameterData{<:IndependentParameter} + ) index = MOIUC.add_item(model.independent_params, object) push!(model.param_object_indices, index) return index end -function _add_data_object(model::InfiniteModel, - object::ScalarParameterData{<:FiniteParameter} - )::FiniteParameterIndex +function _add_data_object( + model::InfiniteModel, + object::ScalarParameterData{<:FiniteParameter} + ) return MOIUC.add_item(model.finite_params, object) end # Extend _data_dictionary (type based) -function _data_dictionary(model::InfiniteModel, - ::Type{IndependentParameter})::MOIUC.CleverDict +function _data_dictionary(model::InfiniteModel, ::Type{IndependentParameter}) return model.independent_params end -function _data_dictionary(model::InfiniteModel, - ::Type{FiniteParameter})::MOIUC.CleverDict +function _data_dictionary(model::InfiniteModel, ::Type{FiniteParameter}) return model.finite_params end # Extend _data_dictionary (ref based) -function _data_dictionary(pref::IndependentParameterRef)::MOIUC.CleverDict +function _data_dictionary(pref::IndependentParameterRef) return JuMP.owner_model(pref).independent_params end -function _data_dictionary(pref::FiniteParameterRef)::MOIUC.CleverDict +function _data_dictionary(pref::FiniteParameterRef) return JuMP.owner_model(pref).finite_params end # Extend _data_object -function _data_object(pref::ScalarParameterRef)::AbstractDataObject +function _data_object(pref::ScalarParameterRef) object = get(_data_dictionary(pref), JuMP.index(pref), nothing) if isnothing(object) error("Invalid scalar parameter reference, cannot find ", @@ -64,32 +66,32 @@ end # CORE OBJECT METHODS ################################################################################ # Extend _core_variable_object for IndependentParameterRefs -function _core_variable_object(pref::IndependentParameterRef)::IndependentParameter +function _core_variable_object(pref::IndependentParameterRef) return _data_object(pref).parameter end # Extend _core_variable_object for FiniteParameterRefs -function _core_variable_object(pref::FiniteParameterRef)::FiniteParameter +function _core_variable_object(pref::FiniteParameterRef) return _data_object(pref).parameter end # Extend _parameter_number -function _parameter_number(pref::IndependentParameterRef)::Int +function _parameter_number(pref::IndependentParameterRef) return _data_object(pref).parameter_num end # Extend _parameter_numbers -function _parameter_numbers(pref::IndependentParameterRef)::Vector{Int} +function _parameter_numbers(pref::IndependentParameterRef) return [_parameter_number(pref)] end # Extend _object_number -function _object_number(pref::IndependentParameterRef)::Int +function _object_number(pref::IndependentParameterRef) return _data_object(pref).object_num end # Extend _object_numbers -function _object_numbers(pref::IndependentParameterRef)::Vector{Int} +function _object_numbers(pref::IndependentParameterRef) return [_object_number(pref)] end @@ -99,7 +101,7 @@ function _adaptive_data_update( pref::ScalarParameterRef, param::P, data::ScalarParameterData{P} - )::Nothing where {P <: ScalarParameter} + ) where {P <: ScalarParameter} data.parameter = param return end @@ -109,7 +111,7 @@ function _adaptive_data_update( pref::ScalarParameterRef, param::P1, data::ScalarParameterData{P2} - )::Nothing where {P1, P2} + ) where {P1, P2} new_data = ScalarParameterData(param, data.object_num, data.parameter_num, data.name, data.parameter_func_indices, data.infinite_var_indices, @@ -124,8 +126,10 @@ function _adaptive_data_update( end # Extend _set_core_variable_object for ScalarParameterRefs -function _set_core_variable_object(pref::ScalarParameterRef, - param::ScalarParameter)::Nothing +function _set_core_variable_object( + pref::ScalarParameterRef, + param::ScalarParameter + ) _adaptive_data_update(pref, param, _data_object(pref)) return end @@ -137,9 +141,11 @@ end const DefaultDerivativeMethod = FiniteDifference() # Check that supports don't violate the domain bounds -function _check_supports_in_bounds(_error::Function, - supports::Union{<:Real, Vector{<:Real}}, - domain::AbstractInfiniteDomain)::Nothing +function _check_supports_in_bounds( + _error::Function, + supports::Union{<:Real, Vector{<:Real}}, + domain::AbstractInfiniteDomain + ) if !supports_in_domain(supports, domain) _error("Supports violate the domain bounds.") end @@ -179,6 +185,7 @@ function build_parameter( for (kwarg, _) in extra_kwargs _error("Unrecognized keyword argument $kwarg") end + domain = round_domain(domain, sig_digits) label = UserDefined length_supports = length(supports) if !isempty(supports) @@ -226,7 +233,7 @@ function build_parameter( _error::Function, value::Real; extra_kwargs... - )::FiniteParameter + ) for (kwarg, _) in extra_kwargs _error("Unrecognized keyword argument $kwarg") end @@ -261,7 +268,7 @@ function add_parameter( model::InfiniteModel, p::IndependentParameter, name::String = "" - )::GeneralVariableRef + ) obj_num = length(_param_object_indices(model)) + 1 param_num = model.last_param_num += 1 data_object = ScalarParameterData(p, obj_num, param_num, name) @@ -293,7 +300,7 @@ function add_parameter( model::InfiniteModel, p::FiniteParameter, name::String = "" - )::GeneralVariableRef + ) data_object = ScalarParameterData(p, -1, -1, name) obj_index = _add_data_object(model, data_object) model.name_to_param = nothing diff --git a/test/array_parameters.jl b/test/array_parameters.jl index 6b967057..66ff6c4b 100644 --- a/test/array_parameters.jl +++ b/test/array_parameters.jl @@ -118,7 +118,7 @@ end @test_throws ErrorException InfiniteOpt._build_parameters(error, [domain1, domain1], inds1, supports = [[0, 0], [2]]) @test_throws ErrorException InfiniteOpt._build_parameters(error, [domain1, domain1], inds1, derivative_method = [2, 2]) # test vector - @test InfiniteOpt._build_parameters(error, [domain1, domain1], inds1).domain == domain1 + @test InfiniteOpt._build_parameters(error, [domain1, domain1], inds1).domain isa CollectionDomain @test InfiniteOpt._build_parameters(error, [domain1, domain1], inds1).supports == Dict{Vector{Float64}, Set{DataType}}() @test InfiniteOpt._build_parameters(error, [domain1, domain1], inds1, supports = [0, 0]).supports == Dict([0., 0.] => Set([UserDefined])) @test InfiniteOpt._build_parameters(error, [domain1, domain1], inds1).sig_digits isa Int diff --git a/test/extensions/infinite_domain.jl b/test/extensions/infinite_domain.jl index 8bba9ac4..6b0404b0 100644 --- a/test/extensions/infinite_domain.jl +++ b/test/extensions/infinite_domain.jl @@ -10,11 +10,22 @@ struct MyNewDomain <: InfiniteOpt.InfiniteScalarDomain end end +# Extend round_domain (only needed if rounding is appropriate for MyNewDomain) +function InfiniteOpt.round_domain( + domain::MyNewDomain, + sig_digits::Int + ) + # Recreate the domain with proper significant digits + attr1 = round(domain.attr1, sigdigits = sig_digits) # REPLACE WITH ROUNDING FOR YOUR DOMAIN + attr2 = round(domain.attr2, sigdigits = sig_digits) # REPLACE WITH ROUNDING FOR YOUR DOMAIN + return MyNewDomain(attr1, attr2) +end + # Extend supports_in_domain function InfiniteOpt.supports_in_domain( supports::Union{Real, Vector{<:Real}}, domain::MyNewDomain - )::Bool + ) # DETERMINE IF SUPPORTS ARE IN THE DOMAIN OF `domain` in_domain = all(domain.attr1 .<= supports .<= domain.attr2) # REPLACE WITH ACTUAL CHECK return in_domain @@ -25,14 +36,14 @@ function InfiniteOpt.generate_support_values( domain::MyNewDomain; num_supports::Int = 10, sig_digits::Int = 5 - )::Tuple{Vector{Float64}, DataType} + ) # REPLACE BELOW WITH METHODS TO GENERATE `num_samples` with `sig_fig` supports = collect(range(domain.attr1, stop = domain.attr2, length = num_supports)) return round.(supports, sigdigits = sig_digits), UniformGrid end # Extend JuMP.has_lower_bound (optional if the answer is always false) -function JuMP.has_lower_bound(domain::MyNewDomain)::Bool +function JuMP.has_lower_bound(domain::MyNewDomain) # INSERT NECESSARY CHECKS IF NEEDED has_bound = true # REPLACE WITH ACTUAL RESULT return has_bound @@ -44,12 +55,12 @@ function JuMP.lower_bound(domain::MyNewDomain) end # Extend JuMP.set_lower_bound if appropriate -function JuMP.set_lower_bound(domain::MyNewDomain, lower::Real)::MyNewDomain +function JuMP.set_lower_bound(domain::MyNewDomain, lower::Real) return MyNewDomain(lower, domain.attr2) # REPLACE WITH ACTUAL CONSTRUCTOR end # Extend JuMP.has_upper_bound (optional if the answer is always false) -function JuMP.has_upper_bound(domain::MyNewDomain)::Bool +function JuMP.has_upper_bound(domain::MyNewDomain) # INSERT NECESSARY CHECKS IF NEEDED has_bound = true # REPLACE WITH ACTUAL RESULT return has_bound @@ -61,7 +72,7 @@ function JuMP.upper_bound(domain::MyNewDomain) end # Extend JuMP.set_upper_bound if appropriate -function JuMP.set_upper_bound(domain::MyNewDomain, upper::Real)::MyNewDomain +function JuMP.set_upper_bound(domain::MyNewDomain, upper::Real) return MyNewDomain(domain.attr1, upper) # REPLACE WITH ACTUAL CONSTRUCTOR end diff --git a/test/scalar_parameters.jl b/test/scalar_parameters.jl index 95e9a1e5..546c367a 100644 --- a/test/scalar_parameters.jl +++ b/test/scalar_parameters.jl @@ -1093,4 +1093,12 @@ end @test fill_in_supports!(pref1, num_supports = 20) isa Nothing @test length(supports(pref1)) == 20 end + # test sigfig changes on infinite domain + @testset "Sigfig support addition test" begin + m = InfiniteModel() + supps = [0.8236475079774124, 0.9103565379264364] + @infinite_parameter(m, p in [supps[1], supps[2]]) + @test fill_in_supports!(p, num_supports = 8) isa Nothing + @test length(supports(p)) == 8 + end end