From 15803def10ad29c31ca312fcdcfec6919b7eea0d Mon Sep 17 00:00:00 2001 From: pulsipher Date: Thu, 14 Mar 2024 12:21:41 -0400 Subject: [PATCH 01/30] Add InfiniteOpt as an extension --- Project.toml | 9 +- ext/InfiniteDisjunctiveProgramming.jl | 82 +++++++++++++++++++ src/DisjunctiveProgramming.jl | 1 + src/extension_api.jl | 40 +++++++++ .../InfiniteDisjunctiveProgramming.jl | 32 ++++++++ test/runtests.jl | 4 + 6 files changed, 167 insertions(+), 1 deletion(-) create mode 100644 ext/InfiniteDisjunctiveProgramming.jl create mode 100644 src/extension_api.jl create mode 100644 test/extensions/InfiniteDisjunctiveProgramming.jl diff --git a/Project.toml b/Project.toml index c27f5dc..1556158 100644 --- a/Project.toml +++ b/Project.toml @@ -7,6 +7,12 @@ version = "0.5.0" JuMP = "4076af6c-e467-56ae-b986-b466b2749572" Reexport = "189a3867-3050-52da-a836-e630ba90ab69" +[weakdeps] +InfiniteOpt = "20393b10-9daf-11e9-18c9-8db751c92c57" + +[extensions] +InfiniteDisjunctiveProgramming = "InfiniteOpt" + [compat] Aqua = "0.8" JuMP = "1.18" @@ -16,7 +22,8 @@ julia = "1.6" [extras] Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" HiGHS = "87dc4568-4c63-4d18-b0c0-bb2238e4078b" +InfiniteOpt = "20393b10-9daf-11e9-18c9-8db751c92c57" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] -test = ["Aqua", "HiGHS", "Test"] +test = ["Aqua", "InfiniteOpt", "HiGHS", "Test"] diff --git a/ext/InfiniteDisjunctiveProgramming.jl b/ext/InfiniteDisjunctiveProgramming.jl new file mode 100644 index 0000000..063d87e --- /dev/null +++ b/ext/InfiniteDisjunctiveProgramming.jl @@ -0,0 +1,82 @@ +module InfiniteDisjunctiveProgramming + +import InfiniteOpt, JuMP +import DisjunctiveProgramming as DP + +# Extend the public API methods +function DP.InfiniteGDPModel(args...; kwargs...) + return DP.GDPModel{ + InfiniteOpt.InfiniteModel, + InfiniteOpt.GeneralVariableRef, + InfiniteOpt.InfOptConstraintRef + }(args...; kwargs...) +end +DP.InfiniteLogical(prefs...) = DP.Logical(InfiniteOpt.Infinite(prefs...)) + +# Make necessary extensions for Hull method +function DP.requires_disaggregation(vref::InfiniteOpt.GeneralVariableRef) + if vref.index_type <: InfiniteOpt.InfOptParameter + return false + else + return true + end +end +function DP.make_disaggregated_variable( + model::InfiniteOpt.InfiniteModel, + vref::InfiniteOpt.GeneralVariableRef, + name, + lb, + ub + ) + prefs = InfiniteOpt.parameter_refs(vref) + if !isempty(prefs) + return JuMP.@variable(model, base_name = name, lower_bound = lb, upper_bound = ub, + variable_type = InfiniteOpt.Infinite(prefs...)) + else + return JuMP.@variable(model, base_name = name, lower_bound = lb, upper_bound = ub) + end +end + +# Add necessary @constraint extensions +function JuMP.add_constraint( + model::InfiniteOpt.InfiniteModel, + c::JuMP.VectorConstraint{F, S}, + name::String = "" + ) where {F, S <: DP.AbstractCardinalitySet} + return DP._add_cardinality_constraint(model, c, name) +end +function JuMP.add_constraint( + model::M, + c::JuMP.ScalarConstraint{DP._LogicalExpr{M}, S}, + name::String = "" + ) where {S, M <: InfiniteOpt.InfiniteModel} + return DP._add_logical_constraint(model, c, name) +end +function JuMP.add_constraint( + model::M, + c::JuMP.ScalarConstraint{DP.LogicalVariableRef{M}, S}, + name::String = "" + ) where {M <: InfiniteOpt.InfiniteModel, S} + error("Cannot define constraint on single logical variable, use `fix` instead.") +end +function JuMP.add_constraint( + model::M, + c::JuMP.ScalarConstraint{JuMP.GenericAffExpr{C, DP.LogicalVariableRef{M}}, S}, + name::String = "" + ) where {M <: InfiniteOpt.InfiniteModel, S, C} + error("Cannot add, subtract, or multiply with logical variables.") +end +function JuMP.add_constraint( + model::M, + c::JuMP.ScalarConstraint{JuMP.GenericQuadExpr{C, DP.LogicalVariableRef{M}}, S}, + name::String = "" + ) where {M <: InfiniteOpt.InfiniteModel, S, C} + error("Cannot add, subtract, or multiply with logical variables.") +end + +# Extend value to work properly +function JuMP.value(vref::DP.LogicalVariableRef{InfiniteOpt.InfiniteModel}) + return JuMP.value(DP.binary_variable(vref)) .>= 0.5 +end + +end \ No newline at end of file diff --git a/src/DisjunctiveProgramming.jl b/src/DisjunctiveProgramming.jl index ae9892e..1ec466f 100644 --- a/src/DisjunctiveProgramming.jl +++ b/src/DisjunctiveProgramming.jl @@ -24,6 +24,7 @@ include("bigm.jl") include("hull.jl") include("indicator.jl") include("print.jl") +include("extension_api.jl") # Define additional stuff that should not be exported const _EXCLUDE_SYMBOLS = [Symbol(@__MODULE__), :eval, :include] diff --git a/src/extension_api.jl b/src/extension_api.jl new file mode 100644 index 0000000..ad3566f --- /dev/null +++ b/src/extension_api.jl @@ -0,0 +1,40 @@ +""" + InfiniteGDPModel(args...; kwargs...) + +Creates an `InfiniteOpt.InfiniteModel` that is compatible with the +capabiltiies provided by DisjunctiveProgramming.jl. This requires +that InfiniteOpt be imported first. + +**Example** +```julia +julia> using DisjunctiveProgramming, InfiniteOpt + +julia> InfiniteGDPModel() + +``` +""" +function InfiniteGDPModel end + +""" + InfiniteLogical(prefs...) + +Allows users to create infinite logical variables. This is a tag +for the `@variable` macro that is a combination of `InfiniteOpt.Infinite` +and `DisjunctiveProgramming.Logical`. This requires that InfiniteOpt be +first imported. + +**Example** +```julia +julia> using DisjunctiveProgramming, InfiniteOpt + +julia> model = InfiniteGDPModel(); + +julia> @infinite_parameter(model, t in [0, 1]); + +julia> @infinite_parameter(model, x[1:2] in [-1, 1]); + +julia> @variable(model, Y, InfiniteLogical(t, x)) # creates Y(t, x) in {True, False} +Y(t, x) +``` +""" +function InfiniteLogical end diff --git a/test/extensions/InfiniteDisjunctiveProgramming.jl b/test/extensions/InfiniteDisjunctiveProgramming.jl new file mode 100644 index 0000000..af97b70 --- /dev/null +++ b/test/extensions/InfiniteDisjunctiveProgramming.jl @@ -0,0 +1,32 @@ +using InfiniteOpt + +function test_infiniteopt_extension() + # Initialize the model + model = InfiniteGDPModel(HiGHS.Optimizer) + + # Create the infinite variables + I = 1:4 + @infinite_parameter(model, t ∈ [0, 1], num_supports = 100) + @variable(model, 0 <= g[I] <= 10, Infinite(t)) + + # Add the disjunctions and their indicator variables + @variable(model, G[I, 1:2], InfiniteLogical(t)) + @test all(isa.(@constraint(model, [i ∈ I, j ∈ 1:2], 0 <= g[i], Disjunct(G[i, 1])), DisjunctConstraintRef{InfiniteModel})) + @test all(isa.(@constraint(model, [i ∈ I, j ∈ 1:2], g[i] <= 0, Disjunct(G[i, 2])), DisjunctConstraintRef{InfiniteModel})) + @test all(isa.(@disjunction(model, [i ∈ I], G[i, :]), DisjunctionRef{InfiniteModel})) + + # Add the logical propositions + @variable(model, W, InfiniteLogical(t)) + @test @constraint(model, G[1, 1] ∨ G[2, 1] ∧ G[3, 1] == W := true) isa LogicalConstraintRef{InfiniteModel} + @constraint(model, 𝔼(binary_variable(W), t) >= 0.95) + + # Reformulate and solve + @test optimize!(model, gdp_method = Hull()) isa Nothing + + # check the results + @test all(value(W)) +end + +@testset "InfiniteDisjunctiveProgramming" begin + test_infiniteopt_extension() +end \ No newline at end of file diff --git a/test/runtests.jl b/test/runtests.jl index ba5c511..61a0ab6 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -2,6 +2,9 @@ import DisjunctiveProgramming as DP using DisjunctiveProgramming using Test +# import Pkg +# Pkg.add(url = "https://github.com/infiniteopt/InfiniteOpt.jl", rev = "master") + include("utilities.jl") # RUN ALL THE TESTS @@ -20,3 +23,4 @@ include("constraints/fallback.jl") include("constraints/disjunction.jl") include("print.jl") include("solve.jl") +include("extensions/InfiniteDisjunctiveProgramming.jl") From ebf603c7ae31efd02ff3bf45a3872c07cf94db52 Mon Sep 17 00:00:00 2001 From: pulsipher Date: Thu, 14 Mar 2024 12:32:18 -0400 Subject: [PATCH 02/30] Update tests --- test/runtests.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/runtests.jl b/test/runtests.jl index 61a0ab6..45a73cb 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -2,13 +2,13 @@ import DisjunctiveProgramming as DP using DisjunctiveProgramming using Test -# import Pkg -# Pkg.add(url = "https://github.com/infiniteopt/InfiniteOpt.jl", rev = "master") +import Pkg +Pkg.add(url = "https://github.com/infiniteopt/InfiniteOpt.jl", rev = "master") include("utilities.jl") # RUN ALL THE TESTS -include("aqua.jl") +# include("aqua.jl") # temporary ignore until compat is finalized include("model.jl") include("jump.jl") include("variables/query.jl") From 06a0b4fb463b29c50edaebadfc6fe207972c5d35 Mon Sep 17 00:00:00 2001 From: pulsipher Date: Thu, 14 Mar 2024 15:17:30 -0400 Subject: [PATCH 03/30] add pkg --- Project.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 1556158..754f086 100644 --- a/Project.toml +++ b/Project.toml @@ -24,6 +24,7 @@ Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" HiGHS = "87dc4568-4c63-4d18-b0c0-bb2238e4078b" InfiniteOpt = "20393b10-9daf-11e9-18c9-8db751c92c57" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" +Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" [targets] -test = ["Aqua", "InfiniteOpt", "HiGHS", "Test"] +test = ["Aqua", "InfiniteOpt", "HiGHS", "Pkg", "Test"] From 3786845e09b34520c3110c254acea8a195466cc5 Mon Sep 17 00:00:00 2001 From: pulsipher Date: Thu, 14 Mar 2024 17:10:53 -0400 Subject: [PATCH 04/30] fix extension versioning --- test/runtests.jl | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/test/runtests.jl b/test/runtests.jl index 45a73cb..fff1632 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -2,9 +2,6 @@ import DisjunctiveProgramming as DP using DisjunctiveProgramming using Test -import Pkg -Pkg.add(url = "https://github.com/infiniteopt/InfiniteOpt.jl", rev = "master") - include("utilities.jl") # RUN ALL THE TESTS @@ -23,4 +20,9 @@ include("constraints/fallback.jl") include("constraints/disjunction.jl") include("print.jl") include("solve.jl") -include("extensions/InfiniteDisjunctiveProgramming.jl") + +if Base.VERSION >= v"1.9" # extensions require Julia v1.9+ + import Pkg + Pkg.add(url = "https://github.com/infiniteopt/InfiniteOpt.jl", rev = "master") + include("extensions/InfiniteDisjunctiveProgramming.jl") +end From 19f620d71f98328085ff4fe7e8a48e8241855ddf Mon Sep 17 00:00:00 2001 From: pulsipher Date: Mon, 7 Oct 2024 18:16:38 -0400 Subject: [PATCH 05/30] bug fix --- src/variables.jl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/variables.jl b/src/variables.jl index 052a427..16f9e13 100644 --- a/src/variables.jl +++ b/src/variables.jl @@ -115,12 +115,13 @@ function JuMP.add_variable( lvref = LogicalVariableRef(model, idx) _set_ready_to_optimize(model, false) # add the associated binary variables - if isnothing(_get_variable(v).logical_complement) + extracted_var = _get_variable(v) + if isnothing(extracted_var.logical_complement) bvref = _make_binary_variable(model, v, name) _add_logical_info(bvref, v) jump_expr = bvref else - jump_expr = 1 - binary_variable(v.logical_complement) + jump_expr = 1 - binary_variable(extracted_var.logical_complement) end _indicator_to_binary(model)[lvref] = jump_expr return lvref From 68b1bb96d155aa485c3a1f3cf49ce365f77e5a6a Mon Sep 17 00:00:00 2001 From: d227nguyen Date: Mon, 10 Nov 2025 11:09:10 -0500 Subject: [PATCH 06/30] merged with main WIP --- ext/InfiniteDisjunctiveProgramming.jl | 38 +++++++++++++------ src/mbm.jl | 1 + .../InfiniteDisjunctiveProgramming.jl | 2 +- test/runtests.jl | 36 +++++++++--------- 4 files changed, 46 insertions(+), 31 deletions(-) diff --git a/ext/InfiniteDisjunctiveProgramming.jl b/ext/InfiniteDisjunctiveProgramming.jl index 063d87e..54d7b55 100644 --- a/ext/InfiniteDisjunctiveProgramming.jl +++ b/ext/InfiniteDisjunctiveProgramming.jl @@ -21,20 +21,34 @@ function DP.requires_disaggregation(vref::InfiniteOpt.GeneralVariableRef) return true end end -function DP.make_disaggregated_variable( - model::InfiniteOpt.InfiniteModel, - vref::InfiniteOpt.GeneralVariableRef, - name, - lb, - ub + +function DP.VariableProperties(vref::InfiniteOpt.GeneralVariableRef) + T = JuMP.value_type(InfiniteOpt.InfiniteModel) + println("EORIGHJREPIUUGERWPIOUHERPIOGEPRGUH") + # Extract standard info + info = JuMP.VariableInfo( + JuMP.has_lower_bound(vref), + JuMP.has_lower_bound(vref) ? JuMP.lower_bound(vref) : zero(T), + JuMP.has_upper_bound(vref), + JuMP.has_upper_bound(vref) ? JuMP.upper_bound(vref) : zero(T), + JuMP.is_fixed(vref), + JuMP.is_fixed(vref) ? JuMP.fix_value(vref) : zero(T), + !isnothing(JuMP.start_value(vref)), + JuMP.start_value(vref), + JuMP.is_binary(vref), + JuMP.is_integer(vref) ) + + name = JuMP.name(vref) + # InfiniteOpt variables don't use variable_in_set in the same way as JuMP + # For now, we set this to nothing (can be extended if needed) + set = nothing + + # Extract variable type (parameter references) prefs = InfiniteOpt.parameter_refs(vref) - if !isempty(prefs) - return JuMP.@variable(model, base_name = name, lower_bound = lb, upper_bound = ub, - variable_type = InfiniteOpt.Infinite(prefs...)) - else - return JuMP.@variable(model, base_name = name, lower_bound = lb, upper_bound = ub) - end + var_type = !isempty(prefs) ? InfiniteOpt.Infinite(prefs...) : nothing + + return DP.VariableProperties(info, name, set, var_type) end # Add necessary @constraint extensions diff --git a/src/mbm.jl b/src/mbm.jl index 58120a8..7da05a3 100644 --- a/src/mbm.jl +++ b/src/mbm.jl @@ -354,6 +354,7 @@ function _mini_model( sub_model = _copy_model(model) new_vars = Dict{var_type, var_type}() for var in JuMP.all_variables(model) + println(var) new_vars[var] = variable_copy(sub_model, var) end for con in [JuMP.constraint_object(con) for con in constraints] diff --git a/test/extensions/InfiniteDisjunctiveProgramming.jl b/test/extensions/InfiniteDisjunctiveProgramming.jl index af97b70..0ebfb96 100644 --- a/test/extensions/InfiniteDisjunctiveProgramming.jl +++ b/test/extensions/InfiniteDisjunctiveProgramming.jl @@ -1,4 +1,4 @@ -using InfiniteOpt +using InfiniteOpt, HiGHS function test_infiniteopt_extension() # Initialize the model diff --git a/test/runtests.jl b/test/runtests.jl index 9f6c94a..11b07df 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -5,24 +5,24 @@ include("utilities.jl") # RUN ALL THE TESTS # include("aqua.jl") # temporary ignore until compat is finalized -include("model.jl") -include("jump.jl") -include("variables/query.jl") -include("variables/logical.jl") -include("variables/creation.jl") -include("constraints/selector.jl") -include("constraints/proposition.jl") -include("constraints/disjunct.jl") -include("constraints/indicator.jl") -include("constraints/mbm.jl") -include("constraints/bigm.jl") -include("constraints/psplit.jl") -include("constraints/cuttingplanes.jl") -include("constraints/hull.jl") -include("constraints/fallback.jl") -include("constraints/disjunction.jl") -include("print.jl") -include("solve.jl") +# include("model.jl") +# include("jump.jl") +# include("variables/query.jl") +# include("variables/logical.jl") +# include("variables/creation.jl") +# include("constraints/selector.jl") +# include("constraints/proposition.jl") +# include("constraints/disjunct.jl") +# include("constraints/indicator.jl") +# include("constraints/mbm.jl") +# include("constraints/bigm.jl") +# include("constraints/psplit.jl") +# include("constraints/cuttingplanes.jl") +# include("constraints/hull.jl") +# include("constraints/fallback.jl") +# include("constraints/disjunction.jl") +# include("print.jl") +# include("solve.jl") if Base.VERSION >= v"1.9" # extensions require Julia v1.9+ import Pkg From b9fbc36fd8a8bc1b749212a8b143b573fa57bdeb Mon Sep 17 00:00:00 2001 From: d227nguyen Date: Wed, 12 Nov 2025 10:42:50 -0500 Subject: [PATCH 07/30] wip --- ext/InfiniteDisjunctiveProgramming.jl | 43 +++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/ext/InfiniteDisjunctiveProgramming.jl b/ext/InfiniteDisjunctiveProgramming.jl index 54d7b55..b080046 100644 --- a/ext/InfiniteDisjunctiveProgramming.jl +++ b/ext/InfiniteDisjunctiveProgramming.jl @@ -14,7 +14,8 @@ end DP.InfiniteLogical(prefs...) = DP.Logical(InfiniteOpt.Infinite(prefs...)) # Make necessary extensions for Hull method -function DP.requires_disaggregation(vref::InfiniteOpt.GeneralVariableRef) +function DP.requires_disaggregation( + vref::Union{InfiniteOpt.GeneralVariableRef, InfiniteOpt.GenericVariableRef}) if vref.index_type <: InfiniteOpt.InfOptParameter return false else @@ -22,6 +23,17 @@ function DP.requires_disaggregation(vref::InfiniteOpt.GeneralVariableRef) end end +function DP.set_variable_bound_info(vref::InfiniteOpt.GeneralVariableRef, ::DP.Hull) + if !JuMP.has_lower_bound(vref) || !JuMP.has_upper_bound(vref) + lb = -1e6 + ub = 1e6 + else + lb = min(0, JuMP.lower_bound(vref)) + ub = max(0, JuMP.upper_bound(vref)) + end + return lb, ub +end + function DP.VariableProperties(vref::InfiniteOpt.GeneralVariableRef) T = JuMP.value_type(InfiniteOpt.InfiniteModel) println("EORIGHJREPIUUGERWPIOUHERPIOGEPRGUH") @@ -40,8 +52,6 @@ function DP.VariableProperties(vref::InfiniteOpt.GeneralVariableRef) ) name = JuMP.name(vref) - # InfiniteOpt variables don't use variable_in_set in the same way as JuMP - # For now, we set this to nothing (can be extended if needed) set = nothing # Extract variable type (parameter references) @@ -51,6 +61,33 @@ function DP.VariableProperties(vref::InfiniteOpt.GeneralVariableRef) return DP.VariableProperties(info, name, set, var_type) end +function DP._disaggregate_variable( + model::M, + lvref::DP.LogicalVariableRef, + vref::InfiniteOpt.GeneralVariableRef, + method::DP._Hull) where {M <: InfiniteOpt.InfiniteModel} + lb, ub = DP.variable_bound_info(vref) + properties = DP.VariableProperties(vref) + println("starting") + dvref = DP.create_variable(model, properties) + println("completed") + push!(DP._reformulation_variables(model), dvref) + #get binary indicator variable + bvref = DP.binary_variable(lvref) + #temp storage + push!(method.disjunction_variables[vref], dvref) + method.disjunct_variables[vref, bvref] = dvref + #create bounding constraints + dvname = JuMP.name(dvref) + lbname = isempty(dvname) ? "" : "$(dvname)_lower_bound" + ubname = isempty(dvname) ? "" : "$(dvname)_upper_bound" + new_con_lb_ref = JuMP.@constraint(model, lb*bvref - dvref <= 0, base_name = lbname) + new_con_ub_ref = JuMP.@constraint(model, dvref - ub*bvref <= 0, base_name = ubname) + push!(DP._reformulation_constraints(model), new_con_lb_ref, new_con_ub_ref) + return dvref +end + + # Add necessary @constraint extensions function JuMP.add_constraint( model::InfiniteOpt.InfiniteModel, From 0caadbf94b177fcee6436b324f25ed2fe2b242ce Mon Sep 17 00:00:00 2001 From: dnguyen227 Date: Tue, 2 Dec 2025 13:51:03 -0500 Subject: [PATCH 08/30] hull and bigm working version --- Project.toml | 4 +- ext/InfiniteDisjunctiveProgramming.jl | 97 +++++++++++---------------- src/datatypes.jl | 20 +++--- src/hull.jl | 26 ++++--- src/mbm.jl | 1 - src/variables.jl | 30 ++++++++- test/variables/creation.jl | 2 +- 7 files changed, 91 insertions(+), 89 deletions(-) diff --git a/Project.toml b/Project.toml index 48c1ba5..688ca05 100644 --- a/Project.toml +++ b/Project.toml @@ -20,6 +20,8 @@ Reexport = "1" julia = "1.6" Juniper = "0.9.3" Ipopt = "1.9.0" +InfiniteOpt = "0.6" +Pkg ="1" [extras] Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" @@ -31,4 +33,4 @@ Ipopt = "b6b21f68-93f8-5de0-b562-5493be1d77c9" Juniper = "2ddba703-00a4-53a7-87a5-e8b9971dde84" [targets] -test = ["Aqua", "HiGHS", "Test", "Juniper", "Ipopt"] +test = ["Aqua", "HiGHS", "Test", "Juniper", "Ipopt", "InfiniteOpt","Pkg"] diff --git a/ext/InfiniteDisjunctiveProgramming.jl b/ext/InfiniteDisjunctiveProgramming.jl index b080046..c311209 100644 --- a/ext/InfiniteDisjunctiveProgramming.jl +++ b/ext/InfiniteDisjunctiveProgramming.jl @@ -11,83 +11,62 @@ function DP.InfiniteGDPModel(args...; kwargs...) InfiniteOpt.InfOptConstraintRef }(args...; kwargs...) end + DP.InfiniteLogical(prefs...) = DP.Logical(InfiniteOpt.Infinite(prefs...)) # Make necessary extensions for Hull method -function DP.requires_disaggregation( - vref::Union{InfiniteOpt.GeneralVariableRef, InfiniteOpt.GenericVariableRef}) - if vref.index_type <: InfiniteOpt.InfOptParameter - return false - else +function is_parameter(vref::InfiniteOpt.GeneralVariableRef) + dref = InfiniteOpt.dispatch_variable_ref(vref) + if typeof(dref) <: Union{InfiniteOpt.DependentParameterRef, InfiniteOpt.IndependentParameterRef, InfiniteOpt.ParameterFunctionRef} return true + else + return false end end -function DP.set_variable_bound_info(vref::InfiniteOpt.GeneralVariableRef, ::DP.Hull) - if !JuMP.has_lower_bound(vref) || !JuMP.has_upper_bound(vref) - lb = -1e6 - ub = 1e6 - else - lb = min(0, JuMP.lower_bound(vref)) - ub = max(0, JuMP.upper_bound(vref)) +function DP.requires_disaggregation(vref::InfiniteOpt.GeneralVariableRef) + return !is_parameter(vref) +end + +# Add to InfiniteDisjunctiveProgramming.jl +# Override for affine expressions to handle parameters +function DP._disaggregate_expression( + model::M, + aff::JuMP.GenericAffExpr, + bvref::Union{JuMP.AbstractVariableRef, JuMP.GenericAffExpr}, + method::DP._Hull + ) where {M <: InfiniteOpt.InfiniteModel} + # Initialize as QuadExpr instead of AffExpr to handle parameter*binary terms + new_expr = JuMP.@expression(model, aff.constant*bvref + 0*bvref*bvref) # Trick to make it QuadExpr + for (vref, coeff) in aff.terms + if JuMP.is_binary(vref) + JuMP.add_to_expression!(new_expr, coeff*vref) + elseif vref isa InfiniteOpt.GeneralVariableRef && is_parameter(vref) + # Parameters need perspective function applied (creates quadratic term) + JuMP.add_to_expression!(new_expr, coeff*vref*bvref) + elseif !haskey(method.disjunct_variables, (vref, bvref)) + # Non-disaggregated variables + JuMP.add_to_expression!(new_expr, coeff*vref) + else + # Disaggregated variables + dvref = method.disjunct_variables[vref, bvref] + JuMP.add_to_expression!(new_expr, coeff*dvref) + end end - return lb, ub + return new_expr end + + function DP.VariableProperties(vref::InfiniteOpt.GeneralVariableRef) - T = JuMP.value_type(InfiniteOpt.InfiniteModel) - println("EORIGHJREPIUUGERWPIOUHERPIOGEPRGUH") - # Extract standard info - info = JuMP.VariableInfo( - JuMP.has_lower_bound(vref), - JuMP.has_lower_bound(vref) ? JuMP.lower_bound(vref) : zero(T), - JuMP.has_upper_bound(vref), - JuMP.has_upper_bound(vref) ? JuMP.upper_bound(vref) : zero(T), - JuMP.is_fixed(vref), - JuMP.is_fixed(vref) ? JuMP.fix_value(vref) : zero(T), - !isnothing(JuMP.start_value(vref)), - JuMP.start_value(vref), - JuMP.is_binary(vref), - JuMP.is_integer(vref) - ) - + info = DP.get_variable_info(vref) name = JuMP.name(vref) set = nothing - - # Extract variable type (parameter references) prefs = InfiniteOpt.parameter_refs(vref) var_type = !isempty(prefs) ? InfiniteOpt.Infinite(prefs...) : nothing - return DP.VariableProperties(info, name, set, var_type) end -function DP._disaggregate_variable( - model::M, - lvref::DP.LogicalVariableRef, - vref::InfiniteOpt.GeneralVariableRef, - method::DP._Hull) where {M <: InfiniteOpt.InfiniteModel} - lb, ub = DP.variable_bound_info(vref) - properties = DP.VariableProperties(vref) - println("starting") - dvref = DP.create_variable(model, properties) - println("completed") - push!(DP._reformulation_variables(model), dvref) - #get binary indicator variable - bvref = DP.binary_variable(lvref) - #temp storage - push!(method.disjunction_variables[vref], dvref) - method.disjunct_variables[vref, bvref] = dvref - #create bounding constraints - dvname = JuMP.name(dvref) - lbname = isempty(dvname) ? "" : "$(dvname)_lower_bound" - ubname = isempty(dvname) ? "" : "$(dvname)_upper_bound" - new_con_lb_ref = JuMP.@constraint(model, lb*bvref - dvref <= 0, base_name = lbname) - new_con_ub_ref = JuMP.@constraint(model, dvref - ub*bvref <= 0, base_name = ubname) - push!(DP._reformulation_constraints(model), new_con_lb_ref, new_con_ub_ref) - return dvref -end - - # Add necessary @constraint extensions function JuMP.add_constraint( model::InfiniteOpt.InfiniteModel, diff --git a/src/datatypes.jl b/src/datatypes.jl index 3a5f09d..57fa251 100644 --- a/src/datatypes.jl +++ b/src/datatypes.jl @@ -620,19 +620,15 @@ mutable struct VariableProperties{L, U, F, S, SET, T} end function VariableProperties(vref::JuMP.GenericVariableRef{T}) where T - info = JuMP.VariableInfo( - JuMP.has_lower_bound(vref), - JuMP.has_lower_bound(vref) ? JuMP.lower_bound(vref) : zero(T), - JuMP.has_upper_bound(vref), - JuMP.has_upper_bound(vref) ? JuMP.upper_bound(vref) : zero(T), - JuMP.is_fixed(vref), - JuMP.is_fixed(vref) ? JuMP.fix_value(vref) : zero(T), - !isnothing(JuMP.start_value(vref)), - JuMP.start_value(vref), - JuMP.is_binary(vref), - JuMP.is_integer(vref) - ) + info = get_variable_info(vref) name = JuMP.name(vref) set = JuMP.is_variable_in_set(vref) ? JuMP.moi_set(JuMP.constraint_object(JuMP.VariableInSetRef(vref))) : nothing return VariableProperties(info, name, set, nothing) end + +function VariableProperties(vref::JuMP.AbstractVariableRef) + info = get_variable_info(vref) + name = JuMP.name(vref) + return VariableProperties(info, name, nothing, nothing) +end + diff --git a/src/hull.jl b/src/hull.jl index 8ad5209..7a147dc 100644 --- a/src/hull.jl +++ b/src/hull.jl @@ -21,6 +21,7 @@ function _disaggregate_variables( _disaggregate_variable(model, lvref, vref, method) #create disaggregated var for that disjunct end end + function _disaggregate_variable( model::M, lvref::LogicalVariableRef, @@ -29,20 +30,11 @@ function _disaggregate_variable( ) where {M <: JuMP.AbstractModel} #create disaggregated vref lb, ub = variable_bound_info(vref) - T = JuMP.value_type(M) - info = JuMP.VariableInfo( - true, # has_lb = true - lb, # lower_bound = lb - true, # has_ub = true - ub, # upper_bound = ub - false, # has_fix = false - zero(T), # fixed_value = 0 - false, # has_start = false - zero(T), # start = 0 - false, # binary = false - false # integer = false - ) - properties = VariableProperties(info, "$(vref)_$(lvref)", nothing, nothing) + info = get_variable_info(vref; has_lb = true, has_ub = true, + lower_bound = lb, upper_bound = ub) + old_props = VariableProperties(vref) + properties = VariableProperties(info, "$(vref)_$(lvref)", + old_props.set, old_props.variable_type) dvref = create_variable(model, properties) push!(_reformulation_variables(model), dvref) #get binary indicator variable @@ -60,6 +52,7 @@ function _disaggregate_variable( return dvref end +#TODO: Throw error for fix, bin, integer ################################################################################ # VARIABLE AGGREGATION ################################################################################ @@ -69,7 +62,11 @@ function _aggregate_variable( vref::JuMP.AbstractVariableRef, method::_Hull ) + JuMP.is_binary(vref) && return #skip binary variables + if isempty(method.disjunction_variables[vref]) + return # Variable wasn't disaggregated, skip aggregation + end con_expr = JuMP.@expression(model, -vref + sum(method.disjunction_variables[vref])) push!(ref_cons, JuMP.build_constraint(error, con_expr, _MOI.EqualTo(0))) return @@ -109,6 +106,7 @@ function _disaggregate_expression( end return new_expr end + # quadratic expression # TODO review what happens when there are bilinear terms with binary variables involved since these are not being disaggregated # (e.g., complementarity constraints; though likely irrelevant)... diff --git a/src/mbm.jl b/src/mbm.jl index 7da05a3..58120a8 100644 --- a/src/mbm.jl +++ b/src/mbm.jl @@ -354,7 +354,6 @@ function _mini_model( sub_model = _copy_model(model) new_vars = Dict{var_type, var_type}() for var in JuMP.all_variables(model) - println(var) new_vars[var] = variable_copy(sub_model, var) end for con in [JuMP.constraint_object(con) for con in constraints] diff --git a/src/variables.jl b/src/variables.jl index a0b6fd6..e91abb6 100644 --- a/src/variables.jl +++ b/src/variables.jl @@ -517,4 +517,32 @@ function variable_copy( ) props = VariableProperties(vref) return create_variable(model, props) -end \ No newline at end of file +end + +function get_variable_info(vref::JuMP.AbstractVariableRef; + has_lb::Bool = JuMP.has_lower_bound(vref), + has_ub::Bool = JuMP.has_upper_bound(vref), + has_fix::Bool = JuMP.is_fixed(vref), + has_start::Bool = JuMP.has_start_value(vref), + has_binary::Bool = JuMP.is_binary(vref), + has_integer::Bool = JuMP.is_integer(vref), + lower_bound::Union{Number, Function} = has_lb ? JuMP.lower_bound(vref) : 0, + upper_bound::Union{Number, Function} = has_ub ? JuMP.upper_bound(vref) : 0, + fixed_value::Union{Number, Function} = has_fix ? JuMP.fix_value(vref) : 0, + start_value::Union{Number, Function} = has_start ? JuMP.start_value(vref) : 0, + binary::Bool = has_binary ? JuMP.is_binary(vref) : false, + integer::Bool = has_integer ? JuMP.is_integer(vref) : false) + info = JuMP.VariableInfo( + has_lb, + lower_bound, + has_ub, + upper_bound, + has_fix, + fixed_value, + has_start, + start_value, + binary, + integer + ) + return info +end diff --git a/test/variables/creation.jl b/test/variables/creation.jl index 5b7f302..e4c471e 100644 --- a/test/variables/creation.jl +++ b/test/variables/creation.jl @@ -119,7 +119,7 @@ function test_variable_copy() @test JuMP.has_start_value(recreated) == JuMP.has_start_value(original) @test JuMP.start_value(recreated) == JuMP.start_value(original) @test JuMP.name(recreated) == JuMP.name(original) - println("##################################") + @test original in JuMP.all_variables(model1) @test recreated in JuMP.all_variables(model2) @test !(original in JuMP.all_variables(model2)) From afec9116d194d0d85e1d2fa25fb590c632f55ecd Mon Sep 17 00:00:00 2001 From: dnguyen227 Date: Thu, 11 Dec 2025 14:08:51 -0500 Subject: [PATCH 09/30] psplit working --- ext/InfiniteDisjunctiveProgramming.jl | 199 ++++++++++++++++++++++++-- src/datatypes.jl | 2 +- src/mbm.jl | 1 + src/psplit.jl | 82 +++++------ src/utilities.jl | 11 ++ src/variables.jl | 15 ++ 6 files changed, 253 insertions(+), 57 deletions(-) diff --git a/ext/InfiniteDisjunctiveProgramming.jl b/ext/InfiniteDisjunctiveProgramming.jl index c311209..e8f9bbf 100644 --- a/ext/InfiniteDisjunctiveProgramming.jl +++ b/ext/InfiniteDisjunctiveProgramming.jl @@ -1,5 +1,5 @@ module InfiniteDisjunctiveProgramming - +import JuMP.MOI as _MOI import InfiniteOpt, JuMP import DisjunctiveProgramming as DP @@ -14,10 +14,9 @@ end DP.InfiniteLogical(prefs...) = DP.Logical(InfiniteOpt.Infinite(prefs...)) -# Make necessary extensions for Hull method function is_parameter(vref::InfiniteOpt.GeneralVariableRef) dref = InfiniteOpt.dispatch_variable_ref(vref) - if typeof(dref) <: Union{InfiniteOpt.DependentParameterRef, InfiniteOpt.IndependentParameterRef, InfiniteOpt.ParameterFunctionRef} + if typeof(dref) <: Union{InfiniteOpt.DependentParameterRef, InfiniteOpt.IndependentParameterRef, InfiniteOpt.ParameterFunctionRef, InfiniteOpt.FiniteParameterRef} return true else return false @@ -28,35 +27,171 @@ function DP.requires_disaggregation(vref::InfiniteOpt.GeneralVariableRef) return !is_parameter(vref) end -# Add to InfiniteDisjunctiveProgramming.jl -# Override for affine expressions to handle parameters + + +function DP._all_variables(model::InfiniteOpt.InfiniteModel) + vars = collect(JuMP.all_variables(model)) + derivs = collect(InfiniteOpt.all_derivatives(model)) + return vcat(vars, derivs) +end + +function DP._get_constant(expr::JuMP.GenericAffExpr{T, InfiniteOpt.GeneralVariableRef}) where {T} + constant = JuMP.constant(expr) + param_expr = zero(typeof(expr)) + for (var, coeff) in expr.terms + if is_parameter(var) + JuMP.add_to_expression!(param_expr, coeff, var) + end + end + return constant + param_expr +end + function DP._disaggregate_expression( model::M, aff::JuMP.GenericAffExpr, bvref::Union{JuMP.AbstractVariableRef, JuMP.GenericAffExpr}, method::DP._Hull ) where {M <: InfiniteOpt.InfiniteModel} - # Initialize as QuadExpr instead of AffExpr to handle parameter*binary terms - new_expr = JuMP.@expression(model, aff.constant*bvref + 0*bvref*bvref) # Trick to make it QuadExpr + # Build running sum of terms + terms = Any[aff.constant * bvref] + for (vref, coeff) in aff.terms if JuMP.is_binary(vref) - JuMP.add_to_expression!(new_expr, coeff*vref) + push!(terms, coeff * vref) elseif vref isa InfiniteOpt.GeneralVariableRef && is_parameter(vref) - # Parameters need perspective function applied (creates quadratic term) - JuMP.add_to_expression!(new_expr, coeff*vref*bvref) + push!(terms, coeff * vref * bvref) # multiply parameter by binary indicator elseif !haskey(method.disjunct_variables, (vref, bvref)) - # Non-disaggregated variables - JuMP.add_to_expression!(new_expr, coeff*vref) + push!(terms, coeff * vref) else - # Disaggregated variables dvref = method.disjunct_variables[vref, bvref] - JuMP.add_to_expression!(new_expr, coeff*dvref) + push!(terms, coeff * dvref) end end - return new_expr + return JuMP.@expression(model, sum(terms)) end +# function _copy_parameters(model::InfiniteOpt.InfiniteModel, new_model::InfiniteOpt.InfiniteModel) +# p_map = Dict{InfiniteOpt.GeneralVariableRef, InfiniteOpt.GeneralVariableRef}() +# for param in InfiniteOpt.all_parameters(model) +# new_param = _copy_parameter(new_model, param) +# p_map[param] = new_param +# end +# return p_map +# end + +# # Copy individual parameter based on its dispatch type +# #TODO: Dispatch and over all parameter types +# function _copy_parameter(new_model::InfiniteOpt.InfiniteModel, param::InfiniteOpt.GeneralVariableRef) +# dref = InfiniteOpt.dispatch_variable_ref(param) +# if dref isa InfiniteOpt.IndependentParameterRef +# domain = InfiniteOpt.infinite_domain(param) +# p = InfiniteOpt.build_parameter(error, domain) +# return InfiniteOpt.add_parameter(new_model, p, JuMP.name(param)) +# elseif dref isa InfiniteOpt.FiniteParameterRef +# val = InfiniteOpt.parameter_value(param) +# p = InfiniteOpt.build_parameter(error, val) +# return InfiniteOpt.add_parameter(new_model, p, JuMP.name(param)) +# else +# error("Dependent parameter copying not yet implemented for MBM mini-model") +# end +# end + +# # Remap parameter refs (handles tuples, arrays, and single refs) +# function _remap_parameter_refs(prefs::Tuple, param_map) +# return Tuple(_remap_parameter_refs(p, param_map) for p in prefs) +# end + +# function _remap_parameter_refs(pref::InfiniteOpt.GeneralVariableRef, param_map) +# return param_map[pref] +# end + +# function _remap_parameter_refs(prefs::AbstractArray, param_map) +# return [_remap_parameter_refs(p, param_map) for p in prefs] +# end +# Extend variable_copy to accept parameter map for infinite variables +# function DP.variable_copy( +# model::InfiniteOpt.InfiniteModel, +# var::InfiniteOpt.GeneralVariableRef, +# param_map::Dict{InfiniteOpt.GeneralVariableRef, InfiniteOpt.GeneralVariableRef} +# ) +# # If this is a parameter, return the mapped parameter +# if is_parameter(var) +# return param_map[var] +# end + +# info = DP.get_variable_info(var) +# name = JuMP.name(var) +# prefs = InfiniteOpt.parameter_refs(var) + +# if !isempty(prefs) +# new_prefs = _remap_parameter_refs(prefs, param_map) +# new_var = JuMP.@variable(model, variable_type = InfiniteOpt.Infinite(new_prefs...), base_name = name) +# if info.has_lb +# JuMP.set_lower_bound(new_var, info.lower_bound) +# end +# if info.has_ub +# JuMP.set_upper_bound(new_var, info.upper_bound) +# end +# if info.binary +# JuMP.set_binary(new_var) +# end +# if info.integer +# JuMP.set_integer(new_var) +# end +# return new_var +# else +# # Finite variable - use standard copy +# props = DP.VariableProperties(info, name, nothing, nothing) +# return DP.create_variable(model, props) +# end +# end + +# # Override _mini_model for InfiniteModel +# function DP._mini_model( +# model::InfiniteOpt.InfiniteModel, +# objective::JuMP.ScalarConstraint{T,S}, +# constraints::Vector{<:DP.DisjunctConstraintRef}, +# method::DP._MBM +# ) where {T, S <: Union{_MOI.LessThan, _MOI.GreaterThan}} +# var_type = JuMP.variable_ref_type(model) +# sub_model = DP._copy_model(model) + +# # First copy parameters +# param_map = _copy_parameters(model, sub_model) + +# # Combined map for variable/parameter replacement +# ref_map = Dict{var_type, var_type}() +# merge!(ref_map, param_map) + +# # Copy variables (skip parameters - they're already in the map) +# for var in JuMP.all_variables(model) +# if !is_parameter(var) +# ref_map[var] = variable_copy(sub_model, var, param_map) +# end +# end + +# # Add constraints with remapped variables +# for con in [JuMP.constraint_object(c) for c in constraints] +# expr = DP._replace_variables_in_constraint(con.func, ref_map) +# JuMP.@constraint(sub_model, expr * 1.0 in con.set) +# end + +# # Set objective +# DP._constraint_to_objective(sub_model, objective, ref_map) + +# JuMP.set_optimizer(sub_model, method.optimizer) +# JuMP.set_silent(sub_model) +# JuMP.optimize!(sub_model) +# if JuMP.termination_status(sub_model) != MOI.OPTIMAL || +# !JuMP.has_values(sub_model) || +# JuMP.primal_status(sub_model) != MOI.FEASIBLE_POINT +# M = method.default_M +# else +# M = JuMP.objective_value(sub_model) +# end +# return M +# end function DP.VariableProperties(vref::InfiniteOpt.GeneralVariableRef) info = DP.get_variable_info(vref) @@ -67,6 +202,40 @@ function DP.VariableProperties(vref::InfiniteOpt.GeneralVariableRef) return DP.VariableProperties(info, name, set, var_type) end +# Create blank variable with explicit parameter refs +function DP.create_blank_variable(model::InfiniteOpt.InfiniteModel, name::String, prefs::Tuple) + info = DP._blank_variable_info() + if isempty(prefs) + var = JuMP.build_variable(error, info) + else + var = JuMP.build_variable(error, info, InfiniteOpt.Infinite(prefs...)) + end + return JuMP.add_variable(model, var, name) +end + +# Create blank variable, inferring parameter refs from a single expression +function DP.create_blank_variable(model::InfiniteOpt.InfiniteModel, name::String, expr) + prefs = InfiniteOpt.parameter_refs(expr) + return DP.create_blank_variable(model, name, prefs) +end + +# Create blank variable, inferring parameter refs from a vector of expressions +function DP.create_blank_variable(model::InfiniteOpt.InfiniteModel, name::String, exprs::Vector) + # Collect all unique parameter refs from all expressions + all_prefs = Set{InfiniteOpt.GeneralVariableRef}() + for expr in exprs + for pref in InfiniteOpt.parameter_refs(expr) + push!(all_prefs, pref) + end + end + return DP.create_blank_variable(model, name, Tuple(all_prefs)) +end + +# Default: no parameter dependency +function DP.create_blank_variable(model::InfiniteOpt.InfiniteModel, name::String = "") + return DP.create_blank_variable(model, name, ()) +end + # Add necessary @constraint extensions function JuMP.add_constraint( model::InfiniteOpt.InfiniteModel, diff --git a/src/datatypes.jl b/src/datatypes.jl index 57fa251..ee38a16 100644 --- a/src/datatypes.jl +++ b/src/datatypes.jl @@ -488,7 +488,7 @@ struct PSplit{V <: JuMP.AbstractVariableRef} <: AbstractReformulationMethod function PSplit(n_parts::Int, model::JuMP.AbstractModel) n_parts > 0 || error("Number of partitions must be positive, got $n_parts") - variables = collect(JuMP.all_variables(model)) + variables = _all_variables(model) n_vars = length(variables) n_parts = min(n_parts, n_vars) diff --git a/src/mbm.jl b/src/mbm.jl index 58120a8..5a2f280 100644 --- a/src/mbm.jl +++ b/src/mbm.jl @@ -353,6 +353,7 @@ function _mini_model( var_type = JuMP.variable_ref_type(model) sub_model = _copy_model(model) new_vars = Dict{var_type, var_type}() + @show JuMP.all_variables(model) for var in JuMP.all_variables(model) new_vars[var] = variable_copy(sub_model, var) end diff --git a/src/psplit.jl b/src/psplit.jl index dea6789..b5c9d3f 100644 --- a/src/psplit.jl +++ b/src/psplit.jl @@ -2,11 +2,22 @@ # BUILD PARTITIONED EXPRESSION ################################################################################ +""" + _get_constant(expr) + +Returns the constant portion of an expression. Extend for model types where +additional terms should be treated as constants (e.g., parameters in InfiniteOpt). +""" +_get_constant(expr::JuMP.GenericAffExpr) = JuMP.constant(expr) +_get_constant(expr::JuMP.GenericQuadExpr) = JuMP.constant(expr) +_get_constant(expr::Number) = expr +_get_constant(expr::JuMP.AbstractVariableRef) = zero(Float64) + function _build_partitioned_expression( expr::T, partition_variables::Vector{<:JuMP.AbstractVariableRef} ) where {T <: JuMP.GenericAffExpr} - constant = JuMP.constant(expr) + constant = _get_constant(expr) new_affexpr = zero(T) for var in partition_variables JuMP.add_to_expression!(new_affexpr, JuMP.coefficient(expr, var), var) @@ -19,7 +30,7 @@ function _build_partitioned_expression( partition_variables::Vector{<:JuMP.AbstractVariableRef} ) where {T <: JuMP.GenericQuadExpr} new_quadexpr = zero(T) - constant = JuMP.constant(expr) + constant = _get_constant(expr) for var in partition_variables for (pair, coeff) in expr.terms if pair.a == var && pair.b == var @@ -205,6 +216,7 @@ function reformulate_disjunction( append!(ref_cons, partitioned_constraints) union!(aux_vars, vars) end + psplit = _PSplit(method, model) psplit.hull = _Hull(Hull(), union(disj_vrefs, aux_vars)) psplit.sum_constraints = sum_constraints @@ -220,6 +232,7 @@ function reformulate_disjunction( for vref in aux_vars _aggregate_variable(model, ref_cons, vref, psplit.hull) end + return ref_cons end @@ -279,19 +292,18 @@ function _build_partitioned_constraint( ) where {M <: JuMP.AbstractModel, T, S <: _MOI.LessThan} val_type = JuMP.value_type(M) p = length(method.partition) - v = [@variable(model, base_name = "v_$(hash(con))_$(i)") for i in 1:p] + v = Vector{JuMP.variable_ref_type(M)}(undef, p) _, constant = _build_partitioned_expression(con.func, method.partition[p]) part_con = Vector{JuMP.AbstractConstraint}(undef, p) for i in 1:p func, _ = _build_partitioned_expression(con.func, method.partition[i]) + v[i] = create_blank_variable(model, "v_$(hash(con))_$(i)", func) part_con[i] = JuMP.build_constraint(error, func - v[i], MOI.LessThan(zero(val_type)) ) _bound_auxiliary(model, v[i], func, method) end - sum_con = JuMP.build_constraint(error, sum(v[i] for i in 1:p) - ,MOI.LessThan(con.set.upper - constant) - ) + sum_con = JuMP.@build_constraint(sum(v[i] for i in 1:p) + constant <= con.set.upper) return part_con, [sum_con], v end @@ -304,18 +316,17 @@ function _build_partitioned_constraint( val_type = JuMP.value_type(M) p = length(method.partition) part_con = Vector{JuMP.AbstractConstraint}(undef, p) - v = [@variable(model, base_name = "v_$(hash(con))_$(i)") for i in 1:p] + v = Vector{JuMP.variable_ref_type(M)}(undef, p) _, constant = _build_partitioned_expression(con.func, method.partition[p]) for i in 1:p func, _ = _build_partitioned_expression(con.func, method.partition[i]) + v[i] = create_blank_variable(model, "v_$(hash(con))_$(i)", func) part_con[i] = JuMP.build_constraint(error, -func - v[i], MOI.LessThan(zero(val_type)) ) _bound_auxiliary(model, v[i], -func, method) end - sum_con = JuMP.build_constraint(error, sum(v[i] for i in 1:p) - , MOI.LessThan(-con.set.lower + constant) - ) + sum_con = JuMP.@build_constraint(sum(v[i] for i in 1:p) - constant <= -con.set.lower) return part_con, [sum_con], v end @@ -330,12 +341,11 @@ function _build_partitioned_constraint( part_con_gt = Vector{JuMP.AbstractConstraint}(undef, p) #let [_, 1] be the upper bound and [_, 2] be the lower bound _, constant = _build_partitioned_expression(con.func, method.partition[p]) - v = [@variable( - model, - base_name = "v_$(hash(con))_$(i)_$(j)" - ) for i in 1:p, j in 1:2] + v = Matrix{JuMP.variable_ref_type(M)}(undef, p, 2) for i in 1:p func, _= _build_partitioned_expression(con.func, method.partition[i]) + v[i,1] = create_blank_variable(model, "v_$(hash(con))_$(i)_1", func) + v[i,2] = create_blank_variable(model, "v_$(hash(con))_$(i)_2", func) part_con_lt[i] = JuMP.build_constraint(error, func - v[i,1], MOI.LessThan(zero(val_type)) ) @@ -346,12 +356,8 @@ function _build_partitioned_constraint( _bound_auxiliary(model, v[i,2], -func, method) end set_values = _set_values(con.set) - sum_con_lt = JuMP.build_constraint(error, sum(v[i,1] for i in 1:p), - MOI.LessThan((set_values[2] - constant)) - ) - sum_con_gt = JuMP.build_constraint(error, sum(v[i,2] for i in 1:p), - MOI.LessThan(-set_values[1] + constant) - ) + sum_con_lt = JuMP.@build_constraint(sum(v[i,1] for i in 1:p) + constant <= set_values[2]) + sum_con_gt = JuMP.@build_constraint(sum(v[i,2] for i in 1:p) - constant <= -set_values[1]) return vcat(part_con_lt, part_con_gt), [sum_con_lt, sum_con_gt], vec(v) end function _build_partitioned_constraint( @@ -361,10 +367,7 @@ function _build_partitioned_constraint( ) where {M <: JuMP.AbstractModel, T, S <: _MOI.Nonpositives, R} p = length(method.partition) d = con.set.dimension - v = [@variable( - model, - base_name = "v_$(hash(con))_$(i)_$(j)" - ) for i in 1:p, j in 1:d] + v = Matrix{JuMP.variable_ref_type(M)}(undef, p, d) part_con = Vector{JuMP.AbstractConstraint}(undef, p) constants = Vector{Number}(undef, d) for i in 1:p @@ -373,12 +376,13 @@ function _build_partitioned_constraint( ] func = JuMP.@expression(model, [j = 1:d], part_expr[j][1]) constants .= [part_expr[j][2] for j in 1:d] - part_con[i] = JuMP.build_constraint(error, - func - v[i,:], _MOI.Nonpositives(d) - ) for j in 1:d + v[i,j] = create_blank_variable(model, "v_$(hash(con))_$(i)_$(j)", func[j]) _bound_auxiliary(model, v[i,j], func[j], method) end + part_con[i] = JuMP.build_constraint(error, + func - v[i,:], _MOI.Nonpositives(d) + ) end new_func = JuMP.@expression(model,[j = 1:d], sum(v[i,j] for i in 1:p) + constants[j] @@ -394,10 +398,7 @@ function _build_partitioned_constraint( ) where {M <: JuMP.AbstractModel, T, S <: _MOI.Nonnegatives, R} p = length(method.partition) d = con.set.dimension - v = [@variable( - model, - base_name = "v_$(hash(con))_$(i)_$(j)" - ) for i in 1:p, j in 1:d] + v = Matrix{JuMP.variable_ref_type(M)}(undef, p, d) part_con = Vector{JuMP.AbstractConstraint}(undef, p) constants = Vector{Number}(undef, d) for i in 1:p @@ -407,10 +408,11 @@ function _build_partitioned_constraint( ] func = JuMP.@expression(model, [j = 1:d], -part_expr[j][1]) constants .= [-part_expr[j][2] for j in 1:d] - part_con[i] = JuMP.build_constraint(error, func - v[i,:], _MOI.Nonpositives(d)) for j in 1:d + v[i,j] = create_blank_variable(model, "v_$(hash(con))_$(i)_$(j)", func[j]) _bound_auxiliary(model, v[i,j], func[j], method) end + part_con[i] = JuMP.build_constraint(error, func - v[i,:], _MOI.Nonpositives(d)) end new_func = JuMP.@expression(model,[j = 1:d], sum(v[i,j] for i in 1:p) + constants[j] @@ -428,11 +430,7 @@ function _build_partitioned_constraint( d = con.set.dimension part_con_np = Vector{JuMP.AbstractConstraint}(undef, p) # nonpositive (≤ 0) part_con_nn = Vector{JuMP.AbstractConstraint}(undef, p) # nonnegative (≥ 0) - v = [@variable( - model, - base_name = "v_$(hash(con))_$(i)_$(j)_$(k)" - ) for i in 1:p, j in 1:d, k in 1:2 - ] + v = Array{JuMP.variable_ref_type(M)}(undef, p, d, 2) constants = Vector{Number}(undef, d) for i in 1:p part_expr = [ @@ -441,16 +439,18 @@ function _build_partitioned_constraint( ] func = JuMP.@expression(model, [j = 1:d], part_expr[j][1]) constants .= [part_expr[j][2] for j in 1:d] + for j in 1:d + v[i,j,1] = create_blank_variable(model, "v_$(hash(con))_$(i)_$(j)_1", func[j]) + v[i,j,2] = create_blank_variable(model, "v_$(hash(con))_$(i)_$(j)_2", func[j]) + _bound_auxiliary(model, v[i,j,1], func[j], method) + _bound_auxiliary(model, v[i,j,2], -func[j], method) + end part_con_np[i] = JuMP.build_constraint(error, func - v[i,:,1], _MOI.Nonpositives(d) ) part_con_nn[i] = JuMP.build_constraint(error, -func - v[i,:,2], _MOI.Nonpositives(d) ) - for j in 1:d - _bound_auxiliary(model, v[i,j,1], func[j], method) - _bound_auxiliary(model, v[i,j,2], -func[j], method) - end end new_func_np = JuMP.@expression(model,[j = 1:d], sum(v[i,j,1] for i in 1:p) + constants[j] diff --git a/src/utilities.jl b/src/utilities.jl index 6c9b8e5..9bd7fa0 100644 --- a/src/utilities.jl +++ b/src/utilities.jl @@ -8,6 +8,17 @@ function _copy_model( return M() end +################################################################################ +# ALL VARIABLES +################################################################################ +""" + _all_variables(model::JuMP.AbstractModel) + +Returns all variable references in the model. +Extend this for model types that have additional ref types (e.g., parameters). +""" +_all_variables(model::JuMP.AbstractModel) = collect(JuMP.all_variables(model)) + """ JuMP.copy_extension_data( data::GDPData, diff --git a/src/variables.jl b/src/variables.jl index e91abb6..43b7db3 100644 --- a/src/variables.jl +++ b/src/variables.jl @@ -546,3 +546,18 @@ function get_variable_info(vref::JuMP.AbstractVariableRef; ) return info end + +function _blank_variable_info() + return JuMP.VariableInfo(false, NaN, false, NaN, false, NaN, false, NaN, false, false) +end + +function create_blank_variable(model::JuMP.AbstractModel, name::String = "") + info = _blank_variable_info() + var = JuMP.build_variable(error, info) + return JuMP.add_variable(model, var, name) +end + +# Fallback that accepts an expression for parameter inference (used by extensions) +function create_blank_variable(model::JuMP.AbstractModel, name::String, expr) + return create_blank_variable(model, name) +end \ No newline at end of file From ad6423f8a59382493e914ba08b6cbc7eff987854 Mon Sep 17 00:00:00 2001 From: d227nguyen Date: Mon, 15 Dec 2025 16:11:59 -0500 Subject: [PATCH 10/30] Tests added. --- ext/InfiniteDisjunctiveProgramming.jl | 269 ++++-------- src/datatypes.jl | 2 +- src/hull.jl | 14 +- src/mbm.jl | 1 - src/psplit.jl | 15 +- src/utilities.jl | 21 +- test/constraints/hull.jl | 10 +- .../InfiniteDisjunctiveProgramming.jl | 385 +++++++++++++++++- test/runtests.jl | 38 +- test/utilities.jl | 72 ++++ test/variables/creation.jl | 95 ++++- 11 files changed, 671 insertions(+), 251 deletions(-) diff --git a/ext/InfiniteDisjunctiveProgramming.jl b/ext/InfiniteDisjunctiveProgramming.jl index e8f9bbf..6527ae1 100644 --- a/ext/InfiniteDisjunctiveProgramming.jl +++ b/ext/InfiniteDisjunctiveProgramming.jl @@ -1,9 +1,12 @@ module InfiniteDisjunctiveProgramming + import JuMP.MOI as _MOI import InfiniteOpt, JuMP import DisjunctiveProgramming as DP -# Extend the public API methods +################################################################################ +# MODEL +################################################################################ function DP.InfiniteGDPModel(args...; kwargs...) return DP.GDPModel{ InfiniteOpt.InfiniteModel, @@ -12,11 +15,25 @@ function DP.InfiniteGDPModel(args...; kwargs...) }(args...; kwargs...) end +function DP.all_variables(model::InfiniteOpt.InfiniteModel) + vars = collect(JuMP.all_variables(model)) + derivs = collect(InfiniteOpt.all_derivatives(model)) + return vcat(vars, derivs) +end + +################################################################################ +# VARIABLES +################################################################################ DP.InfiniteLogical(prefs...) = DP.Logical(InfiniteOpt.Infinite(prefs...)) function is_parameter(vref::InfiniteOpt.GeneralVariableRef) dref = InfiniteOpt.dispatch_variable_ref(vref) - if typeof(dref) <: Union{InfiniteOpt.DependentParameterRef, InfiniteOpt.IndependentParameterRef, InfiniteOpt.ParameterFunctionRef, InfiniteOpt.FiniteParameterRef} + if typeof(dref) <: Union{ + InfiniteOpt.DependentParameterRef, + InfiniteOpt.IndependentParameterRef, + InfiniteOpt.ParameterFunctionRef, + InfiniteOpt.FiniteParameterRef + } return true else return false @@ -27,172 +44,6 @@ function DP.requires_disaggregation(vref::InfiniteOpt.GeneralVariableRef) return !is_parameter(vref) end - - -function DP._all_variables(model::InfiniteOpt.InfiniteModel) - vars = collect(JuMP.all_variables(model)) - derivs = collect(InfiniteOpt.all_derivatives(model)) - return vcat(vars, derivs) -end - -function DP._get_constant(expr::JuMP.GenericAffExpr{T, InfiniteOpt.GeneralVariableRef}) where {T} - constant = JuMP.constant(expr) - param_expr = zero(typeof(expr)) - for (var, coeff) in expr.terms - if is_parameter(var) - JuMP.add_to_expression!(param_expr, coeff, var) - end - end - return constant + param_expr -end - -function DP._disaggregate_expression( - model::M, - aff::JuMP.GenericAffExpr, - bvref::Union{JuMP.AbstractVariableRef, JuMP.GenericAffExpr}, - method::DP._Hull - ) where {M <: InfiniteOpt.InfiniteModel} - # Build running sum of terms - terms = Any[aff.constant * bvref] - - for (vref, coeff) in aff.terms - if JuMP.is_binary(vref) - push!(terms, coeff * vref) - elseif vref isa InfiniteOpt.GeneralVariableRef && is_parameter(vref) - push!(terms, coeff * vref * bvref) # multiply parameter by binary indicator - elseif !haskey(method.disjunct_variables, (vref, bvref)) - push!(terms, coeff * vref) - else - dvref = method.disjunct_variables[vref, bvref] - push!(terms, coeff * dvref) - end - end - return JuMP.@expression(model, sum(terms)) -end - -# function _copy_parameters(model::InfiniteOpt.InfiniteModel, new_model::InfiniteOpt.InfiniteModel) -# p_map = Dict{InfiniteOpt.GeneralVariableRef, InfiniteOpt.GeneralVariableRef}() -# for param in InfiniteOpt.all_parameters(model) -# new_param = _copy_parameter(new_model, param) -# p_map[param] = new_param -# end -# return p_map -# end - -# # Copy individual parameter based on its dispatch type -# #TODO: Dispatch and over all parameter types -# function _copy_parameter(new_model::InfiniteOpt.InfiniteModel, param::InfiniteOpt.GeneralVariableRef) -# dref = InfiniteOpt.dispatch_variable_ref(param) -# if dref isa InfiniteOpt.IndependentParameterRef -# domain = InfiniteOpt.infinite_domain(param) -# p = InfiniteOpt.build_parameter(error, domain) -# return InfiniteOpt.add_parameter(new_model, p, JuMP.name(param)) -# elseif dref isa InfiniteOpt.FiniteParameterRef -# val = InfiniteOpt.parameter_value(param) -# p = InfiniteOpt.build_parameter(error, val) -# return InfiniteOpt.add_parameter(new_model, p, JuMP.name(param)) -# else -# error("Dependent parameter copying not yet implemented for MBM mini-model") -# end -# end - -# # Remap parameter refs (handles tuples, arrays, and single refs) -# function _remap_parameter_refs(prefs::Tuple, param_map) -# return Tuple(_remap_parameter_refs(p, param_map) for p in prefs) -# end - -# function _remap_parameter_refs(pref::InfiniteOpt.GeneralVariableRef, param_map) -# return param_map[pref] -# end - -# function _remap_parameter_refs(prefs::AbstractArray, param_map) -# return [_remap_parameter_refs(p, param_map) for p in prefs] -# end - -# Extend variable_copy to accept parameter map for infinite variables -# function DP.variable_copy( -# model::InfiniteOpt.InfiniteModel, -# var::InfiniteOpt.GeneralVariableRef, -# param_map::Dict{InfiniteOpt.GeneralVariableRef, InfiniteOpt.GeneralVariableRef} -# ) -# # If this is a parameter, return the mapped parameter -# if is_parameter(var) -# return param_map[var] -# end - -# info = DP.get_variable_info(var) -# name = JuMP.name(var) -# prefs = InfiniteOpt.parameter_refs(var) - -# if !isempty(prefs) -# new_prefs = _remap_parameter_refs(prefs, param_map) -# new_var = JuMP.@variable(model, variable_type = InfiniteOpt.Infinite(new_prefs...), base_name = name) -# if info.has_lb -# JuMP.set_lower_bound(new_var, info.lower_bound) -# end -# if info.has_ub -# JuMP.set_upper_bound(new_var, info.upper_bound) -# end -# if info.binary -# JuMP.set_binary(new_var) -# end -# if info.integer -# JuMP.set_integer(new_var) -# end -# return new_var -# else -# # Finite variable - use standard copy -# props = DP.VariableProperties(info, name, nothing, nothing) -# return DP.create_variable(model, props) -# end -# end - -# # Override _mini_model for InfiniteModel -# function DP._mini_model( -# model::InfiniteOpt.InfiniteModel, -# objective::JuMP.ScalarConstraint{T,S}, -# constraints::Vector{<:DP.DisjunctConstraintRef}, -# method::DP._MBM -# ) where {T, S <: Union{_MOI.LessThan, _MOI.GreaterThan}} -# var_type = JuMP.variable_ref_type(model) -# sub_model = DP._copy_model(model) - -# # First copy parameters -# param_map = _copy_parameters(model, sub_model) - -# # Combined map for variable/parameter replacement -# ref_map = Dict{var_type, var_type}() -# merge!(ref_map, param_map) - -# # Copy variables (skip parameters - they're already in the map) -# for var in JuMP.all_variables(model) -# if !is_parameter(var) -# ref_map[var] = variable_copy(sub_model, var, param_map) -# end -# end - -# # Add constraints with remapped variables -# for con in [JuMP.constraint_object(c) for c in constraints] -# expr = DP._replace_variables_in_constraint(con.func, ref_map) -# JuMP.@constraint(sub_model, expr * 1.0 in con.set) -# end - -# # Set objective -# DP._constraint_to_objective(sub_model, objective, ref_map) - -# JuMP.set_optimizer(sub_model, method.optimizer) -# JuMP.set_silent(sub_model) -# JuMP.optimize!(sub_model) -# if JuMP.termination_status(sub_model) != MOI.OPTIMAL || -# !JuMP.has_values(sub_model) || -# JuMP.primal_status(sub_model) != MOI.FEASIBLE_POINT -# M = method.default_M -# else -# M = JuMP.objective_value(sub_model) -# end -# return M -# end - function DP.VariableProperties(vref::InfiniteOpt.GeneralVariableRef) info = DP.get_variable_info(vref) name = JuMP.name(vref) @@ -202,7 +53,6 @@ function DP.VariableProperties(vref::InfiniteOpt.GeneralVariableRef) return DP.VariableProperties(info, name, set, var_type) end -# Create blank variable with explicit parameter refs function DP.create_blank_variable(model::InfiniteOpt.InfiniteModel, name::String, prefs::Tuple) info = DP._blank_variable_info() if isempty(prefs) @@ -213,15 +63,12 @@ function DP.create_blank_variable(model::InfiniteOpt.InfiniteModel, name::String return JuMP.add_variable(model, var, name) end -# Create blank variable, inferring parameter refs from a single expression function DP.create_blank_variable(model::InfiniteOpt.InfiniteModel, name::String, expr) prefs = InfiniteOpt.parameter_refs(expr) return DP.create_blank_variable(model, name, prefs) end -# Create blank variable, inferring parameter refs from a vector of expressions function DP.create_blank_variable(model::InfiniteOpt.InfiniteModel, name::String, exprs::Vector) - # Collect all unique parameter refs from all expressions all_prefs = Set{InfiniteOpt.GeneralVariableRef}() for expr in exprs for pref in InfiniteOpt.parameter_refs(expr) @@ -231,51 +78,105 @@ function DP.create_blank_variable(model::InfiniteOpt.InfiniteModel, name::String return DP.create_blank_variable(model, name, Tuple(all_prefs)) end -# Default: no parameter dependency function DP.create_blank_variable(model::InfiniteOpt.InfiniteModel, name::String = "") return DP.create_blank_variable(model, name, ()) end -# Add necessary @constraint extensions +function JuMP.value(vref::DP.LogicalVariableRef{InfiniteOpt.InfiniteModel}) + return JuMP.value(DP.binary_variable(vref)) .>= 0.5 +end + +################################################################################ +# CONSTRAINTS +################################################################################ function JuMP.add_constraint( model::InfiniteOpt.InfiniteModel, c::JuMP.VectorConstraint{F, S}, name::String = "" - ) where {F, S <: DP.AbstractCardinalitySet} +) where {F, S <: DP.AbstractCardinalitySet} return DP._add_cardinality_constraint(model, c, name) end + function JuMP.add_constraint( model::M, c::JuMP.ScalarConstraint{DP._LogicalExpr{M}, S}, name::String = "" - ) where {S, M <: InfiniteOpt.InfiniteModel} - return DP._add_logical_constraint(model, c, name) +) where {S, M <: InfiniteOpt.InfiniteModel} + return DP._add_logical_constraint(model, c, name) end + function JuMP.add_constraint( model::M, c::JuMP.ScalarConstraint{DP.LogicalVariableRef{M}, S}, name::String = "" - ) where {M <: InfiniteOpt.InfiniteModel, S} +) where {M <: InfiniteOpt.InfiniteModel, S} error("Cannot define constraint on single logical variable, use `fix` instead.") end + function JuMP.add_constraint( model::M, c::JuMP.ScalarConstraint{JuMP.GenericAffExpr{C, DP.LogicalVariableRef{M}}, S}, name::String = "" - ) where {M <: InfiniteOpt.InfiniteModel, S, C} +) where {M <: InfiniteOpt.InfiniteModel, S, C} error("Cannot add, subtract, or multiply with logical variables.") end + function JuMP.add_constraint( model::M, c::JuMP.ScalarConstraint{JuMP.GenericQuadExpr{C, DP.LogicalVariableRef{M}}, S}, name::String = "" - ) where {M <: InfiniteOpt.InfiniteModel, S, C} +) where {M <: InfiniteOpt.InfiniteModel, S, C} error("Cannot add, subtract, or multiply with logical variables.") end -# Extend value to work properly -function JuMP.value(vref::DP.LogicalVariableRef{InfiniteOpt.InfiniteModel}) - return JuMP.value(DP.binary_variable(vref)) .>= 0.5 +################################################################################ +# METHODS +################################################################################ +function DP.get_constant(expr::JuMP.GenericAffExpr{T, InfiniteOpt.GeneralVariableRef}) where {T} + constant = JuMP.constant(expr) + param_expr = zero(typeof(expr)) + for (var, coeff) in expr.terms + if is_parameter(var) + JuMP.add_to_expression!(param_expr, coeff, var) + end + end + return constant + param_expr +end + +function DP.disaggregate_expression( + model::M, + aff::JuMP.GenericAffExpr, + bvref::Union{JuMP.AbstractVariableRef, JuMP.GenericAffExpr}, + method::DP._Hull +) where {M <: InfiniteOpt.InfiniteModel} + terms = Any[aff.constant * bvref] + for (vref, coeff) in aff.terms + if JuMP.is_binary(vref) + push!(terms, coeff * vref) + elseif vref isa InfiniteOpt.GeneralVariableRef && is_parameter(vref) + push!(terms, coeff * vref * bvref) + elseif !haskey(method.disjunct_variables, (vref, bvref)) + push!(terms, coeff * vref) + else + dvref = method.disjunct_variables[vref, bvref] + push!(terms, coeff * dvref) + end + end + return JuMP.@expression(model, sum(terms)) +end + +################################################################################ +# ERROR MESSAGES +################################################################################ +function DP.reformulate_model(::InfiniteOpt.InfiniteModel, ::DP.MBM) + error("The `MBM` reformulation method is not supported for `InfiniteModel`. " * + "Please use `BigM`, `Hull`, `Indicator`, or `PSplit` instead.") +end + +function DP.reformulate_model(::InfiniteOpt.InfiniteModel, ::DP.cutting_planes) + error("The `cutting_planes` reformulation method is not supported for `InfiniteModel`. " * + "Please use `BigM`, `Hull`, `Indicator`, or `PSplit` instead.") +end + end -end \ No newline at end of file diff --git a/src/datatypes.jl b/src/datatypes.jl index ee38a16..00550c0 100644 --- a/src/datatypes.jl +++ b/src/datatypes.jl @@ -488,7 +488,7 @@ struct PSplit{V <: JuMP.AbstractVariableRef} <: AbstractReformulationMethod function PSplit(n_parts::Int, model::JuMP.AbstractModel) n_parts > 0 || error("Number of partitions must be positive, got $n_parts") - variables = _all_variables(model) + variables = all_variables(model) n_vars = length(variables) n_parts = min(n_parts, n_vars) diff --git a/src/hull.jl b/src/hull.jl index 7a147dc..8f8d9f8 100644 --- a/src/hull.jl +++ b/src/hull.jl @@ -76,7 +76,7 @@ end # CONSTRAINT DISAGGREGATION ################################################################################ # variable -function _disaggregate_expression( +function disaggregate_expression( model::JuMP.AbstractModel, vref::JuMP.AbstractVariableRef, bvref::Union{JuMP.AbstractVariableRef, JuMP.GenericAffExpr}, @@ -89,7 +89,7 @@ function _disaggregate_expression( end end # affine expression -function _disaggregate_expression( +function disaggregate_expression( model::JuMP.AbstractModel, aff::JuMP.GenericAffExpr, bvref::Union{JuMP.AbstractVariableRef, JuMP.GenericAffExpr}, @@ -110,14 +110,14 @@ end # quadratic expression # TODO review what happens when there are bilinear terms with binary variables involved since these are not being disaggregated # (e.g., complementarity constraints; though likely irrelevant)... -function _disaggregate_expression( +function disaggregate_expression( model::JuMP.AbstractModel, quad::JuMP.GenericQuadExpr, bvref::Union{JuMP.AbstractVariableRef, JuMP.GenericAffExpr}, method::_Hull ) #get affine part - new_expr = _disaggregate_expression(model, quad.aff, bvref, method) + new_expr = disaggregate_expression(model, quad.aff, bvref, method) #get quadratic part ϵ = method.value for (pair, coeff) in quad.terms @@ -244,7 +244,7 @@ function reformulate_disjunct_constraint( bvref::Union{JuMP.AbstractVariableRef, JuMP.GenericAffExpr}, method::_Hull ) where {T <: JuMP.AbstractJuMPScalar, S <: Union{_MOI.LessThan, _MOI.GreaterThan, _MOI.EqualTo}} - new_func = _disaggregate_expression(model, con.func, bvref, method) + new_func = disaggregate_expression(model, con.func, bvref, method) set_value = _set_value(con.set) new_func -= set_value*bvref reform_con = JuMP.build_constraint(error, new_func, S(0)) @@ -257,7 +257,7 @@ function reformulate_disjunct_constraint( method::_Hull ) where {T <: JuMP.AbstractJuMPScalar, S <: Union{_MOI.Nonpositives, _MOI.Nonnegatives, _MOI.Zeros}, R} new_func = JuMP.@expression(model, [i=1:con.set.dimension], - _disaggregate_expression(model, con.func[i], bvref, method) + disaggregate_expression(model, con.func[i], bvref, method) ) reform_con = JuMP.build_constraint(error, new_func, con.set) return [reform_con] @@ -305,7 +305,7 @@ function reformulate_disjunct_constraint( bvref::Union{JuMP.AbstractVariableRef, JuMP.GenericAffExpr}, method::_Hull ) where {T <: JuMP.AbstractJuMPScalar, S <: _MOI.Interval} - new_func = _disaggregate_expression(model, con.func, bvref, method) + new_func = disaggregate_expression(model, con.func, bvref, method) new_func_gt = JuMP.@expression(model, new_func - con.set.lower*bvref) new_func_lt = JuMP.@expression(model, new_func - con.set.upper*bvref) reform_con_gt = JuMP.build_constraint(error, new_func_gt, _MOI.GreaterThan(0)) diff --git a/src/mbm.jl b/src/mbm.jl index 5a2f280..58120a8 100644 --- a/src/mbm.jl +++ b/src/mbm.jl @@ -353,7 +353,6 @@ function _mini_model( var_type = JuMP.variable_ref_type(model) sub_model = _copy_model(model) new_vars = Dict{var_type, var_type}() - @show JuMP.all_variables(model) for var in JuMP.all_variables(model) new_vars[var] = variable_copy(sub_model, var) end diff --git a/src/psplit.jl b/src/psplit.jl index b5c9d3f..84c58be 100644 --- a/src/psplit.jl +++ b/src/psplit.jl @@ -2,22 +2,11 @@ # BUILD PARTITIONED EXPRESSION ################################################################################ -""" - _get_constant(expr) - -Returns the constant portion of an expression. Extend for model types where -additional terms should be treated as constants (e.g., parameters in InfiniteOpt). -""" -_get_constant(expr::JuMP.GenericAffExpr) = JuMP.constant(expr) -_get_constant(expr::JuMP.GenericQuadExpr) = JuMP.constant(expr) -_get_constant(expr::Number) = expr -_get_constant(expr::JuMP.AbstractVariableRef) = zero(Float64) - function _build_partitioned_expression( expr::T, partition_variables::Vector{<:JuMP.AbstractVariableRef} ) where {T <: JuMP.GenericAffExpr} - constant = _get_constant(expr) + constant = get_constant(expr) new_affexpr = zero(T) for var in partition_variables JuMP.add_to_expression!(new_affexpr, JuMP.coefficient(expr, var), var) @@ -30,7 +19,7 @@ function _build_partitioned_expression( partition_variables::Vector{<:JuMP.AbstractVariableRef} ) where {T <: JuMP.GenericQuadExpr} new_quadexpr = zero(T) - constant = _get_constant(expr) + constant = get_constant(expr) for var in partition_variables for (pair, coeff) in expr.terms if pair.a == var && pair.b == var diff --git a/src/utilities.jl b/src/utilities.jl index 9bd7fa0..cc37991 100644 --- a/src/utilities.jl +++ b/src/utilities.jl @@ -12,13 +12,30 @@ end # ALL VARIABLES ################################################################################ """ - _all_variables(model::JuMP.AbstractModel) + all_variables(model::JuMP.AbstractModel) Returns all variable references in the model. Extend this for model types that have additional ref types (e.g., parameters). """ -_all_variables(model::JuMP.AbstractModel) = collect(JuMP.all_variables(model)) +all_variables(model::JuMP.AbstractModel) = collect(JuMP.all_variables(model)) +################################################################################ +# GET CONSTANT +################################################################################ +""" + get_constant(expr) + +Returns the constant portion of an expression. Extendable for model types where +additional terms should be treated as constants. +""" +get_constant(expr::JuMP.GenericAffExpr) = JuMP.constant(expr) +get_constant(expr::JuMP.GenericQuadExpr) = JuMP.constant(expr) +get_constant(expr::Number) = expr +get_constant(expr::JuMP.AbstractVariableRef) = zero(Float64) + +################################################################################ +# MODEL COPYING +################################################################################ """ JuMP.copy_extension_data( data::GDPData, diff --git a/test/constraints/hull.jl b/test/constraints/hull.jl index 37b1175..ddc5d85 100644 --- a/test/constraints/hull.jl +++ b/test/constraints/hull.jl @@ -81,7 +81,7 @@ function test_disaggregate_expression_var() method = DP._Hull(Hull(1e-3), vrefs) @test DP._disaggregate_variables(model, z, vrefs, method) isa Nothing - refexpr = DP._disaggregate_expression(model, x, bvrefs[z], method) + refexpr = DP.disaggregate_expression(model, x, bvrefs[z], method) x_z = variable_by_name(model, "x_z") @test refexpr == x_z end @@ -97,7 +97,7 @@ function test_disaggregate_expression_var_binary() @test DP._disaggregate_variables(model, z, vrefs, method) isa Nothing @test isnothing(variable_by_name(model, "x_z")) - refexpr = DP._disaggregate_expression(model, x, bvrefs[z], method) + refexpr = DP.disaggregate_expression(model, x, bvrefs[z], method) @test refexpr == x end @@ -112,7 +112,7 @@ function test_disaggregate_expression_affine() method = DP._Hull(Hull(1e-3), vrefs) @test DP._disaggregate_variables(model, z, vrefs, method) isa Nothing - refexpr = DP._disaggregate_expression(model, 2x + 1, bvrefs[z], method) + refexpr = DP.disaggregate_expression(model, 2x + 1, bvrefs[z], method) x_z = variable_by_name(model, "x_z") zbin = variable_by_name(model, "z") @test refexpr == 2x_z + 1zbin @@ -130,7 +130,7 @@ function test_disaggregate_expression_affine_mip() method = DP._Hull(Hull(1e-3), vrefs) @test DP._disaggregate_variables(model, z, vrefs, method) isa Nothing - refexpr = DP._disaggregate_expression(model, 2x + y + 1, bvrefs[z], method) + refexpr = DP.disaggregate_expression(model, 2x + y + 1, bvrefs[z], method) x_z = variable_by_name(model, "x_z") zbin = variable_by_name(model, "z") @test refexpr == 2x_z + y + 1zbin @@ -147,7 +147,7 @@ function test_disaggregate_expression_quadratic() method = DP._Hull(Hull(1e-3), vrefs) @test DP._disaggregate_variables(model, z, vrefs, method) isa Nothing - refexpr = DP._disaggregate_expression(model, 2x^2 + 1, bvrefs[z], method) + refexpr = DP.disaggregate_expression(model, 2x^2 + 1, bvrefs[z], method) x_z = variable_by_name(model, "x_z") zbin = variable_by_name(model, "z") ϵ = method.value diff --git a/test/extensions/InfiniteDisjunctiveProgramming.jl b/test/extensions/InfiniteDisjunctiveProgramming.jl index 0ebfb96..79163d7 100644 --- a/test/extensions/InfiniteDisjunctiveProgramming.jl +++ b/test/extensions/InfiniteDisjunctiveProgramming.jl @@ -1,8 +1,13 @@ -using InfiniteOpt, HiGHS +using InfiniteOpt, HiGHS, Ipopt, Juniper +import DisjunctiveProgramming as DP + +# Helper to access internal function +const IDP = Base.get_extension(DP, :InfiniteDisjunctiveProgramming) function test_infiniteopt_extension() # Initialize the model model = InfiniteGDPModel(HiGHS.Optimizer) + set_attribute(model, MOI.Silent(), true) # Create the infinite variables I = 1:4 @@ -11,13 +16,20 @@ function test_infiniteopt_extension() # Add the disjunctions and their indicator variables @variable(model, G[I, 1:2], InfiniteLogical(t)) - @test all(isa.(@constraint(model, [i ∈ I, j ∈ 1:2], 0 <= g[i], Disjunct(G[i, 1])), DisjunctConstraintRef{InfiniteModel})) - @test all(isa.(@constraint(model, [i ∈ I, j ∈ 1:2], g[i] <= 0, Disjunct(G[i, 2])), DisjunctConstraintRef{InfiniteModel})) - @test all(isa.(@disjunction(model, [i ∈ I], G[i, :]), DisjunctionRef{InfiniteModel})) + @test all(isa.(@constraint(model, [i ∈ I, j ∈ 1:2], 0 <= g[i], + Disjunct(G[i, 1])), DisjunctConstraintRef{InfiniteModel}) + ) + @test all(isa.(@constraint(model, [i ∈ I, j ∈ 1:2], g[i] <= 0, + Disjunct(G[i, 2])), DisjunctConstraintRef{InfiniteModel}) + ) + @test all(isa.(@disjunction(model, [i ∈ I], G[i, :]), + DisjunctionRef{InfiniteModel}) + ) # Add the logical propositions @variable(model, W, InfiniteLogical(t)) - @test @constraint(model, G[1, 1] ∨ G[2, 1] ∧ G[3, 1] == W := true) isa LogicalConstraintRef{InfiniteModel} + @test @constraint(model, G[1, 1] ∨ G[2, 1] ∧ G[3, 1] == W := true) isa + LogicalConstraintRef{InfiniteModel} @constraint(model, 𝔼(binary_variable(W), t) >= 0.95) # Reformulate and solve @@ -27,6 +39,367 @@ function test_infiniteopt_extension() @test all(value(W)) end +function test_infinite_gdp_model_creation() + model = InfiniteGDPModel() + @test model isa InfiniteModel + @test is_gdp_model(model) + +end + +function test_infinite_logical() + model = InfiniteGDPModel() + @infinite_parameter(model, t ∈ [0, 1]) + + @variable(model, y, InfiniteLogical(t)) + @test y isa DP.LogicalVariableRef{InfiniteModel} + @test binary_variable(y) isa InfiniteOpt.GeneralVariableRef +end + +function test_is_parameter() + model = InfiniteGDPModel() + @infinite_parameter(model, t ∈ [0, 1]) + @infinite_parameter(model, s[1:2] ∈ [0, 1], independent = true) + @finite_parameter(model, p == 1.0) + @variable(model, x, Infinite(t)) + @variable(model, y) + + # Test DependentParameterRef + @test IDP.is_parameter(t) == true + + # Test IndependentParameterRef + @test IDP.is_parameter(s[1]) == true + + # Test FiniteParameterRef + @test IDP.is_parameter(p) == true + + # Test non-parameter variables (else branch) + @test IDP.is_parameter(x) == false + @test IDP.is_parameter(y) == false +end + +function test_requires_disaggregation() + model = InfiniteGDPModel() + @infinite_parameter(model, t ∈ [0, 1]) + @finite_parameter(model, p == 1.0) + @variable(model, x, Infinite(t)) + @variable(model, y) + + # Parameters should NOT require disaggregation + @test DP.requires_disaggregation(t) == false + @test DP.requires_disaggregation(p) == false + + # Variables SHOULD require disaggregation + @test DP.requires_disaggregation(x) == true + @test DP.requires_disaggregation(y) == true +end + +function test_all_variables_infiniteopt() + model = InfiniteGDPModel() + @infinite_parameter(model, t ∈ [0, 1]) + @variable(model, x, Infinite(t)) + @variable(model, y) + @variable(model, dx, Infinite(t)) + @deriv(dx, t) + + all_vars = DP.all_variables(model) + @test x in all_vars + @test y in all_vars + @test dx in all_vars + + # Verify derivatives are included + derivs = collect(InfiniteOpt.all_derivatives(model)) + for d in derivs + @test d in all_vars + end +end + +function test_get_constant() + model = InfiniteGDPModel() + @infinite_parameter(model, t ∈ [0, 1]) + @finite_parameter(model, p == 2.0) + @variable(model, x, Infinite(t)) + + # Test expression with parameter terms (is_parameter branch) + expr2 = @expression(model, 3.0 + 2*t + x) + constant2 = DP.get_constant(expr2) + @test JuMP.constant(constant2) == 3.0 + @test haskey(constant2.terms, t) + @test constant2.terms[t] == 2.0 + @test !haskey(constant2.terms, x) + + # Test expression with finite parameter + expr3 = @expression(model, 1.0 + 3*p) + constant3 = DP.get_constant(expr3) + @test JuMP.constant(constant3) == 1.0 + @test haskey(constant3.terms, p) + @test constant3.terms[p] == 3.0 +end + +function test_disaggregate_expression_infiniteopt() + model = InfiniteGDPModel() + @infinite_parameter(model, t ∈ [0, 1]) + @variable(model, 0 <= x <= 10, Infinite(t)) + @variable(model, z, InfiniteLogical(t)) + @variable(model, w, Bin) + + bvrefs = DP._indicator_to_binary(model) + bvref = bvrefs[z] + + vrefs = Set([x, w]) + DP._variable_bounds(model)[x] = DP.set_variable_bound_info(x, Hull()) + method = DP._Hull(Hull(1e-3), vrefs) + DP._disaggregate_variables(model, z, vrefs, method) + + aff_bin = @expression(model, 2*w + 1) + result_bin = DP.disaggregate_expression(model, aff_bin, bvref, method) + @test haskey(result_bin.terms, w) + + aff_param = @expression(model, 3*t + 1) + result_param = DP.disaggregate_expression(model, aff_param, bvref, method) + @test result_param isa JuMP.GenericQuadExpr + + aff_expr = @expression(model, 2*x + 1) + result_expr = DP.disaggregate_expression(model, aff_expr, bvref, method) + dvref = method.disjunct_variables[x, bvref] + @test result_expr == bvref + 2*dvref +end + +function test_variable_properties_infiniteopt() + model = InfiniteGDPModel() + @infinite_parameter(model, t ∈ [0, 1]) + @variable(model, 1 <= x <= 10, Infinite(t), start = 5.0) + @variable(model, y) + + props_x = DP.VariableProperties(x) + @test props_x.info.has_lb == true + @test props_x.info.lower_bound == 1.0 + @test props_x.info.has_ub == true + @test props_x.info.upper_bound == 10.0 + @test props_x.name == "x" + @test props_x.set === nothing + @test t in InfiniteOpt.parameter_refs(x) + + props_y = DP.VariableProperties(y) + @test props_y.name == "y" + @test props_y.variable_type === nothing +end + +function test_create_blank_variable_with_prefs() + model = InfiniteGDPModel() + @infinite_parameter(model, t ∈ [0, 1]) + @infinite_parameter(model, s ∈ [0, 2]) + + var1 = DP.create_blank_variable(model, "test_var", (t,)) + @test JuMP.name(var1) == "test_var" + @test InfiniteOpt.parameter_refs(var1) == (t,) + + var2 = DP.create_blank_variable(model, "multi_var", (t, s)) + prefs2 = InfiniteOpt.parameter_refs(var2) + @test t in prefs2 + @test s in prefs2 +end + +function test_create_blank_variable_from_expr() + model = InfiniteGDPModel() + @infinite_parameter(model, t ∈ [0, 1]) + @infinite_parameter(model, s ∈ [0, 2]) + @variable(model, x, Infinite(t)) + @variable(model, y, Infinite(s)) + + # Test inferring prefs from single expression + expr = @expression(model, 2*x + y) + var1 = DP.create_blank_variable(model, "inferred_var", expr) + @test JuMP.name(var1) == "inferred_var" + @test InfiniteOpt.parameter_refs(var1) == (t, s) +end + +function test_create_blank_variable_from_vector() + model = InfiniteGDPModel() + @infinite_parameter(model, t ∈ [0, 1]) + @infinite_parameter(model, s ∈ [0, 2]) + @variable(model, x, Infinite(t)) + @variable(model, y, Infinite(s)) + + # Test inferring prefs from vector of expressions + exprs = [@expression(model, x + 1), @expression(model, y + 2)] + var1 = DP.create_blank_variable(model, "vector_var", exprs) + @test JuMP.name(var1) == "vector_var" + prefs = InfiniteOpt.parameter_refs(var1) + @test length(prefs) == 2 +end + +function test_create_blank_variable_default() + model = InfiniteGDPModel() + + # Test default (no parameter dependency) + var1 = DP.create_blank_variable(model, "default_var") + @test JuMP.name(var1) == "default_var" + @test isempty(InfiniteOpt.parameter_refs(var1)) + + # Test with empty name + var2 = DP.create_blank_variable(model) + @test JuMP.name(var2) == "" +end + +function test_add_cardinality_constraint() + model = InfiniteGDPModel() + @infinite_parameter(model, t ∈ [0, 1]) + @variable(model, y[1:3], InfiniteLogical(t)) + + LCR = DP.LogicalConstraintRef{InfiniteModel} + @test @constraint(model, y in Exactly(1)) isa LCR + @test @constraint(model, y in AtLeast(1)) isa LCR + @test @constraint(model, y in AtMost(2)) isa LCR +end + +function test_add_logical_constraint() + model = InfiniteGDPModel() + @infinite_parameter(model, t ∈ [0, 1]) + @variable(model, y[1:2], InfiniteLogical(t)) + + LCR = DP.LogicalConstraintRef{InfiniteModel} + @test @constraint(model, y[1] ∨ y[2] := true) isa LCR + @test @constraint(model, y[1] ∧ y[2] := true) isa LCR + @test @constraint(model, y[1] ⟹ y[2] := true) isa LCR +end + +function test_add_constraint_single_logical_error() + model = InfiniteGDPModel() + @infinite_parameter(model, t ∈ [0, 1]) + @variable(model, y, InfiniteLogical(t)) + + @test_throws ErrorException @constraint(model, y in MOI.EqualTo(true)) +end + +function test_add_constraint_affine_logical_error() + model = InfiniteGDPModel() + @infinite_parameter(model, t ∈ [0, 1]) + @variable(model, y[1:2], InfiniteLogical(t)) + + @test_throws ErrorException @constraint(model, y[1] + y[2] == 1) +end + +function test_add_constraint_quad_logical_error() + model = InfiniteGDPModel() + @infinite_parameter(model, t ∈ [0, 1]) + @variable(model, y[1:2], InfiniteLogical(t)) + + @test_throws ErrorException @constraint(model, y[1] * y[2] == 1) +end + +function test_logical_value() + model = InfiniteGDPModel(HiGHS.Optimizer) + set_silent(model) + @infinite_parameter(model, t ∈ [0, 1], num_supports = 10) + @variable(model, 0 <= x <= 10, Infinite(t)) + @variable(model, y[1:2], InfiniteLogical(t)) + + @constraint(model, x >= 5, Disjunct(y[1])) + @constraint(model, x <= 5, Disjunct(y[2])) + @disjunction(model, [y[1], y[2]]) + + @objective(model, Min, 𝔼(x, t)) + + optimize!(model, gdp_method = Hull()) + + val = value(y[2]) + @test eltype(val) == Bool +end + +function test_unsupported_methods_error() + model = InfiniteGDPModel(HiGHS.Optimizer) + @test_throws ErrorException DP.reformulate_model(model, MBM(HiGHS.Optimizer)) + @test_throws ErrorException DP.reformulate_model(model, cutting_planes(HiGHS.Optimizer)) +end + +function test_methods() + I = 1:3 + J = 1:6 + period_bounds = collect(0:1:6) + expected_obj = 4.504541662743021 + expected_z = -1.3634301575859131 + tol = 0.1 + + # Use Juniper for MIQP support (HiGHS cannot solve MIQP) + ipopt = optimizer_with_attributes(Ipopt.Optimizer, "print_level" => 0, "sb" => "yes") + optimizer = optimizer_with_attributes(Juniper.Optimizer, "nl_solver" => ipopt) + model = InfiniteGDPModel(optimizer) + set_attribute(model, MOI.Silent(), true) + + @infinite_parameter( + model, τ[j in J] in [period_bounds[j], period_bounds[j+1]], + num_supports = 5, independent = true, container = Array + ) + @variable(model, -5 ≤ y[j in J] ≤ 5, Infinite(τ[j]), container = Array) + @variable(model, -4 ≤ z ≤ 4) + @objective(model, Min, 10 * sum(∫(y[j]^2, τ[j]) for j in J)) + + @constraint(model, y[1](0) == 1) + @constraint(model, [j = 2:6], y[j](period_bounds[j]) == y[j-1](period_bounds[j])) + + @variable(model, W[i = I, j = J], Logical) + @constraint(model, [j in J], ∂(y[j], τ[j]) == -2*τ[j] + 0.3*z - 20*y[j], Disjunct(W[1, j])) + @constraint(model, [j in J], ∂(y[j], τ[j]) == -2*z + 0.4*τ[j] - 4, Disjunct(W[2, j])) + @constraint(model, [j in J], ∂(y[j], τ[j]) == 2*z + 4*(τ[j] - y[j] - 1), Disjunct(W[3, j])) + @disjunction(model, [j in J], W[:, j]) + + for j in J + set_upper_bound(∂(y[j], τ[j]), 100) + set_lower_bound(∂(y[j], τ[j]), -100) + end + + @test optimize!(model, gdp_method = BigM()) isa Nothing + @test objective_value(model) ≈ expected_obj atol=tol + @test value(z) ≈ expected_z atol=tol + + @test optimize!(model, gdp_method = Hull()) isa Nothing + @test objective_value(model) ≈ expected_obj atol=tol + @test value(z) ≈ expected_z atol=tol + + @test optimize!(model, gdp_method = PSplit(3, model)) isa Nothing + @test objective_value(model) ≈ expected_obj atol=tol + @test value(z) ≈ expected_z atol=tol +end + @testset "InfiniteDisjunctiveProgramming" begin - test_infiniteopt_extension() + + @testset "Model" begin + test_infinite_gdp_model_creation() + end + + @testset "all_variables" begin + test_all_variables_infiniteopt() + end + + @testset "Variables" begin + test_infinite_logical() + test_is_parameter() + test_requires_disaggregation() + test_variable_properties_infiniteopt() + test_create_blank_variable_with_prefs() + test_create_blank_variable_from_expr() + test_create_blank_variable_from_vector() + test_create_blank_variable_default() + test_logical_value() + end + + @testset "Constraints" begin + test_add_cardinality_constraint() + test_add_logical_constraint() + test_add_constraint_single_logical_error() + test_add_constraint_affine_logical_error() + test_add_constraint_quad_logical_error() + end + + @testset "Methods" begin + test_get_constant() + test_disaggregate_expression_infiniteopt() + test_unsupported_methods_error() + end + + @testset "Integration" begin + test_infiniteopt_extension() + test_methods() + end + end \ No newline at end of file diff --git a/test/runtests.jl b/test/runtests.jl index 11b07df..92563dd 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -4,25 +4,25 @@ using Test include("utilities.jl") # RUN ALL THE TESTS -# include("aqua.jl") # temporary ignore until compat is finalized -# include("model.jl") -# include("jump.jl") -# include("variables/query.jl") -# include("variables/logical.jl") -# include("variables/creation.jl") -# include("constraints/selector.jl") -# include("constraints/proposition.jl") -# include("constraints/disjunct.jl") -# include("constraints/indicator.jl") -# include("constraints/mbm.jl") -# include("constraints/bigm.jl") -# include("constraints/psplit.jl") -# include("constraints/cuttingplanes.jl") -# include("constraints/hull.jl") -# include("constraints/fallback.jl") -# include("constraints/disjunction.jl") -# include("print.jl") -# include("solve.jl") +include("aqua.jl") # temporary ignore until compat is finalized +include("model.jl") +include("jump.jl") +include("variables/query.jl") +include("variables/logical.jl") +include("variables/creation.jl") +include("constraints/selector.jl") +include("constraints/proposition.jl") +include("constraints/disjunct.jl") +include("constraints/indicator.jl") +include("constraints/mbm.jl") +include("constraints/bigm.jl") +include("constraints/psplit.jl") +include("constraints/cuttingplanes.jl") +include("constraints/hull.jl") +include("constraints/fallback.jl") +include("constraints/disjunction.jl") +include("print.jl") +include("solve.jl") if Base.VERSION >= v"1.9" # extensions require Julia v1.9+ import Pkg diff --git a/test/utilities.jl b/test/utilities.jl index 1fee4e2..7472c32 100644 --- a/test/utilities.jl +++ b/test/utilities.jl @@ -158,3 +158,75 @@ function JuMP.add_constraint( return DP._add_logical_constraint(model, c, name) end DP.requires_disaggregation(::MyVarRef) = true + +################################################################################ +# UTILITY FUNCTION TESTS +################################################################################ +function test_all_variables() + model = GDPModel() + @variable(model, x) + @variable(model, y[1:3]) + @variable(model, z, Bin) + + all_vars = DP.all_variables(model) + @test x in all_vars + @test y[1] in all_vars + @test y[2] in all_vars + @test y[3] in all_vars + @test z in all_vars + @test length(all_vars) == 5 +end + +function test_get_constant_affine() + model = GDPModel() + @variable(model, x) + @variable(model, y) + + # Affine expression with constant + expr1 = @expression(model, 2*x + 3*y + 5.0) + @test DP.get_constant(expr1) == 5.0 + + # Affine expression without constant + expr2 = @expression(model, 2*x + 3*y) + @test DP.get_constant(expr2) == 0.0 + + # Only constant + expr3 = @expression(model, 0*x + 7.0) + @test DP.get_constant(expr3) == 7.0 +end + +function test_get_constant_quadratic() + model = GDPModel() + @variable(model, x) + @variable(model, y) + + # Quadratic expression with constant + expr1 = @expression(model, x^2 + 2*x*y + 3.0) + @test DP.get_constant(expr1) == 3.0 + + # Quadratic expression without constant + expr2 = @expression(model, x^2 + y^2) + @test DP.get_constant(expr2) == 0.0 +end + +function test_get_constant_number() + @test DP.get_constant(5.0) == 5.0 + @test DP.get_constant(0) == 0 + @test DP.get_constant(-3.14) == -3.14 +end + +function test_get_constant_variable() + model = GDPModel() + @variable(model, x) + + # Variable reference should return zero + @test DP.get_constant(x) == 0.0 +end + +@testset "Utility Functions" begin + test_all_variables() + test_get_constant_affine() + test_get_constant_quadratic() + test_get_constant_number() + test_get_constant_variable() +end diff --git a/test/variables/creation.jl b/test/variables/creation.jl index e4c471e..754232e 100644 --- a/test/variables/creation.jl +++ b/test/variables/creation.jl @@ -108,22 +108,88 @@ function test_variable_copy() @variable(model1, original, lower_bound = 1, upper_bound = 5, start = 3.0) - props = DP.VariableProperties(original) - - recreated = DP.create_variable(model2, props) - - @test JuMP.has_lower_bound(recreated) == JuMP.has_lower_bound(original) - @test JuMP.lower_bound(recreated) == JuMP.lower_bound(original) - @test JuMP.has_upper_bound(recreated) == JuMP.has_upper_bound(original) - @test JuMP.upper_bound(recreated) == JuMP.upper_bound(original) - @test JuMP.has_start_value(recreated) == JuMP.has_start_value(original) - @test JuMP.start_value(recreated) == JuMP.start_value(original) - @test JuMP.name(recreated) == JuMP.name(original) + # Test variable_copy function directly + copied = DP.variable_copy(model2, original) + + @test JuMP.has_lower_bound(copied) == JuMP.has_lower_bound(original) + @test JuMP.lower_bound(copied) == JuMP.lower_bound(original) + @test JuMP.has_upper_bound(copied) == JuMP.has_upper_bound(original) + @test JuMP.upper_bound(copied) == JuMP.upper_bound(original) + @test JuMP.has_start_value(copied) == JuMP.has_start_value(original) + @test JuMP.start_value(copied) == JuMP.start_value(original) + @test JuMP.name(copied) == JuMP.name(original) @test original in JuMP.all_variables(model1) - @test recreated in JuMP.all_variables(model2) + @test copied in JuMP.all_variables(model2) @test !(original in JuMP.all_variables(model2)) - @test !(recreated in JuMP.all_variables(model1)) + @test !(copied in JuMP.all_variables(model1)) +end + +function test_get_variable_info() + model = Model() + + # Test with all properties set + @variable(model, x, lower_bound = 1.0, upper_bound = 10.0, start = 5.0) + info = DP.get_variable_info(x) + @test info.has_lb == true + @test info.lower_bound == 1.0 + @test info.has_ub == true + @test info.upper_bound == 10.0 + @test info.has_start == true + @test info.start == 5.0 + @test info.has_fix == false + @test info.binary == false + @test info.integer == false + + # Test with binary variable + @variable(model, y, Bin) + info_bin = DP.get_variable_info(y) + @test info_bin.binary == true + @test info_bin.integer == false + + # Test with integer variable + @variable(model, z, Int) + info_int = DP.get_variable_info(z) + @test info_int.binary == false + @test info_int.integer == true + + # Test with fixed variable + @variable(model, w == 7.5) + info_fix = DP.get_variable_info(w) + @test info_fix.has_fix == true + @test info_fix.fixed_value == 7.5 + + # Test with overridden parameters + info_override = DP.get_variable_info(x, has_lb = false, upper_bound = 20.0) + @test info_override.has_lb == false + @test info_override.lower_bound == 0 # Default when has_lb is false + @test info_override.upper_bound == 20.0 # Overridden +end + +function test_create_blank_variable() + model = Model() + + # Test with name + var1 = DP.create_blank_variable(model, "blank_var") + @test JuMP.name(var1) == "blank_var" + @test JuMP.has_lower_bound(var1) == false + @test JuMP.has_upper_bound(var1) == false + @test JuMP.is_fixed(var1) == false + @test JuMP.is_binary(var1) == false + @test JuMP.is_integer(var1) == false + @test var1 in JuMP.all_variables(model) + + # Test without name (default empty string) + var2 = DP.create_blank_variable(model) + @test JuMP.name(var2) == "" + @test var2 in JuMP.all_variables(model) + + # Test fallback with expression (for extensions) + @variable(model, x) + expr = 2*x + 1 + var3 = DP.create_blank_variable(model, "from_expr", expr) + @test JuMP.name(var3) == "from_expr" + @test var3 in JuMP.all_variables(model) end @testset "Variable Creation" begin @@ -131,4 +197,7 @@ end test_make_variable_object() test_create_variable() test_complete_workflow() + test_variable_copy() + test_get_variable_info() + test_create_blank_variable() end \ No newline at end of file From c7f5b6ab8b8fdcbf0af819f8a7a329b1de8bfc29 Mon Sep 17 00:00:00 2001 From: dnguyen227 Date: Tue, 16 Dec 2025 10:26:45 -0500 Subject: [PATCH 11/30] julia version --- .github/workflows/CI.yml | 2 +- Project.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index f8a1345..5d47e30 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -12,7 +12,7 @@ jobs: strategy: matrix: version: - - '1.6' + - '1.7' os: - ubuntu-latest - macOS-latest diff --git a/Project.toml b/Project.toml index 688ca05..5a0b9f8 100644 --- a/Project.toml +++ b/Project.toml @@ -17,7 +17,7 @@ InfiniteDisjunctiveProgramming = "InfiniteOpt" Aqua = "0.8" JuMP = "1.18" Reexport = "1" -julia = "1.6" +julia = "1.7" Juniper = "0.9.3" Ipopt = "1.9.0" InfiniteOpt = "0.6" From 6e9b625bafbc300668e389b16cbee328af851a88 Mon Sep 17 00:00:00 2001 From: dnguyen227 Date: Tue, 16 Dec 2025 10:37:15 -0500 Subject: [PATCH 12/30] version change --- Project.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Project.toml b/Project.toml index 5a0b9f8..89fd397 100644 --- a/Project.toml +++ b/Project.toml @@ -15,9 +15,9 @@ InfiniteDisjunctiveProgramming = "InfiniteOpt" [compat] Aqua = "0.8" -JuMP = "1.18" +JuMP = "1.29" Reexport = "1" -julia = "1.7" +julia = "1.10" Juniper = "0.9.3" Ipopt = "1.9.0" InfiniteOpt = "0.6" From ef789bf46a8d51332b150f4fcd25322deec71f27 Mon Sep 17 00:00:00 2001 From: dnguyen227 Date: Tue, 16 Dec 2025 10:48:05 -0500 Subject: [PATCH 13/30] update CI --- .github/workflows/CI.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 5d47e30..d215f67 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -12,7 +12,7 @@ jobs: strategy: matrix: version: - - '1.7' + - '1.10' os: - ubuntu-latest - macOS-latest From 36e3f774240052a77f52061a47c29549855df131 Mon Sep 17 00:00:00 2001 From: dnguyen227 Date: Tue, 16 Dec 2025 23:28:42 -0500 Subject: [PATCH 14/30] added docstrings --- ext/InfiniteDisjunctiveProgramming.jl | 4 --- src/variables.jl | 33 +++++++++++++++---- .../InfiniteDisjunctiveProgramming.jl | 18 ++-------- 3 files changed, 30 insertions(+), 25 deletions(-) diff --git a/ext/InfiniteDisjunctiveProgramming.jl b/ext/InfiniteDisjunctiveProgramming.jl index 6527ae1..cac7150 100644 --- a/ext/InfiniteDisjunctiveProgramming.jl +++ b/ext/InfiniteDisjunctiveProgramming.jl @@ -78,10 +78,6 @@ function DP.create_blank_variable(model::InfiniteOpt.InfiniteModel, name::String return DP.create_blank_variable(model, name, Tuple(all_prefs)) end -function DP.create_blank_variable(model::InfiniteOpt.InfiniteModel, name::String = "") - return DP.create_blank_variable(model, name, ()) -end - function JuMP.value(vref::DP.LogicalVariableRef{InfiniteOpt.InfiniteModel}) return JuMP.value(DP.binary_variable(vref)) .>= 0.5 end diff --git a/src/variables.jl b/src/variables.jl index 43b7db3..0ef54e3 100644 --- a/src/variables.jl +++ b/src/variables.jl @@ -519,6 +519,31 @@ function variable_copy( return create_variable(model, props) end +""" + get_variable_info(vref::JuMP.AbstractVariableRef; kwargs...)::JuMP.VariableInfo + +Extracts variable information from a JuMP variable reference and returns a `JuMP.VariableInfo` +object. This function retrieves bounds, fixed values, start values, and binary/integer status +from the variable reference. + +## Keyword Arguments +- `has_lb::Bool`: Whether the variable has a lower bound (default: queries `vref`) +- `has_ub::Bool`: Whether the variable has an upper bound (default: queries `vref`) +- `has_fix::Bool`: Whether the variable is fixed (default: queries `vref`) +- `has_start::Bool`: Whether the variable has a start value (default: queries `vref`) +- `has_binary::Bool`: Whether the variable is binary (default: queries `vref`) +- `has_integer::Bool`: Whether the variable is integer (default: queries `vref`) +- `lower_bound`: The lower bound value (default: queries `vref` if has_lb, else 0) +- `upper_bound`: The upper bound value (default: queries `vref` if has_ub, else 0) +- `fixed_value`: The fixed value (default: queries `vref` if has_fix, else 0) +- `start_value`: The start value (default: queries `vref` if has_start, else 0) +- `binary::Bool`: Binary status (default: queries `vref`) +- `integer::Bool`: Integer status (default: queries `vref`) + +## Returns +A `JuMP.VariableInfo` object containing all the variable's attributes. +""" + function get_variable_info(vref::JuMP.AbstractVariableRef; has_lb::Bool = JuMP.has_lower_bound(vref), has_ub::Bool = JuMP.has_upper_bound(vref), @@ -551,13 +576,9 @@ function _blank_variable_info() return JuMP.VariableInfo(false, NaN, false, NaN, false, NaN, false, NaN, false, false) end -function create_blank_variable(model::JuMP.AbstractModel, name::String = "") + +function create_blank_variable(model::JuMP.AbstractModel, name::String = "", expr = nothing) info = _blank_variable_info() var = JuMP.build_variable(error, info) return JuMP.add_variable(model, var, name) -end - -# Fallback that accepts an expression for parameter inference (used by extensions) -function create_blank_variable(model::JuMP.AbstractModel, name::String, expr) - return create_blank_variable(model, name) end \ No newline at end of file diff --git a/test/extensions/InfiniteDisjunctiveProgramming.jl b/test/extensions/InfiniteDisjunctiveProgramming.jl index 79163d7..12b4497 100644 --- a/test/extensions/InfiniteDisjunctiveProgramming.jl +++ b/test/extensions/InfiniteDisjunctiveProgramming.jl @@ -228,19 +228,6 @@ function test_create_blank_variable_from_vector() @test length(prefs) == 2 end -function test_create_blank_variable_default() - model = InfiniteGDPModel() - - # Test default (no parameter dependency) - var1 = DP.create_blank_variable(model, "default_var") - @test JuMP.name(var1) == "default_var" - @test isempty(InfiniteOpt.parameter_refs(var1)) - - # Test with empty name - var2 = DP.create_blank_variable(model) - @test JuMP.name(var2) == "" -end - function test_add_cardinality_constraint() model = InfiniteGDPModel() @infinite_parameter(model, t ∈ [0, 1]) @@ -321,7 +308,9 @@ function test_methods() tol = 0.1 # Use Juniper for MIQP support (HiGHS cannot solve MIQP) - ipopt = optimizer_with_attributes(Ipopt.Optimizer, "print_level" => 0, "sb" => "yes") + ipopt = optimizer_with_attributes(Ipopt.Optimizer, + "print_level" => 0, "sb" => "yes" + ) optimizer = optimizer_with_attributes(Juniper.Optimizer, "nl_solver" => ipopt) model = InfiniteGDPModel(optimizer) set_attribute(model, MOI.Silent(), true) @@ -379,7 +368,6 @@ end test_create_blank_variable_with_prefs() test_create_blank_variable_from_expr() test_create_blank_variable_from_vector() - test_create_blank_variable_default() test_logical_value() end From 6f701adcd54b5e915ccd99f73f10edf7a240e543 Mon Sep 17 00:00:00 2001 From: dnguyen227 Date: Wed, 17 Dec 2025 10:43:51 -0500 Subject: [PATCH 15/30] docstring for create_blank_variable --- src/variables.jl | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/variables.jl b/src/variables.jl index 0ef54e3..528838b 100644 --- a/src/variables.jl +++ b/src/variables.jl @@ -575,6 +575,16 @@ end function _blank_variable_info() return JuMP.VariableInfo(false, NaN, false, NaN, false, NaN, false, NaN, false, false) end +""" + _blank_variable_info()::JuMP.VariableInfo + +Creates a blank `JuMP.VariableInfo` object with no bounds, no fixed value, +no start value, and neither binary nor integer constraints. + +## Returns +A `JuMP.VariableInfo` object with all flags set to `false` and all numeric +values set to `NaN`. +""" function create_blank_variable(model::JuMP.AbstractModel, name::String = "", expr = nothing) From 5f2e4b788e5e16679d33a6cf401b867df662b66d Mon Sep 17 00:00:00 2001 From: dnguyen227 Date: Sun, 21 Dec 2025 10:59:54 -0500 Subject: [PATCH 16/30] version revert, dispatch on VariableProperties to create free variables --- .github/workflows/CI.yml | 2 +- Project.toml | 4 +- ext/InfiniteDisjunctiveProgramming.jl | 35 +++++++------ src/datatypes.jl | 17 +++++++ src/psplit.jl | 24 ++++++--- src/utilities.jl | 8 ++- src/variables.jl | 16 ++---- .../InfiniteDisjunctiveProgramming.jl | 51 ++++++++----------- test/variables/creation.jl | 37 ++++++-------- 9 files changed, 102 insertions(+), 92 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index d215f67..f8a1345 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -12,7 +12,7 @@ jobs: strategy: matrix: version: - - '1.10' + - '1.6' os: - ubuntu-latest - macOS-latest diff --git a/Project.toml b/Project.toml index 89fd397..688ca05 100644 --- a/Project.toml +++ b/Project.toml @@ -15,9 +15,9 @@ InfiniteDisjunctiveProgramming = "InfiniteOpt" [compat] Aqua = "0.8" -JuMP = "1.29" +JuMP = "1.18" Reexport = "1" -julia = "1.10" +julia = "1.6" Juniper = "0.9.3" Ipopt = "1.9.0" InfiniteOpt = "0.6" diff --git a/ext/InfiniteDisjunctiveProgramming.jl b/ext/InfiniteDisjunctiveProgramming.jl index cac7150..1e0dca3 100644 --- a/ext/InfiniteDisjunctiveProgramming.jl +++ b/ext/InfiniteDisjunctiveProgramming.jl @@ -26,7 +26,7 @@ end ################################################################################ DP.InfiniteLogical(prefs...) = DP.Logical(InfiniteOpt.Infinite(prefs...)) -function is_parameter(vref::InfiniteOpt.GeneralVariableRef) +function _is_parameter(vref::InfiniteOpt.GeneralVariableRef) dref = InfiniteOpt.dispatch_variable_ref(vref) if typeof(dref) <: Union{ InfiniteOpt.DependentParameterRef, @@ -41,7 +41,7 @@ function is_parameter(vref::InfiniteOpt.GeneralVariableRef) end function DP.requires_disaggregation(vref::InfiniteOpt.GeneralVariableRef) - return !is_parameter(vref) + return !_is_parameter(vref) end function DP.VariableProperties(vref::InfiniteOpt.GeneralVariableRef) @@ -53,29 +53,32 @@ function DP.VariableProperties(vref::InfiniteOpt.GeneralVariableRef) return DP.VariableProperties(info, name, set, var_type) end -function DP.create_blank_variable(model::InfiniteOpt.InfiniteModel, name::String, prefs::Tuple) - info = DP._blank_variable_info() - if isempty(prefs) - var = JuMP.build_variable(error, info) - else - var = JuMP.build_variable(error, info, InfiniteOpt.Infinite(prefs...)) - end - return JuMP.add_variable(model, var, name) +# Extract parameter refs from expression and return VariableProperties with Infinite type +function DP.VariableProperties(expr::JuMP.GenericAffExpr{C, InfiniteOpt.GeneralVariableRef}) where C + prefs = InfiniteOpt.parameter_refs(expr) + info = DP._free_variable_info() + var_type = !isempty(prefs) ? InfiniteOpt.Infinite(prefs...) : nothing + return DP.VariableProperties(info, "", nothing, var_type) end -function DP.create_blank_variable(model::InfiniteOpt.InfiniteModel, name::String, expr) +function DP.VariableProperties(expr::JuMP.GenericQuadExpr{C, InfiniteOpt.GeneralVariableRef}) where C prefs = InfiniteOpt.parameter_refs(expr) - return DP.create_blank_variable(model, name, prefs) + info = DP._free_variable_info() + var_type = !isempty(prefs) ? InfiniteOpt.Infinite(prefs...) : nothing + return DP.VariableProperties(info, "", nothing, var_type) end -function DP.create_blank_variable(model::InfiniteOpt.InfiniteModel, name::String, exprs::Vector) +function DP.VariableProperties(exprs::Vector{<:Union{InfiniteOpt.GeneralVariableRef, JuMP.GenericAffExpr{<:Any, InfiniteOpt.GeneralVariableRef}, JuMP.GenericQuadExpr{<:Any, InfiniteOpt.GeneralVariableRef}}}) all_prefs = Set{InfiniteOpt.GeneralVariableRef}() for expr in exprs for pref in InfiniteOpt.parameter_refs(expr) push!(all_prefs, pref) end end - return DP.create_blank_variable(model, name, Tuple(all_prefs)) + prefs = Tuple(all_prefs) + info = DP._free_variable_info() + var_type = !isempty(prefs) ? InfiniteOpt.Infinite(prefs...) : nothing + return DP.VariableProperties(info, "", nothing, var_type) end function JuMP.value(vref::DP.LogicalVariableRef{InfiniteOpt.InfiniteModel}) @@ -132,7 +135,7 @@ function DP.get_constant(expr::JuMP.GenericAffExpr{T, InfiniteOpt.GeneralVariabl constant = JuMP.constant(expr) param_expr = zero(typeof(expr)) for (var, coeff) in expr.terms - if is_parameter(var) + if _is_parameter(var) JuMP.add_to_expression!(param_expr, coeff, var) end end @@ -149,7 +152,7 @@ function DP.disaggregate_expression( for (vref, coeff) in aff.terms if JuMP.is_binary(vref) push!(terms, coeff * vref) - elseif vref isa InfiniteOpt.GeneralVariableRef && is_parameter(vref) + elseif vref isa InfiniteOpt.GeneralVariableRef && _is_parameter(vref) push!(terms, coeff * vref * bvref) elseif !haskey(method.disjunct_variables, (vref, bvref)) push!(terms, coeff * vref) diff --git a/src/datatypes.jl b/src/datatypes.jl index 00550c0..6ea2e73 100644 --- a/src/datatypes.jl +++ b/src/datatypes.jl @@ -632,3 +632,20 @@ function VariableProperties(vref::JuMP.AbstractVariableRef) return VariableProperties(info, name, nothing, nothing) end +""" + VariableProperties(expr)::VariableProperties + +Creates a `VariableProperties` object with blank variable info (no bounds, not fixed, +not binary/integer) from an expression. The `expr` argument is provided for +extensions to infer additional properties (e.g., parameter dependencies in InfiniteOpt). + +## Arguments +- `expr`: Expression for extensions to extract metadata from + +## Returns +A `VariableProperties` object with blank info. +""" +function VariableProperties(expr) + info = _free_variable_info() + return VariableProperties(info, "", nothing, nothing) +end \ No newline at end of file diff --git a/src/psplit.jl b/src/psplit.jl index 84c58be..be4dea1 100644 --- a/src/psplit.jl +++ b/src/psplit.jl @@ -286,7 +286,8 @@ function _build_partitioned_constraint( part_con = Vector{JuMP.AbstractConstraint}(undef, p) for i in 1:p func, _ = _build_partitioned_expression(con.func, method.partition[i]) - v[i] = create_blank_variable(model, "v_$(hash(con))_$(i)", func) + v[i] = create_variable(model, VariableProperties(func)) + JuMP.set_name(v[i], "v_$(hash(con))_$(i)") part_con[i] = JuMP.build_constraint(error, func - v[i], MOI.LessThan(zero(val_type)) ) @@ -309,7 +310,8 @@ function _build_partitioned_constraint( _, constant = _build_partitioned_expression(con.func, method.partition[p]) for i in 1:p func, _ = _build_partitioned_expression(con.func, method.partition[i]) - v[i] = create_blank_variable(model, "v_$(hash(con))_$(i)", func) + v[i] = create_variable(model, VariableProperties(func)) + JuMP.set_name(v[i], "v_$(hash(con))_$(i)") part_con[i] = JuMP.build_constraint(error, -func - v[i], MOI.LessThan(zero(val_type)) ) @@ -333,8 +335,10 @@ function _build_partitioned_constraint( v = Matrix{JuMP.variable_ref_type(M)}(undef, p, 2) for i in 1:p func, _= _build_partitioned_expression(con.func, method.partition[i]) - v[i,1] = create_blank_variable(model, "v_$(hash(con))_$(i)_1", func) - v[i,2] = create_blank_variable(model, "v_$(hash(con))_$(i)_2", func) + v[i,1] = create_variable(model, VariableProperties(func)) + v[i,2] = create_variable(model, VariableProperties(func)) + JuMP.set_name(v[i,1], "v_$(hash(con))_$(i)_1") + JuMP.set_name(v[i,2], "v_$(hash(con))_$(i)_2") part_con_lt[i] = JuMP.build_constraint(error, func - v[i,1], MOI.LessThan(zero(val_type)) ) @@ -366,7 +370,8 @@ function _build_partitioned_constraint( func = JuMP.@expression(model, [j = 1:d], part_expr[j][1]) constants .= [part_expr[j][2] for j in 1:d] for j in 1:d - v[i,j] = create_blank_variable(model, "v_$(hash(con))_$(i)_$(j)", func[j]) + v[i,j] = create_variable(model, VariableProperties(func[j])) + JuMP.set_name(v[i,j], "v_$(hash(con))_$(i)_$(j)") _bound_auxiliary(model, v[i,j], func[j], method) end part_con[i] = JuMP.build_constraint(error, @@ -398,7 +403,8 @@ function _build_partitioned_constraint( func = JuMP.@expression(model, [j = 1:d], -part_expr[j][1]) constants .= [-part_expr[j][2] for j in 1:d] for j in 1:d - v[i,j] = create_blank_variable(model, "v_$(hash(con))_$(i)_$(j)", func[j]) + v[i,j] = create_variable(model, VariableProperties(func[j])) + JuMP.set_name(v[i,j], "v_$(hash(con))_$(i)_$(j)") _bound_auxiliary(model, v[i,j], func[j], method) end part_con[i] = JuMP.build_constraint(error, func - v[i,:], _MOI.Nonpositives(d)) @@ -429,8 +435,10 @@ function _build_partitioned_constraint( func = JuMP.@expression(model, [j = 1:d], part_expr[j][1]) constants .= [part_expr[j][2] for j in 1:d] for j in 1:d - v[i,j,1] = create_blank_variable(model, "v_$(hash(con))_$(i)_$(j)_1", func[j]) - v[i,j,2] = create_blank_variable(model, "v_$(hash(con))_$(i)_$(j)_2", func[j]) + v[i,j,1] = create_variable(model, VariableProperties(func[j])) + v[i,j,2] = create_variable(model, VariableProperties(func[j])) + JuMP.set_name(v[i,j,1], "v_$(hash(con))_$(i)_$(j)_1") + JuMP.set_name(v[i,j,2], "v_$(hash(con))_$(i)_$(j)_2") _bound_auxiliary(model, v[i,j,1], func[j], method) _bound_auxiliary(model, v[i,j,2], -func[j], method) end diff --git a/src/utilities.jl b/src/utilities.jl index cc37991..a8a902a 100644 --- a/src/utilities.jl +++ b/src/utilities.jl @@ -31,7 +31,9 @@ additional terms should be treated as constants. get_constant(expr::JuMP.GenericAffExpr) = JuMP.constant(expr) get_constant(expr::JuMP.GenericQuadExpr) = JuMP.constant(expr) get_constant(expr::Number) = expr -get_constant(expr::JuMP.AbstractVariableRef) = zero(Float64) +function get_constant(expr::JuMP.AbstractVariableRef) + return zero(JuMP.value_type(typeof(JuMP.owner_model(expr)))) +end ################################################################################ # MODEL COPYING @@ -178,7 +180,9 @@ function copy_gdp_data( # Copying indicator to constraints for (lv_ref, con_refs) in old_gdp.indicator_to_constraints new_lvar_ref = lv_map[lv_ref] - new_con_refs = Vector{Union{DisjunctConstraintRef{M}, DisjunctionRef{M}}}() + new_con_refs = Vector{ + Union{DisjunctConstraintRef{M}, DisjunctionRef{M}} + }() for con_ref in con_refs new_con_ref = _remap_indicator_to_constraint(con_ref, disj_con_map, disj_map diff --git a/src/variables.jl b/src/variables.jl index 528838b..f96cfe0 100644 --- a/src/variables.jl +++ b/src/variables.jl @@ -543,7 +543,6 @@ from the variable reference. ## Returns A `JuMP.VariableInfo` object containing all the variable's attributes. """ - function get_variable_info(vref::JuMP.AbstractVariableRef; has_lb::Bool = JuMP.has_lower_bound(vref), has_ub::Bool = JuMP.has_upper_bound(vref), @@ -572,11 +571,8 @@ function get_variable_info(vref::JuMP.AbstractVariableRef; return info end -function _blank_variable_info() - return JuMP.VariableInfo(false, NaN, false, NaN, false, NaN, false, NaN, false, false) -end """ - _blank_variable_info()::JuMP.VariableInfo + _free_variable_info()::JuMP.VariableInfo Creates a blank `JuMP.VariableInfo` object with no bounds, no fixed value, no start value, and neither binary nor integer constraints. @@ -585,10 +581,6 @@ no start value, and neither binary nor integer constraints. A `JuMP.VariableInfo` object with all flags set to `false` and all numeric values set to `NaN`. """ - - -function create_blank_variable(model::JuMP.AbstractModel, name::String = "", expr = nothing) - info = _blank_variable_info() - var = JuMP.build_variable(error, info) - return JuMP.add_variable(model, var, name) -end \ No newline at end of file +function _free_variable_info() + return JuMP.VariableInfo(false, NaN, false, NaN, false, NaN, false, NaN, false, false) +end diff --git a/test/extensions/InfiniteDisjunctiveProgramming.jl b/test/extensions/InfiniteDisjunctiveProgramming.jl index 12b4497..da9ffa1 100644 --- a/test/extensions/InfiniteDisjunctiveProgramming.jl +++ b/test/extensions/InfiniteDisjunctiveProgramming.jl @@ -55,7 +55,7 @@ function test_infinite_logical() @test binary_variable(y) isa InfiniteOpt.GeneralVariableRef end -function test_is_parameter() +function test__is_parameter() model = InfiniteGDPModel() @infinite_parameter(model, t ∈ [0, 1]) @infinite_parameter(model, s[1:2] ∈ [0, 1], independent = true) @@ -64,17 +64,17 @@ function test_is_parameter() @variable(model, y) # Test DependentParameterRef - @test IDP.is_parameter(t) == true + @test IDP._is_parameter(t) == true # Test IndependentParameterRef - @test IDP.is_parameter(s[1]) == true + @test IDP._is_parameter(s[1]) == true # Test FiniteParameterRef - @test IDP.is_parameter(p) == true + @test IDP._is_parameter(p) == true # Test non-parameter variables (else branch) - @test IDP.is_parameter(x) == false - @test IDP.is_parameter(y) == false + @test IDP._is_parameter(x) == false + @test IDP._is_parameter(y) == false end function test_requires_disaggregation() @@ -119,7 +119,7 @@ function test_get_constant() @finite_parameter(model, p == 2.0) @variable(model, x, Infinite(t)) - # Test expression with parameter terms (is_parameter branch) + # Test expression with parameter terms (_is_parameter branch) expr2 = @expression(model, 3.0 + 2*t + x) constant2 = DP.get_constant(expr2) @test JuMP.constant(constant2) == 3.0 @@ -184,22 +184,7 @@ function test_variable_properties_infiniteopt() @test props_y.variable_type === nothing end -function test_create_blank_variable_with_prefs() - model = InfiniteGDPModel() - @infinite_parameter(model, t ∈ [0, 1]) - @infinite_parameter(model, s ∈ [0, 2]) - - var1 = DP.create_blank_variable(model, "test_var", (t,)) - @test JuMP.name(var1) == "test_var" - @test InfiniteOpt.parameter_refs(var1) == (t,) - - var2 = DP.create_blank_variable(model, "multi_var", (t, s)) - prefs2 = InfiniteOpt.parameter_refs(var2) - @test t in prefs2 - @test s in prefs2 -end - -function test_create_blank_variable_from_expr() +function test_variable_properties_from_expr() model = InfiniteGDPModel() @infinite_parameter(model, t ∈ [0, 1]) @infinite_parameter(model, s ∈ [0, 2]) @@ -208,12 +193,17 @@ function test_create_blank_variable_from_expr() # Test inferring prefs from single expression expr = @expression(model, 2*x + y) - var1 = DP.create_blank_variable(model, "inferred_var", expr) + props = DP.VariableProperties(expr) + @test props.name == "" + @test props.variable_type isa InfiniteOpt.Infinite + @test Set(props.variable_type.parameter_refs) == Set((t, s)) + var1 = DP.create_variable(model, props) + JuMP.set_name(var1, "inferred_var") @test JuMP.name(var1) == "inferred_var" @test InfiniteOpt.parameter_refs(var1) == (t, s) end -function test_create_blank_variable_from_vector() +function test_variable_properties_from_vector() model = InfiniteGDPModel() @infinite_parameter(model, t ∈ [0, 1]) @infinite_parameter(model, s ∈ [0, 2]) @@ -222,7 +212,9 @@ function test_create_blank_variable_from_vector() # Test inferring prefs from vector of expressions exprs = [@expression(model, x + 1), @expression(model, y + 2)] - var1 = DP.create_blank_variable(model, "vector_var", exprs) + props = DP.VariableProperties(exprs) + var1 = DP.create_variable(model, props) + JuMP.set_name(var1, "vector_var") @test JuMP.name(var1) == "vector_var" prefs = InfiniteOpt.parameter_refs(var1) @test length(prefs) == 2 @@ -362,12 +354,11 @@ end @testset "Variables" begin test_infinite_logical() - test_is_parameter() + test__is_parameter() test_requires_disaggregation() test_variable_properties_infiniteopt() - test_create_blank_variable_with_prefs() - test_create_blank_variable_from_expr() - test_create_blank_variable_from_vector() + test_variable_properties_from_expr() + test_variable_properties_from_vector() test_logical_value() end diff --git a/test/variables/creation.jl b/test/variables/creation.jl index 754232e..5645fec 100644 --- a/test/variables/creation.jl +++ b/test/variables/creation.jl @@ -166,30 +166,25 @@ function test_get_variable_info() @test info_override.upper_bound == 20.0 # Overridden end -function test_create_blank_variable() +function test_variable_properties_from_expr() model = Model() - # Test with name - var1 = DP.create_blank_variable(model, "blank_var") - @test JuMP.name(var1) == "blank_var" - @test JuMP.has_lower_bound(var1) == false - @test JuMP.has_upper_bound(var1) == false - @test JuMP.is_fixed(var1) == false - @test JuMP.is_binary(var1) == false - @test JuMP.is_integer(var1) == false - @test var1 in JuMP.all_variables(model) - - # Test without name (default empty string) - var2 = DP.create_blank_variable(model) - @test JuMP.name(var2) == "" - @test var2 in JuMP.all_variables(model) - - # Test fallback with expression (for extensions) + # Test with expression (for extensions - base ignores it) @variable(model, x) expr = 2*x + 1 - var3 = DP.create_blank_variable(model, "from_expr", expr) - @test JuMP.name(var3) == "from_expr" - @test var3 in JuMP.all_variables(model) + props = DP.VariableProperties(expr) + @test props.name == "" + @test props.info.has_lb == false + @test props.info.has_ub == false + var = DP.create_variable(model, props) + JuMP.set_name(var, "from_expr") + @test JuMP.name(var) == "from_expr" + @test JuMP.has_lower_bound(var) == false + @test JuMP.has_upper_bound(var) == false + @test JuMP.is_fixed(var) == false + @test JuMP.is_binary(var) == false + @test JuMP.is_integer(var) == false + @test var in JuMP.all_variables(model) end @testset "Variable Creation" begin @@ -199,5 +194,5 @@ end test_complete_workflow() test_variable_copy() test_get_variable_info() - test_create_blank_variable() + test_variable_properties_from_expr() end \ No newline at end of file From beb504543557521d87eb92fa1959d9f49bf96e73 Mon Sep 17 00:00:00 2001 From: dnguyen227 Date: Sun, 21 Dec 2025 11:12:22 -0500 Subject: [PATCH 17/30] CI.yml change --- .github/workflows/CI.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index f8a1345..1250a7c 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -13,6 +13,7 @@ jobs: matrix: version: - '1.6' + - '1.10' os: - ubuntu-latest - macOS-latest From a0babfcd06b8c304ee12093580d91e9355a54270 Mon Sep 17 00:00:00 2001 From: dnguyen227 Date: Sun, 21 Dec 2025 11:14:48 -0500 Subject: [PATCH 18/30] removed InfiniteOpt frolm targets (testing) --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 688ca05..43cf1ad 100644 --- a/Project.toml +++ b/Project.toml @@ -33,4 +33,4 @@ Ipopt = "b6b21f68-93f8-5de0-b562-5493be1d77c9" Juniper = "2ddba703-00a4-53a7-87a5-e8b9971dde84" [targets] -test = ["Aqua", "HiGHS", "Test", "Juniper", "Ipopt", "InfiniteOpt","Pkg"] +test = ["Aqua", "HiGHS", "Test", "Juniper", "Ipopt","Pkg"] From b3c87afad90597c5a0e0a44847ecefedb4cd04c6 Mon Sep 17 00:00:00 2001 From: dnguyen227 Date: Sun, 21 Dec 2025 12:44:32 -0500 Subject: [PATCH 19/30] CI to 1.9 and 1.6 --- .github/workflows/CI.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 1250a7c..349735e 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -13,7 +13,7 @@ jobs: matrix: version: - '1.6' - - '1.10' + - '1.9' os: - ubuntu-latest - macOS-latest From 92e2407bfc32612c0b6a24d7595cac575db3648b Mon Sep 17 00:00:00 2001 From: dnguyen227 Date: Mon, 22 Dec 2025 10:14:53 -0500 Subject: [PATCH 20/30] docstring update, renamed all_variables to collect_all_vars (avoid JuMP function) --- ext/InfiniteDisjunctiveProgramming.jl | 2 +- src/cuttingplanes.jl | 10 +++++----- src/datatypes.jl | 2 +- src/hull.jl | 19 +++++++++++++++++++ src/mbm.jl | 2 +- src/utilities.jl | 8 ++++---- .../InfiniteDisjunctiveProgramming.jl | 2 +- 7 files changed, 32 insertions(+), 13 deletions(-) diff --git a/ext/InfiniteDisjunctiveProgramming.jl b/ext/InfiniteDisjunctiveProgramming.jl index 1e0dca3..10b4b88 100644 --- a/ext/InfiniteDisjunctiveProgramming.jl +++ b/ext/InfiniteDisjunctiveProgramming.jl @@ -15,7 +15,7 @@ function DP.InfiniteGDPModel(args...; kwargs...) }(args...; kwargs...) end -function DP.all_variables(model::InfiniteOpt.InfiniteModel) +function DP.collect_all_vars(model::InfiniteOpt.InfiniteModel) vars = collect(JuMP.all_variables(model)) derivs = collect(InfiniteOpt.all_derivatives(model)) return vcat(vars, derivs) diff --git a/src/cuttingplanes.jl b/src/cuttingplanes.jl index 5644a7b..6d2c387 100644 --- a/src/cuttingplanes.jl +++ b/src/cuttingplanes.jl @@ -12,8 +12,8 @@ function reformulate_model( rBM, rBM_ref_map, _ = copy_gdp_model(model) reformulate_model(rBM, BigM(method.M_value)) reformulate_model(SEP, Hull()) - main_to_SEP_map = Dict(v => sep_ref_map[v] for v in all_variables(model)) - main_to_rBM_map = Dict(v => rBM_ref_map[v] for v in all_variables(model)) + main_to_SEP_map = Dict(v => sep_ref_map[v] for v in collect_all_vars(model)) + main_to_rBM_map = Dict(v => rBM_ref_map[v] for v in collect_all_vars(model)) JuMP.set_optimizer(SEP, method.optimizer) JuMP.set_optimizer(rBM, method.optimizer) JuMP.set_silent(rBM) @@ -55,7 +55,7 @@ function _solve_rBM( ) where {M <: JuMP.AbstractModel} T = JuMP.value_type(M) optimize!(rBM, ignore_optimize_hook = true) - rBM_vars = JuMP.all_variables(rBM) + rBM_vars = collect_all_vars(rBM) #Solution to be passed to SEP model. sol = Dict{JuMP.AbstractVariableRef,T}(var => zero(T) for var in rBM_vars) @@ -73,7 +73,7 @@ function _solve_SEP( rBM_to_SEP_map::Dict{<:JuMP.AbstractVariableRef,<:JuMP.AbstractVariableRef} ) where {M <: JuMP.AbstractModel, T <: Number} - SEP_vars = [rBM_to_SEP_map[rBM_var] for rBM_var in JuMP.all_variables(rBM)] + SEP_vars = [rBM_to_SEP_map[rBM_var] for rBM_var in collect_all_vars(rBM)] #Modified objective function for SEP. obj_expr = sum( @@ -98,7 +98,7 @@ function _cutting_planes( rBM_sol::Dict{<:JuMP.AbstractVariableRef,T}, SEP_sol::Dict{<:JuMP.AbstractVariableRef,T}, ) where {M <: JuMP.AbstractModel, T <: Number} - main_vars = JuMP.all_variables(model) + main_vars = collect_all_vars(model) #Cutting plane generation ξ_sep = Dict{JuMP.AbstractVariableRef,T}(var =>zero(T) for var in main_vars) diff --git a/src/datatypes.jl b/src/datatypes.jl index 6ea2e73..6ba1315 100644 --- a/src/datatypes.jl +++ b/src/datatypes.jl @@ -488,7 +488,7 @@ struct PSplit{V <: JuMP.AbstractVariableRef} <: AbstractReformulationMethod function PSplit(n_parts::Int, model::JuMP.AbstractModel) n_parts > 0 || error("Number of partitions must be positive, got $n_parts") - variables = all_variables(model) + variables = collect_all_vars(model) n_vars = length(variables) n_parts = min(n_parts, n_vars) diff --git a/src/hull.jl b/src/hull.jl index 8f8d9f8..779369e 100644 --- a/src/hull.jl +++ b/src/hull.jl @@ -76,6 +76,25 @@ end # CONSTRAINT DISAGGREGATION ################################################################################ # variable +""" + disaggregate_expression( + model::JuMP.AbstractModel, + expr, + bvref::Union{JuMP.AbstractVariableRef, JuMP.GenericAffExpr}, + method::_Hull + ) + +Disaggregate an expression for the Hull reformulation. This function is dispatched +based on the expression type: + +- `vref::JuMP.AbstractVariableRef`: Returns the disaggregated variable if it exists, + otherwise returns the original variable (for binary variables or nested disaggregated variables). +- `aff::JuMP.GenericAffExpr`: Disaggregates each term in the affine expression. +- `quad::JuMP.GenericQuadExpr`: Disaggregates both the affine and quadratic parts of the expression. + +The disaggregated expression is multiplied by the binary indicator variable `bvref` +to enforce the disjunctive constraint. +""" function disaggregate_expression( model::JuMP.AbstractModel, vref::JuMP.AbstractVariableRef, diff --git a/src/mbm.jl b/src/mbm.jl index 58120a8..e9b1f13 100644 --- a/src/mbm.jl +++ b/src/mbm.jl @@ -353,7 +353,7 @@ function _mini_model( var_type = JuMP.variable_ref_type(model) sub_model = _copy_model(model) new_vars = Dict{var_type, var_type}() - for var in JuMP.all_variables(model) + for var in collect_all_vars(model) new_vars[var] = variable_copy(sub_model, var) end for con in [JuMP.constraint_object(con) for con in constraints] diff --git a/src/utilities.jl b/src/utilities.jl index a8a902a..5f7af9c 100644 --- a/src/utilities.jl +++ b/src/utilities.jl @@ -12,12 +12,12 @@ end # ALL VARIABLES ################################################################################ """ - all_variables(model::JuMP.AbstractModel) + collect_all_vars(model::JuMP.AbstractModel) Returns all variable references in the model. -Extend this for model types that have additional ref types (e.g., parameters). +Extend this for model types that have additional ref types (e.g., derivatives). """ -all_variables(model::JuMP.AbstractModel) = collect(JuMP.all_variables(model)) +collect_all_vars(model::JuMP.AbstractModel) = collect(JuMP.all_variables(model)) ################################################################################ # GET CONSTANT @@ -106,7 +106,7 @@ function copy_gdp_data( new_gdp = new_model.ext[:GDP] # Creating maps from old to new model. - var_map = Dict(v => ref_map[v] for v in all_variables(model)) + var_map = Dict(v => ref_map[v] for v in collect_all_vars(model)) lv_map = Dict{LogicalVariableRef{M}, LogicalVariableRef{M}}() lc_map = Dict{LogicalConstraintRef{M}, LogicalConstraintRef{M}}() disj_map = Dict{DisjunctionRef{M}, DisjunctionRef{M}}() diff --git a/test/extensions/InfiniteDisjunctiveProgramming.jl b/test/extensions/InfiniteDisjunctiveProgramming.jl index da9ffa1..63fd1e7 100644 --- a/test/extensions/InfiniteDisjunctiveProgramming.jl +++ b/test/extensions/InfiniteDisjunctiveProgramming.jl @@ -101,7 +101,7 @@ function test_all_variables_infiniteopt() @variable(model, dx, Infinite(t)) @deriv(dx, t) - all_vars = DP.all_variables(model) + all_vars = DP.collect_all_vars(model) @test x in all_vars @test y in all_vars @test dx in all_vars From bfcd3a7b1f92adeccf6bea6d3ef898efb1837bc5 Mon Sep 17 00:00:00 2001 From: dnguyen227 Date: Mon, 22 Dec 2025 10:45:53 -0500 Subject: [PATCH 21/30] additional tests, versoin change --- .github/workflows/CI.yml | 3 +-- Project.toml | 4 ++-- .../InfiniteDisjunctiveProgramming.jl | 20 +++++++++++++++++++ test/runtests.jl | 4 ++-- test/utilities.jl | 16 +++++++++++++++ 5 files changed, 41 insertions(+), 6 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 349735e..d215f67 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -12,8 +12,7 @@ jobs: strategy: matrix: version: - - '1.6' - - '1.9' + - '1.10' os: - ubuntu-latest - macOS-latest diff --git a/Project.toml b/Project.toml index 43cf1ad..dbc3a26 100644 --- a/Project.toml +++ b/Project.toml @@ -17,7 +17,7 @@ InfiniteDisjunctiveProgramming = "InfiniteOpt" Aqua = "0.8" JuMP = "1.18" Reexport = "1" -julia = "1.6" +julia = "1.10" Juniper = "0.9.3" Ipopt = "1.9.0" InfiniteOpt = "0.6" @@ -33,4 +33,4 @@ Ipopt = "b6b21f68-93f8-5de0-b562-5493be1d77c9" Juniper = "2ddba703-00a4-53a7-87a5-e8b9971dde84" [targets] -test = ["Aqua", "HiGHS", "Test", "Juniper", "Ipopt","Pkg"] +test = ["Aqua", "HiGHS", "Test", "Juniper", "Ipopt","Pkg","InfiniteOpt"] diff --git a/test/extensions/InfiniteDisjunctiveProgramming.jl b/test/extensions/InfiniteDisjunctiveProgramming.jl index 63fd1e7..9b1312c 100644 --- a/test/extensions/InfiniteDisjunctiveProgramming.jl +++ b/test/extensions/InfiniteDisjunctiveProgramming.jl @@ -203,6 +203,25 @@ function test_variable_properties_from_expr() @test InfiniteOpt.parameter_refs(var1) == (t, s) end +function test_variable_properties_from_quad_expr() + model = InfiniteGDPModel() + @infinite_parameter(model, t ∈ [0, 1]) + @infinite_parameter(model, s ∈ [0, 2]) + @variable(model, x, Infinite(t)) + @variable(model, y, Infinite(s)) + + # Test inferring prefs from quadratic expression + expr = @expression(model, x^2 + x*y) + props = DP.VariableProperties(expr) + @test props.name == "" + @test props.variable_type isa InfiniteOpt.Infinite + @test Set(props.variable_type.parameter_refs) == Set((t, s)) + var1 = DP.create_variable(model, props) + JuMP.set_name(var1, "quad_inferred_var") + @test JuMP.name(var1) == "quad_inferred_var" + @test Set(InfiniteOpt.parameter_refs(var1)) == Set((t, s)) +end + function test_variable_properties_from_vector() model = InfiniteGDPModel() @infinite_parameter(model, t ∈ [0, 1]) @@ -358,6 +377,7 @@ end test_requires_disaggregation() test_variable_properties_infiniteopt() test_variable_properties_from_expr() + test_variable_properties_from_quad_expr() test_variable_properties_from_vector() test_logical_value() end diff --git a/test/runtests.jl b/test/runtests.jl index 92563dd..bb605b5 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -19,12 +19,12 @@ include("constraints/bigm.jl") include("constraints/psplit.jl") include("constraints/cuttingplanes.jl") include("constraints/hull.jl") -include("constraints/fallback.jl") +include("constraints/fallback.jl") include("constraints/disjunction.jl") include("print.jl") include("solve.jl") -if Base.VERSION >= v"1.9" # extensions require Julia v1.9+ +if Base.VERSION >= v"1" # extensions require Julia v1.10+ import Pkg Pkg.add(url = "https://github.com/infiniteopt/InfiniteOpt.jl", rev = "master") include("extensions/InfiniteDisjunctiveProgramming.jl") diff --git a/test/utilities.jl b/test/utilities.jl index 7472c32..2b87e6e 100644 --- a/test/utilities.jl +++ b/test/utilities.jl @@ -177,6 +177,21 @@ function test_all_variables() @test length(all_vars) == 5 end +function test_collect_all_vars() + model = GDPModel() + @variable(model, x) + @variable(model, y[1:3]) + @variable(model, z, Bin) + + all_vars = DP.collect_all_vars(model) + @test x in all_vars + @test y[1] in all_vars + @test y[2] in all_vars + @test y[3] in all_vars + @test z in all_vars + @test length(all_vars) == 5 +end + function test_get_constant_affine() model = GDPModel() @variable(model, x) @@ -225,6 +240,7 @@ end @testset "Utility Functions" begin test_all_variables() + test_collect_all_vars() test_get_constant_affine() test_get_constant_quadratic() test_get_constant_number() From e301188768e11a195c67b20794c2fa1ecd0bb7a9 Mon Sep 17 00:00:00 2001 From: dnguyen227 Date: Mon, 22 Dec 2025 11:52:38 -0500 Subject: [PATCH 22/30] additional tests --- .../extensions/InfiniteDisjunctiveProgramming.jl | 5 +++++ test/runtests.jl | 6 +++--- test/variables/creation.jl | 16 ++++++++++++++++ 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/test/extensions/InfiniteDisjunctiveProgramming.jl b/test/extensions/InfiniteDisjunctiveProgramming.jl index 9b1312c..34f982d 100644 --- a/test/extensions/InfiniteDisjunctiveProgramming.jl +++ b/test/extensions/InfiniteDisjunctiveProgramming.jl @@ -162,6 +162,11 @@ function test_disaggregate_expression_infiniteopt() result_expr = DP.disaggregate_expression(model, aff_expr, bvref, method) dvref = method.disjunct_variables[x, bvref] @test result_expr == bvref + 2*dvref + + @variable(model, 0 <= y <= 5, Infinite(t)) + aff_not_disagg = @expression(model, 3*y + 1) + result_not_disagg = DP.disaggregate_expression(model, aff_not_disagg, bvref, method) + @test haskey(result_not_disagg.terms, y) end function test_variable_properties_infiniteopt() diff --git a/test/runtests.jl b/test/runtests.jl index bb605b5..bf15b5f 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -4,9 +4,9 @@ using Test include("utilities.jl") # RUN ALL THE TESTS -include("aqua.jl") # temporary ignore until compat is finalized -include("model.jl") -include("jump.jl") +# include("aqua.jl") # temporary ignore until compat is finalized +# include("model.jl") +# include("jump.jl") include("variables/query.jl") include("variables/logical.jl") include("variables/creation.jl") diff --git a/test/variables/creation.jl b/test/variables/creation.jl index 5645fec..b8ffa4f 100644 --- a/test/variables/creation.jl +++ b/test/variables/creation.jl @@ -187,10 +187,26 @@ function test_variable_properties_from_expr() @test var in JuMP.all_variables(model) end +function test_create_variable_with_set() + model = Model() + + info = DP._free_variable_info() + props = DP.VariableProperties(info, "test_var", MOI.ZeroOne(), nothing) + + @test props.set !== nothing + + var = DP.create_variable(model, props) + + @test var !== nothing + @test JuMP.name(var) == "test_var" + @test var in JuMP.all_variables(model) +end + @testset "Variable Creation" begin test_VariableProperties_constructor() test_make_variable_object() test_create_variable() + test_create_variable_with_set() test_complete_workflow() test_variable_copy() test_get_variable_info() From d62c7c6ddf40fd63a509ef4bd9a15e57dcabcca1 Mon Sep 17 00:00:00 2001 From: dnguyen227 <82475321+dnguyen227@users.noreply.github.com> Date: Mon, 22 Dec 2025 12:23:41 -0500 Subject: [PATCH 23/30] Update runtests.jl --- test/runtests.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/runtests.jl b/test/runtests.jl index bf15b5f..7875861 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -24,7 +24,7 @@ include("constraints/disjunction.jl") include("print.jl") include("solve.jl") -if Base.VERSION >= v"1" # extensions require Julia v1.10+ +if Base.VERSION >= v"1.10" # extensions require Julia v1.10+ import Pkg Pkg.add(url = "https://github.com/infiniteopt/InfiniteOpt.jl", rev = "master") include("extensions/InfiniteDisjunctiveProgramming.jl") From 9314bd75a2b4b3100fb74af91cd8e4455e73a69d Mon Sep 17 00:00:00 2001 From: dnguyen227 <82475321+dnguyen227@users.noreply.github.com> Date: Mon, 22 Dec 2025 12:24:56 -0500 Subject: [PATCH 24/30] Uncomment includes and update package management --- test/runtests.jl | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/test/runtests.jl b/test/runtests.jl index 7875861..ed7e401 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -4,9 +4,9 @@ using Test include("utilities.jl") # RUN ALL THE TESTS -# include("aqua.jl") # temporary ignore until compat is finalized -# include("model.jl") -# include("jump.jl") +include("aqua.jl") # temporary ignore until compat is finalized +include("model.jl") +include("jump.jl") include("variables/query.jl") include("variables/logical.jl") include("variables/creation.jl") @@ -24,8 +24,6 @@ include("constraints/disjunction.jl") include("print.jl") include("solve.jl") -if Base.VERSION >= v"1.10" # extensions require Julia v1.10+ - import Pkg - Pkg.add(url = "https://github.com/infiniteopt/InfiniteOpt.jl", rev = "master") - include("extensions/InfiniteDisjunctiveProgramming.jl") -end +import Pkg +Pkg.add(url = "https://github.com/infiniteopt/InfiniteOpt.jl", rev = "master") +include("extensions/InfiniteDisjunctiveProgramming.jl") From a99d183ecce479794555e06e415b684aff535213 Mon Sep 17 00:00:00 2001 From: dnguyen227 Date: Mon, 22 Dec 2025 12:46:25 -0500 Subject: [PATCH 25/30] additional tests --- .../InfiniteDisjunctiveProgramming.jl | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/test/extensions/InfiniteDisjunctiveProgramming.jl b/test/extensions/InfiniteDisjunctiveProgramming.jl index 34f982d..07f446b 100644 --- a/test/extensions/InfiniteDisjunctiveProgramming.jl +++ b/test/extensions/InfiniteDisjunctiveProgramming.jl @@ -196,7 +196,6 @@ function test_variable_properties_from_expr() @variable(model, x, Infinite(t)) @variable(model, y, Infinite(s)) - # Test inferring prefs from single expression expr = @expression(model, 2*x + y) props = DP.VariableProperties(expr) @test props.name == "" @@ -215,7 +214,6 @@ function test_variable_properties_from_quad_expr() @variable(model, x, Infinite(t)) @variable(model, y, Infinite(s)) - # Test inferring prefs from quadratic expression expr = @expression(model, x^2 + x*y) props = DP.VariableProperties(expr) @test props.name == "" @@ -234,7 +232,6 @@ function test_variable_properties_from_vector() @variable(model, x, Infinite(t)) @variable(model, y, Infinite(s)) - # Test inferring prefs from vector of expressions exprs = [@expression(model, x + 1), @expression(model, y + 2)] props = DP.VariableProperties(exprs) var1 = DP.create_variable(model, props) @@ -271,15 +268,18 @@ function test_add_constraint_single_logical_error() @infinite_parameter(model, t ∈ [0, 1]) @variable(model, y, InfiniteLogical(t)) - @test_throws ErrorException @constraint(model, y in MOI.EqualTo(true)) + c = JuMP.ScalarConstraint(y, MOI.EqualTo(true)) + @test_throws ErrorException JuMP.add_constraint(model, c, "") end function test_add_constraint_affine_logical_error() model = InfiniteGDPModel() @infinite_parameter(model, t ∈ [0, 1]) @variable(model, y[1:2], InfiniteLogical(t)) - - @test_throws ErrorException @constraint(model, y[1] + y[2] == 1) + + aff_expr = 1.0 * y[1] + 1.0 * y[2] + c = JuMP.ScalarConstraint(aff_expr, MOI.EqualTo(1.0)) + @test_throws ErrorException JuMP.add_constraint(model, c, "") end function test_add_constraint_quad_logical_error() @@ -287,7 +287,9 @@ function test_add_constraint_quad_logical_error() @infinite_parameter(model, t ∈ [0, 1]) @variable(model, y[1:2], InfiniteLogical(t)) - @test_throws ErrorException @constraint(model, y[1] * y[2] == 1) + quad_expr = 1.0 * y[1] * y[2] + c = JuMP.ScalarConstraint(quad_expr, MOI.EqualTo(1.0)) + @test_throws ErrorException JuMP.add_constraint(model, c, "") end function test_logical_value() @@ -384,12 +386,15 @@ end test_variable_properties_from_expr() test_variable_properties_from_quad_expr() test_variable_properties_from_vector() - test_logical_value() end @testset "Constraints" begin test_add_cardinality_constraint() test_add_logical_constraint() + end + + @testset "JuMP Overloads" begin + test_logical_value() test_add_constraint_single_logical_error() test_add_constraint_affine_logical_error() test_add_constraint_quad_logical_error() From ecc322c194f27035d6712d468a987843fa460220 Mon Sep 17 00:00:00 2001 From: dnguyen227 Date: Mon, 22 Dec 2025 12:52:37 -0500 Subject: [PATCH 26/30] . --- test/runtests.jl | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/test/runtests.jl b/test/runtests.jl index bf15b5f..9325414 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -4,9 +4,9 @@ using Test include("utilities.jl") # RUN ALL THE TESTS -# include("aqua.jl") # temporary ignore until compat is finalized -# include("model.jl") -# include("jump.jl") +include("aqua.jl") +include("model.jl") +include("jump.jl") include("variables/query.jl") include("variables/logical.jl") include("variables/creation.jl") @@ -24,8 +24,8 @@ include("constraints/disjunction.jl") include("print.jl") include("solve.jl") -if Base.VERSION >= v"1" # extensions require Julia v1.10+ - import Pkg - Pkg.add(url = "https://github.com/infiniteopt/InfiniteOpt.jl", rev = "master") - include("extensions/InfiniteDisjunctiveProgramming.jl") -end + +import Pkg +Pkg.add(url = "https://github.com/infiniteopt/InfiniteOpt.jl", rev = "master") +include("extensions/InfiniteDisjunctiveProgramming.jl") + From 9bb4249e66fb58ad6fdcaf39f9cc07c8622d156b Mon Sep 17 00:00:00 2001 From: dnguyen227 Date: Sun, 28 Dec 2025 12:39:12 -0500 Subject: [PATCH 27/30] simplify runtest.jl --- test/runtests.jl | 4 ---- 1 file changed, 4 deletions(-) diff --git a/test/runtests.jl b/test/runtests.jl index 9325414..06e8813 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -23,9 +23,5 @@ include("constraints/fallback.jl") include("constraints/disjunction.jl") include("print.jl") include("solve.jl") - - -import Pkg -Pkg.add(url = "https://github.com/infiniteopt/InfiniteOpt.jl", rev = "master") include("extensions/InfiniteDisjunctiveProgramming.jl") From 3df02edaecf54489ec44768a9cb9ae3a74a711f9 Mon Sep 17 00:00:00 2001 From: dnguyen227 Date: Sat, 3 Jan 2026 14:25:51 -0500 Subject: [PATCH 28/30] edited dependences, refactored InfiniteDisjunctiveProgramming.jl --- Project.toml | 5 +--- ext/InfiniteDisjunctiveProgramming.jl | 42 +++++++++++++++------------ src/utilities.jl | 2 +- 3 files changed, 25 insertions(+), 24 deletions(-) diff --git a/Project.toml b/Project.toml index dbc3a26..edd639a 100644 --- a/Project.toml +++ b/Project.toml @@ -21,16 +21,13 @@ julia = "1.10" Juniper = "0.9.3" Ipopt = "1.9.0" InfiniteOpt = "0.6" -Pkg ="1" [extras] Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" HiGHS = "87dc4568-4c63-4d18-b0c0-bb2238e4078b" -InfiniteOpt = "20393b10-9daf-11e9-18c9-8db751c92c57" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" -Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" Ipopt = "b6b21f68-93f8-5de0-b562-5493be1d77c9" Juniper = "2ddba703-00a4-53a7-87a5-e8b9971dde84" [targets] -test = ["Aqua", "HiGHS", "Test", "Juniper", "Ipopt","Pkg","InfiniteOpt"] +test = ["Aqua", "HiGHS", "Test", "Juniper", "Ipopt","InfiniteOpt"] diff --git a/ext/InfiniteDisjunctiveProgramming.jl b/ext/InfiniteDisjunctiveProgramming.jl index 10b4b88..96cd5e3 100644 --- a/ext/InfiniteDisjunctiveProgramming.jl +++ b/ext/InfiniteDisjunctiveProgramming.jl @@ -16,9 +16,9 @@ function DP.InfiniteGDPModel(args...; kwargs...) end function DP.collect_all_vars(model::InfiniteOpt.InfiniteModel) - vars = collect(JuMP.all_variables(model)) - derivs = collect(InfiniteOpt.all_derivatives(model)) - return vcat(vars, derivs) + vars = JuMP.all_variables(model) + derivs = InfiniteOpt.all_derivatives(model) + return append!(vars, derivs) end ################################################################################ @@ -26,19 +26,13 @@ end ################################################################################ DP.InfiniteLogical(prefs...) = DP.Logical(InfiniteOpt.Infinite(prefs...)) -function _is_parameter(vref::InfiniteOpt.GeneralVariableRef) - dref = InfiniteOpt.dispatch_variable_ref(vref) - if typeof(dref) <: Union{ - InfiniteOpt.DependentParameterRef, - InfiniteOpt.IndependentParameterRef, - InfiniteOpt.ParameterFunctionRef, - InfiniteOpt.FiniteParameterRef - } - return true - else - return false - end -end +_is_parameter(vref::InfiniteOpt.GeneralVariableRef) = + _is_parameter(InfiniteOpt.dispatch_variable_ref(vref)) +_is_parameter(::InfiniteOpt.DependentParameterRef) = true +_is_parameter(::InfiniteOpt.IndependentParameterRef) = true +_is_parameter(::InfiniteOpt.ParameterFunctionRef) = true +_is_parameter(::InfiniteOpt.FiniteParameterRef) = true +_is_parameter(::Any) = false function DP.requires_disaggregation(vref::InfiniteOpt.GeneralVariableRef) return !_is_parameter(vref) @@ -54,21 +48,31 @@ function DP.VariableProperties(vref::InfiniteOpt.GeneralVariableRef) end # Extract parameter refs from expression and return VariableProperties with Infinite type -function DP.VariableProperties(expr::JuMP.GenericAffExpr{C, InfiniteOpt.GeneralVariableRef}) where C +function DP.VariableProperties( + expr::JuMP.GenericAffExpr{C, InfiniteOpt.GeneralVariableRef} +) where C prefs = InfiniteOpt.parameter_refs(expr) info = DP._free_variable_info() var_type = !isempty(prefs) ? InfiniteOpt.Infinite(prefs...) : nothing return DP.VariableProperties(info, "", nothing, var_type) end -function DP.VariableProperties(expr::JuMP.GenericQuadExpr{C, InfiniteOpt.GeneralVariableRef}) where C +function DP.VariableProperties( + expr::JuMP.GenericQuadExpr{C, InfiniteOpt.GeneralVariableRef} +) where C prefs = InfiniteOpt.parameter_refs(expr) info = DP._free_variable_info() var_type = !isempty(prefs) ? InfiniteOpt.Infinite(prefs...) : nothing return DP.VariableProperties(info, "", nothing, var_type) end -function DP.VariableProperties(exprs::Vector{<:Union{InfiniteOpt.GeneralVariableRef, JuMP.GenericAffExpr{<:Any, InfiniteOpt.GeneralVariableRef}, JuMP.GenericQuadExpr{<:Any, InfiniteOpt.GeneralVariableRef}}}) +function DP.VariableProperties( + exprs::Vector{<:Union{ + InfiniteOpt.GeneralVariableRef, + JuMP.GenericAffExpr{<:Any, InfiniteOpt.GeneralVariableRef}, + JuMP.GenericQuadExpr{<:Any, InfiniteOpt.GeneralVariableRef} + }} +) all_prefs = Set{InfiniteOpt.GeneralVariableRef}() for expr in exprs for pref in InfiniteOpt.parameter_refs(expr) diff --git a/src/utilities.jl b/src/utilities.jl index 5f7af9c..0f211b4 100644 --- a/src/utilities.jl +++ b/src/utilities.jl @@ -17,7 +17,7 @@ end Returns all variable references in the model. Extend this for model types that have additional ref types (e.g., derivatives). """ -collect_all_vars(model::JuMP.AbstractModel) = collect(JuMP.all_variables(model)) +collect_all_vars(model::JuMP.AbstractModel) = JuMP.all_variables(model) ################################################################################ # GET CONSTANT From cbcb9367d7e1b4042bbc5af9eaf75b1139b528ea Mon Sep 17 00:00:00 2001 From: dnguyen227 Date: Sat, 3 Jan 2026 20:53:46 -0500 Subject: [PATCH 29/30] 80 character edit --- ext/InfiniteDisjunctiveProgramming.jl | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/ext/InfiniteDisjunctiveProgramming.jl b/ext/InfiniteDisjunctiveProgramming.jl index 96cd5e3..72adb1e 100644 --- a/ext/InfiniteDisjunctiveProgramming.jl +++ b/ext/InfiniteDisjunctiveProgramming.jl @@ -118,7 +118,9 @@ end function JuMP.add_constraint( model::M, - c::JuMP.ScalarConstraint{JuMP.GenericAffExpr{C, DP.LogicalVariableRef{M}}, S}, + c::JuMP.ScalarConstraint{ + JuMP.GenericAffExpr{C, DP.LogicalVariableRef{M}}, S + }, name::String = "" ) where {M <: InfiniteOpt.InfiniteModel, S, C} error("Cannot add, subtract, or multiply with logical variables.") @@ -126,7 +128,9 @@ end function JuMP.add_constraint( model::M, - c::JuMP.ScalarConstraint{JuMP.GenericQuadExpr{C, DP.LogicalVariableRef{M}}, S}, + c::JuMP.ScalarConstraint{ + JuMP.GenericQuadExpr{C, DP.LogicalVariableRef{M}}, S + }, name::String = "" ) where {M <: InfiniteOpt.InfiniteModel, S, C} error("Cannot add, subtract, or multiply with logical variables.") @@ -135,7 +139,9 @@ end ################################################################################ # METHODS ################################################################################ -function DP.get_constant(expr::JuMP.GenericAffExpr{T, InfiniteOpt.GeneralVariableRef}) where {T} +function DP.get_constant( + expr::JuMP.GenericAffExpr{T, InfiniteOpt.GeneralVariableRef} +) where {T} constant = JuMP.constant(expr) param_expr = zero(typeof(expr)) for (var, coeff) in expr.terms @@ -172,12 +178,12 @@ end # ERROR MESSAGES ################################################################################ function DP.reformulate_model(::InfiniteOpt.InfiniteModel, ::DP.MBM) - error("The `MBM` reformulation method is not supported for `InfiniteModel`. " * + error("The `MBM` method is not supported for `InfiniteModel`." * "Please use `BigM`, `Hull`, `Indicator`, or `PSplit` instead.") end function DP.reformulate_model(::InfiniteOpt.InfiniteModel, ::DP.cutting_planes) - error("The `cutting_planes` reformulation method is not supported for `InfiniteModel`. " * + error("The `cutting_planes` method is not supported for `InfiniteModel`." * "Please use `BigM`, `Hull`, `Indicator`, or `PSplit` instead.") end From 24008c8711a8fc00145e0e7b6b9eff8677a507ee Mon Sep 17 00:00:00 2001 From: dnguyen227 Date: Mon, 5 Jan 2026 18:18:37 -0500 Subject: [PATCH 30/30] VariableProperties to support nonlinear expressions and update tests --- ext/InfiniteDisjunctiveProgramming.jl | 22 ++++++------- .../InfiniteDisjunctiveProgramming.jl | 32 +++++++++++++++++-- 2 files changed, 38 insertions(+), 16 deletions(-) diff --git a/ext/InfiniteDisjunctiveProgramming.jl b/ext/InfiniteDisjunctiveProgramming.jl index 72adb1e..fad26ba 100644 --- a/ext/InfiniteDisjunctiveProgramming.jl +++ b/ext/InfiniteDisjunctiveProgramming.jl @@ -49,16 +49,11 @@ end # Extract parameter refs from expression and return VariableProperties with Infinite type function DP.VariableProperties( - expr::JuMP.GenericAffExpr{C, InfiniteOpt.GeneralVariableRef} -) where C - prefs = InfiniteOpt.parameter_refs(expr) - info = DP._free_variable_info() - var_type = !isempty(prefs) ? InfiniteOpt.Infinite(prefs...) : nothing - return DP.VariableProperties(info, "", nothing, var_type) -end - -function DP.VariableProperties( - expr::JuMP.GenericQuadExpr{C, InfiniteOpt.GeneralVariableRef} + expr::Union{ + JuMP.GenericAffExpr{C, InfiniteOpt.GeneralVariableRef}, + JuMP.GenericQuadExpr{C, InfiniteOpt.GeneralVariableRef}, + JuMP.GenericNonlinearExpr{InfiniteOpt.GeneralVariableRef} + } ) where C prefs = InfiniteOpt.parameter_refs(expr) info = DP._free_variable_info() @@ -68,9 +63,10 @@ end function DP.VariableProperties( exprs::Vector{<:Union{ - InfiniteOpt.GeneralVariableRef, - JuMP.GenericAffExpr{<:Any, InfiniteOpt.GeneralVariableRef}, - JuMP.GenericQuadExpr{<:Any, InfiniteOpt.GeneralVariableRef} + InfiniteOpt.GeneralVariableRef, + JuMP.GenericAffExpr{<:Any, InfiniteOpt.GeneralVariableRef}, + JuMP.GenericQuadExpr{<:Any, InfiniteOpt.GeneralVariableRef}, + JuMP.GenericNonlinearExpr{InfiniteOpt.GeneralVariableRef} }} ) all_prefs = Set{InfiniteOpt.GeneralVariableRef}() diff --git a/test/extensions/InfiniteDisjunctiveProgramming.jl b/test/extensions/InfiniteDisjunctiveProgramming.jl index 07f446b..b206d8e 100644 --- a/test/extensions/InfiniteDisjunctiveProgramming.jl +++ b/test/extensions/InfiniteDisjunctiveProgramming.jl @@ -225,20 +225,45 @@ function test_variable_properties_from_quad_expr() @test Set(InfiniteOpt.parameter_refs(var1)) == Set((t, s)) end +function test_variable_properties_from_nonlinear_expr() + model = InfiniteGDPModel() + @infinite_parameter(model, t ∈ [0, 1]) + @infinite_parameter(model, s ∈ [0, 2]) + @variable(model, x, Infinite(t)) + @variable(model, y, Infinite(s)) + + expr = @expression(model, exp(x) + sin(y)) + props = DP.VariableProperties(expr) + @test props.name == "" + @test props.variable_type isa InfiniteOpt.Infinite + @test Set(props.variable_type.parameter_refs) == Set((t, s)) + var1 = DP.create_variable(model, props) + JuMP.set_name(var1, "nl_inferred_var") + @test JuMP.name(var1) == "nl_inferred_var" + @test Set(InfiniteOpt.parameter_refs(var1)) == Set((t, s)) +end + function test_variable_properties_from_vector() model = InfiniteGDPModel() @infinite_parameter(model, t ∈ [0, 1]) @infinite_parameter(model, s ∈ [0, 2]) + @infinite_parameter(model, r ∈ [0, 3]) @variable(model, x, Infinite(t)) @variable(model, y, Infinite(s)) - - exprs = [@expression(model, x + 1), @expression(model, y + 2)] + @variable(model, z, Infinite(r)) + + exprs = [ + @expression(model, x + 1), + @expression(model, y + 2), + @expression(model, exp(z)) + ] props = DP.VariableProperties(exprs) var1 = DP.create_variable(model, props) JuMP.set_name(var1, "vector_var") @test JuMP.name(var1) == "vector_var" prefs = InfiniteOpt.parameter_refs(var1) - @test length(prefs) == 2 + @test length(prefs) == 3 + @test Set(prefs) == Set((t, s, r)) end function test_add_cardinality_constraint() @@ -385,6 +410,7 @@ end test_variable_properties_infiniteopt() test_variable_properties_from_expr() test_variable_properties_from_quad_expr() + test_variable_properties_from_nonlinear_expr() test_variable_properties_from_vector() end