Skip to content

Commit e9e7222

Browse files
Add InfiniteOpt as an Extension (#130)
* Add InfiniteOpt as an extension * Update tests * add pkg * fix extension versioning * bug fix * merged with main WIP * wip * hull and bigm working version * psplit working * Tests added. * julia version * version change * update CI * added docstrings * docstring for create_blank_variable * version revert, dispatch on VariableProperties to create free variables * CI.yml change * removed InfiniteOpt frolm targets (testing) * CI to 1.9 and 1.6 * docstring update, renamed all_variables to collect_all_vars (avoid JuMP function) * additional tests, versoin change * additional tests * Update runtests.jl * Uncomment includes and update package management * additional tests * . * simplify runtest.jl * edited dependences, refactored InfiniteDisjunctiveProgramming.jl * 80 character edit * VariableProperties to support nonlinear expressions and update tests --------- Co-authored-by: pulsipher <[email protected]>
1 parent d3c218c commit e9e7222

File tree

17 files changed

+1080
-109
lines changed

17 files changed

+1080
-109
lines changed

.github/workflows/CI.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ jobs:
1212
strategy:
1313
matrix:
1414
version:
15-
- '1.6'
15+
- '1.10'
1616
os:
1717
- ubuntu-latest
1818
- macOS-latest

Project.toml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,20 @@ version = "0.5.0"
77
JuMP = "4076af6c-e467-56ae-b986-b466b2749572"
88
Reexport = "189a3867-3050-52da-a836-e630ba90ab69"
99

10+
[weakdeps]
11+
InfiniteOpt = "20393b10-9daf-11e9-18c9-8db751c92c57"
12+
13+
[extensions]
14+
InfiniteDisjunctiveProgramming = "InfiniteOpt"
15+
1016
[compat]
1117
Aqua = "0.8"
1218
JuMP = "1.18"
1319
Reexport = "1"
14-
julia = "1.6"
20+
julia = "1.10"
1521
Juniper = "0.9.3"
1622
Ipopt = "1.9.0"
23+
InfiniteOpt = "0.6"
1724

1825
[extras]
1926
Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595"
@@ -23,4 +30,4 @@ Ipopt = "b6b21f68-93f8-5de0-b562-5493be1d77c9"
2330
Juniper = "2ddba703-00a4-53a7-87a5-e8b9971dde84"
2431

2532
[targets]
26-
test = ["Aqua", "HiGHS", "Test", "Juniper", "Ipopt"]
33+
test = ["Aqua", "HiGHS", "Test", "Juniper", "Ipopt","InfiniteOpt"]
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
module InfiniteDisjunctiveProgramming
2+
3+
import JuMP.MOI as _MOI
4+
import InfiniteOpt, JuMP
5+
import DisjunctiveProgramming as DP
6+
7+
################################################################################
8+
# MODEL
9+
################################################################################
10+
function DP.InfiniteGDPModel(args...; kwargs...)
11+
return DP.GDPModel{
12+
InfiniteOpt.InfiniteModel,
13+
InfiniteOpt.GeneralVariableRef,
14+
InfiniteOpt.InfOptConstraintRef
15+
}(args...; kwargs...)
16+
end
17+
18+
function DP.collect_all_vars(model::InfiniteOpt.InfiniteModel)
19+
vars = JuMP.all_variables(model)
20+
derivs = InfiniteOpt.all_derivatives(model)
21+
return append!(vars, derivs)
22+
end
23+
24+
################################################################################
25+
# VARIABLES
26+
################################################################################
27+
DP.InfiniteLogical(prefs...) = DP.Logical(InfiniteOpt.Infinite(prefs...))
28+
29+
_is_parameter(vref::InfiniteOpt.GeneralVariableRef) =
30+
_is_parameter(InfiniteOpt.dispatch_variable_ref(vref))
31+
_is_parameter(::InfiniteOpt.DependentParameterRef) = true
32+
_is_parameter(::InfiniteOpt.IndependentParameterRef) = true
33+
_is_parameter(::InfiniteOpt.ParameterFunctionRef) = true
34+
_is_parameter(::InfiniteOpt.FiniteParameterRef) = true
35+
_is_parameter(::Any) = false
36+
37+
function DP.requires_disaggregation(vref::InfiniteOpt.GeneralVariableRef)
38+
return !_is_parameter(vref)
39+
end
40+
41+
function DP.VariableProperties(vref::InfiniteOpt.GeneralVariableRef)
42+
info = DP.get_variable_info(vref)
43+
name = JuMP.name(vref)
44+
set = nothing
45+
prefs = InfiniteOpt.parameter_refs(vref)
46+
var_type = !isempty(prefs) ? InfiniteOpt.Infinite(prefs...) : nothing
47+
return DP.VariableProperties(info, name, set, var_type)
48+
end
49+
50+
# Extract parameter refs from expression and return VariableProperties with Infinite type
51+
function DP.VariableProperties(
52+
expr::Union{
53+
JuMP.GenericAffExpr{C, InfiniteOpt.GeneralVariableRef},
54+
JuMP.GenericQuadExpr{C, InfiniteOpt.GeneralVariableRef},
55+
JuMP.GenericNonlinearExpr{InfiniteOpt.GeneralVariableRef}
56+
}
57+
) where C
58+
prefs = InfiniteOpt.parameter_refs(expr)
59+
info = DP._free_variable_info()
60+
var_type = !isempty(prefs) ? InfiniteOpt.Infinite(prefs...) : nothing
61+
return DP.VariableProperties(info, "", nothing, var_type)
62+
end
63+
64+
function DP.VariableProperties(
65+
exprs::Vector{<:Union{
66+
InfiniteOpt.GeneralVariableRef,
67+
JuMP.GenericAffExpr{<:Any, InfiniteOpt.GeneralVariableRef},
68+
JuMP.GenericQuadExpr{<:Any, InfiniteOpt.GeneralVariableRef},
69+
JuMP.GenericNonlinearExpr{InfiniteOpt.GeneralVariableRef}
70+
}}
71+
)
72+
all_prefs = Set{InfiniteOpt.GeneralVariableRef}()
73+
for expr in exprs
74+
for pref in InfiniteOpt.parameter_refs(expr)
75+
push!(all_prefs, pref)
76+
end
77+
end
78+
prefs = Tuple(all_prefs)
79+
info = DP._free_variable_info()
80+
var_type = !isempty(prefs) ? InfiniteOpt.Infinite(prefs...) : nothing
81+
return DP.VariableProperties(info, "", nothing, var_type)
82+
end
83+
84+
function JuMP.value(vref::DP.LogicalVariableRef{InfiniteOpt.InfiniteModel})
85+
return JuMP.value(DP.binary_variable(vref)) .>= 0.5
86+
end
87+
88+
################################################################################
89+
# CONSTRAINTS
90+
################################################################################
91+
function JuMP.add_constraint(
92+
model::InfiniteOpt.InfiniteModel,
93+
c::JuMP.VectorConstraint{F, S},
94+
name::String = ""
95+
) where {F, S <: DP.AbstractCardinalitySet}
96+
return DP._add_cardinality_constraint(model, c, name)
97+
end
98+
99+
function JuMP.add_constraint(
100+
model::M,
101+
c::JuMP.ScalarConstraint{DP._LogicalExpr{M}, S},
102+
name::String = ""
103+
) where {S, M <: InfiniteOpt.InfiniteModel}
104+
return DP._add_logical_constraint(model, c, name)
105+
end
106+
107+
function JuMP.add_constraint(
108+
model::M,
109+
c::JuMP.ScalarConstraint{DP.LogicalVariableRef{M}, S},
110+
name::String = ""
111+
) where {M <: InfiniteOpt.InfiniteModel, S}
112+
error("Cannot define constraint on single logical variable, use `fix` instead.")
113+
end
114+
115+
function JuMP.add_constraint(
116+
model::M,
117+
c::JuMP.ScalarConstraint{
118+
JuMP.GenericAffExpr{C, DP.LogicalVariableRef{M}}, S
119+
},
120+
name::String = ""
121+
) where {M <: InfiniteOpt.InfiniteModel, S, C}
122+
error("Cannot add, subtract, or multiply with logical variables.")
123+
end
124+
125+
function JuMP.add_constraint(
126+
model::M,
127+
c::JuMP.ScalarConstraint{
128+
JuMP.GenericQuadExpr{C, DP.LogicalVariableRef{M}}, S
129+
},
130+
name::String = ""
131+
) where {M <: InfiniteOpt.InfiniteModel, S, C}
132+
error("Cannot add, subtract, or multiply with logical variables.")
133+
end
134+
135+
################################################################################
136+
# METHODS
137+
################################################################################
138+
function DP.get_constant(
139+
expr::JuMP.GenericAffExpr{T, InfiniteOpt.GeneralVariableRef}
140+
) where {T}
141+
constant = JuMP.constant(expr)
142+
param_expr = zero(typeof(expr))
143+
for (var, coeff) in expr.terms
144+
if _is_parameter(var)
145+
JuMP.add_to_expression!(param_expr, coeff, var)
146+
end
147+
end
148+
return constant + param_expr
149+
end
150+
151+
function DP.disaggregate_expression(
152+
model::M,
153+
aff::JuMP.GenericAffExpr,
154+
bvref::Union{JuMP.AbstractVariableRef, JuMP.GenericAffExpr},
155+
method::DP._Hull
156+
) where {M <: InfiniteOpt.InfiniteModel}
157+
terms = Any[aff.constant * bvref]
158+
for (vref, coeff) in aff.terms
159+
if JuMP.is_binary(vref)
160+
push!(terms, coeff * vref)
161+
elseif vref isa InfiniteOpt.GeneralVariableRef && _is_parameter(vref)
162+
push!(terms, coeff * vref * bvref)
163+
elseif !haskey(method.disjunct_variables, (vref, bvref))
164+
push!(terms, coeff * vref)
165+
else
166+
dvref = method.disjunct_variables[vref, bvref]
167+
push!(terms, coeff * dvref)
168+
end
169+
end
170+
return JuMP.@expression(model, sum(terms))
171+
end
172+
173+
################################################################################
174+
# ERROR MESSAGES
175+
################################################################################
176+
function DP.reformulate_model(::InfiniteOpt.InfiniteModel, ::DP.MBM)
177+
error("The `MBM` method is not supported for `InfiniteModel`." *
178+
"Please use `BigM`, `Hull`, `Indicator`, or `PSplit` instead.")
179+
end
180+
181+
function DP.reformulate_model(::InfiniteOpt.InfiniteModel, ::DP.cutting_planes)
182+
error("The `cutting_planes` method is not supported for `InfiniteModel`." *
183+
"Please use `BigM`, `Hull`, `Indicator`, or `PSplit` instead.")
184+
end
185+
186+
end
187+

src/DisjunctiveProgramming.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ include("hull.jl")
2525
include("mbm.jl")
2626
include("indicator.jl")
2727
include("print.jl")
28+
include("extension_api.jl")
2829
include("utilities.jl")
2930
include("psplit.jl")
3031

src/cuttingplanes.jl

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ function reformulate_model(
1212
rBM, rBM_ref_map, _ = copy_gdp_model(model)
1313
reformulate_model(rBM, BigM(method.M_value))
1414
reformulate_model(SEP, Hull())
15-
main_to_SEP_map = Dict(v => sep_ref_map[v] for v in all_variables(model))
16-
main_to_rBM_map = Dict(v => rBM_ref_map[v] for v in all_variables(model))
15+
main_to_SEP_map = Dict(v => sep_ref_map[v] for v in collect_all_vars(model))
16+
main_to_rBM_map = Dict(v => rBM_ref_map[v] for v in collect_all_vars(model))
1717
JuMP.set_optimizer(SEP, method.optimizer)
1818
JuMP.set_optimizer(rBM, method.optimizer)
1919
JuMP.set_silent(rBM)
@@ -55,7 +55,7 @@ function _solve_rBM(
5555
) where {M <: JuMP.AbstractModel}
5656
T = JuMP.value_type(M)
5757
optimize!(rBM, ignore_optimize_hook = true)
58-
rBM_vars = JuMP.all_variables(rBM)
58+
rBM_vars = collect_all_vars(rBM)
5959

6060
#Solution to be passed to SEP model.
6161
sol = Dict{JuMP.AbstractVariableRef,T}(var => zero(T) for var in rBM_vars)
@@ -73,7 +73,7 @@ function _solve_SEP(
7373
rBM_to_SEP_map::Dict{<:JuMP.AbstractVariableRef,<:JuMP.AbstractVariableRef}
7474
) where {M <: JuMP.AbstractModel, T <: Number}
7575

76-
SEP_vars = [rBM_to_SEP_map[rBM_var] for rBM_var in JuMP.all_variables(rBM)]
76+
SEP_vars = [rBM_to_SEP_map[rBM_var] for rBM_var in collect_all_vars(rBM)]
7777

7878
#Modified objective function for SEP.
7979
obj_expr = sum(
@@ -98,7 +98,7 @@ function _cutting_planes(
9898
rBM_sol::Dict{<:JuMP.AbstractVariableRef,T},
9999
SEP_sol::Dict{<:JuMP.AbstractVariableRef,T},
100100
) where {M <: JuMP.AbstractModel, T <: Number}
101-
main_vars = JuMP.all_variables(model)
101+
main_vars = collect_all_vars(model)
102102

103103
#Cutting plane generation
104104
ξ_sep = Dict{JuMP.AbstractVariableRef,T}(var =>zero(T) for var in main_vars)

src/datatypes.jl

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -488,7 +488,7 @@ struct PSplit{V <: JuMP.AbstractVariableRef} <: AbstractReformulationMethod
488488
function PSplit(n_parts::Int, model::JuMP.AbstractModel)
489489
n_parts > 0 || error("Number of partitions must be
490490
positive, got $n_parts")
491-
variables = collect(JuMP.all_variables(model))
491+
variables = collect_all_vars(model)
492492
n_vars = length(variables)
493493

494494
n_parts = min(n_parts, n_vars)
@@ -620,19 +620,32 @@ mutable struct VariableProperties{L, U, F, S, SET, T}
620620
end
621621

622622
function VariableProperties(vref::JuMP.GenericVariableRef{T}) where T
623-
info = JuMP.VariableInfo(
624-
JuMP.has_lower_bound(vref),
625-
JuMP.has_lower_bound(vref) ? JuMP.lower_bound(vref) : zero(T),
626-
JuMP.has_upper_bound(vref),
627-
JuMP.has_upper_bound(vref) ? JuMP.upper_bound(vref) : zero(T),
628-
JuMP.is_fixed(vref),
629-
JuMP.is_fixed(vref) ? JuMP.fix_value(vref) : zero(T),
630-
!isnothing(JuMP.start_value(vref)),
631-
JuMP.start_value(vref),
632-
JuMP.is_binary(vref),
633-
JuMP.is_integer(vref)
634-
)
623+
info = get_variable_info(vref)
635624
name = JuMP.name(vref)
636625
set = JuMP.is_variable_in_set(vref) ? JuMP.moi_set(JuMP.constraint_object(JuMP.VariableInSetRef(vref))) : nothing
637626
return VariableProperties(info, name, set, nothing)
638627
end
628+
629+
function VariableProperties(vref::JuMP.AbstractVariableRef)
630+
info = get_variable_info(vref)
631+
name = JuMP.name(vref)
632+
return VariableProperties(info, name, nothing, nothing)
633+
end
634+
635+
"""
636+
VariableProperties(expr)::VariableProperties
637+
638+
Creates a `VariableProperties` object with blank variable info (no bounds, not fixed,
639+
not binary/integer) from an expression. The `expr` argument is provided for
640+
extensions to infer additional properties (e.g., parameter dependencies in InfiniteOpt).
641+
642+
## Arguments
643+
- `expr`: Expression for extensions to extract metadata from
644+
645+
## Returns
646+
A `VariableProperties` object with blank info.
647+
"""
648+
function VariableProperties(expr)
649+
info = _free_variable_info()
650+
return VariableProperties(info, "", nothing, nothing)
651+
end

src/extension_api.jl

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
"""
2+
InfiniteGDPModel(args...; kwargs...)
3+
4+
Creates an `InfiniteOpt.InfiniteModel` that is compatible with the
5+
capabiltiies provided by DisjunctiveProgramming.jl. This requires
6+
that InfiniteOpt be imported first.
7+
8+
**Example**
9+
```julia
10+
julia> using DisjunctiveProgramming, InfiniteOpt
11+
12+
julia> InfiniteGDPModel()
13+
14+
```
15+
"""
16+
function InfiniteGDPModel end
17+
18+
"""
19+
InfiniteLogical(prefs...)
20+
21+
Allows users to create infinite logical variables. This is a tag
22+
for the `@variable` macro that is a combination of `InfiniteOpt.Infinite`
23+
and `DisjunctiveProgramming.Logical`. This requires that InfiniteOpt be
24+
first imported.
25+
26+
**Example**
27+
```julia
28+
julia> using DisjunctiveProgramming, InfiniteOpt
29+
30+
julia> model = InfiniteGDPModel();
31+
32+
julia> @infinite_parameter(model, t in [0, 1]);
33+
34+
julia> @infinite_parameter(model, x[1:2] in [-1, 1]);
35+
36+
julia> @variable(model, Y, InfiniteLogical(t, x)) # creates Y(t, x) in {True, False}
37+
Y(t, x)
38+
```
39+
"""
40+
function InfiniteLogical end

0 commit comments

Comments
 (0)