Closed odow closed 2 months ago
Here's where I'm heading:
julia> model = MOI.Utilities.Model{Float64}()
An empty MOIU.Model{Float64}
julia> MOI.Utilities.loadfromstring!(model, """
variables: x, y
minobjective: 2.0 * x
c1: x >= 0.0
c2: 1.0 * x + 2.0 * y <= 3.0
c2: 1.0 * x + 3.0 * y <= 2.0
""")
julia> MOI.Utilities.CachingOptimizer(model, MOI.Utilities.Model{Float64}())
A MOI.Utilities.CachingOptimizer:
├ state
│ └ EMPTY_OPTIMIZER
├ mode
│ └ AUTOMATIC
├ model_cache :: MOIU.Model{Float64}
A MOIU.Model{Float64}
├ ObjectiveSense
│ └ MIN_SENSE
├ ObjectiveFunctionType
│ └ MOI.ScalarAffineFunction{Float64}
├ NumberOfVariables
│ └ 2
└ NumberOfConstraints
├ MOI.ScalarAffineFunction{Float64} in MOI.LessThan{Float64}: 2
└ MOI.VariableIndex in MOI.GreaterThan{Float64}: 1
└ optimizer :: MOIU.Model{Float64}
An empty MOIU.Model{Float64}
I think I like this:
julia> include("test/Bridges/sdpa_models.jl")
julia> model = MOI.Utilities.CachingOptimizer(
MOI.Utilities.Model{Float64}(),
MOI.Bridges.full_bridge_optimizer(StandardSDPAModel{Float64}(), Float64),
)
A MOI.Utilities.CachingOptimizer:
├ state
│ └ EMPTY_OPTIMIZER
├ mode
│ └ AUTOMATIC
├ model_cache
│ An empty MOIU.Model{Float64}
└ optimizer
A MOIB.LazyBridgeOptimizer{MOIU.GenericModel{Float64, MOIU.ObjectiveContainer{Float64}, MOIU.VariablesContainer{Float64}, StandardSDPAModelFunctionConstraints{Float64}}}
├ Variable bridges
│ └ none
├ Constraint bridges
│ └ none
├ Objective bridges
│ └ none
└ model
An empty MOIU.GenericModel{Float64, MOIU.ObjectiveContainer{Float64}, MOIU.VariablesContainer{Float64}, StandardSDPAModelFunctionConstraints{Float64}}
julia> x = MOI.add_variable(model)
MOI.VariableIndex(1)
julia> MOI.add_constraint(model, 2.0 * x, MOI.LessThan(3.0))
MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.LessThan{Float64}}(1)
julia> MOI.Utilities.attach_optimizer(model)
julia> model
A MOI.Utilities.CachingOptimizer:
├ state
│ └ ATTACHED_OPTIMIZER
├ mode
│ └ AUTOMATIC
├ model_cache
│ A MOIU.Model{Float64}
│ ├ ObjectiveSense
│ │ └ FEASIBILITY_SENSE
│ ├ ObjectiveFunctionType
│ │ └ MOI.ScalarAffineFunction{Float64}
│ ├ NumberOfVariables
│ │ └ 1
│ └ NumberOfConstraints
│ └ MOI.ScalarAffineFunction{Float64} in MOI.LessThan{Float64}: 1
└ optimizer
A MOIB.LazyBridgeOptimizer{MOIU.GenericModel{Float64, MOIU.ObjectiveContainer{Float64}, MOIU.VariablesContainer{Float64}, StandardSDPAModelFunctionConstraints{Float64}}}
├ Variable bridges
│ ├ MOIB.Variable.FreeBridge{Float64}
│ └ MOIB.Variable.VectorizeBridge{Float64, MOI.Nonnegatives}
├ Constraint bridges
│ ├ MOIB.Constraint.LessToGreaterBridge{Float64, MOI.ScalarAffineFunction{Float64}, MOI.ScalarAffineFunction{Float64}}
│ └ MOIB.Constraint.ScalarSlackBridge{Float64, MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}}
├ Objective bridges
│ └ none
└ model
A MOIU.GenericModel{Float64, MOIU.ObjectiveContainer{Float64}, MOIU.VariablesContainer{Float64}, StandardSDPAModelFunctionConstraints{Float64}}
├ ObjectiveSense
│ └ FEASIBILITY_SENSE
├ ObjectiveFunctionType
│ └ MOI.ScalarAffineFunction{Float64}
├ NumberOfVariables
│ └ 3
└ NumberOfConstraints
├ MOI.ScalarAffineFunction{Float64} in MOI.EqualTo{Float64}: 1
└ MOI.VectorOfVariables in MOI.Nonnegatives: 2
So this is actually super useful for debugging.
Cribbing from a recent post on discourse:
julia> using JuMP
julia> import HiGHS
julia> import MultiObjectiveAlgorithms as MOA
julia> model = Model(() -> MOA.Optimizer(HiGHS.Optimizer));
julia> set_silent(model)
julia> set_attribute(model, MOA.Algorithm(), MOA.Hierarchical())
julia> @variables(model, begin
lb[i] <= z[i in 1:length(lb)] <= ub[i], Int
d >= 0
end);
julia> @constraints(model, begin
z - lb .- d >= 0
z - ub .+ d <= 0
sum(z) <= 500
end);
julia> @objective(model, Max, [sum(z), d]);
julia> optimize!(model)
julia> backend(model)
A MOI.Utilities.CachingOptimizer:
├ state
│ └ ATTACHED_OPTIMIZER
├ mode
│ └ AUTOMATIC
├ model_cache
│ A MOIU.UniversalFallback{MOIU.Model{Float64}}
│ ├ ObjectiveSense
│ │ └ MAX_SENSE
│ ├ ObjectiveFunctionType
│ │ └ MOI.VectorAffineFunction{Float64}
│ ├ NumberOfVariables
│ │ └ 11
│ └ NumberOfConstraints
│ ├ MOI.ScalarAffineFunction{Float64} in MOI.LessThan{Float64}: 1
│ ├ MOI.VectorAffineFunction{Float64} in MOI.Nonnegatives: 1
│ ├ MOI.VectorAffineFunction{Float64} in MOI.Nonpositives: 1
│ ├ MOI.VariableIndex in MOI.GreaterThan{Float64}: 11
│ ├ MOI.VariableIndex in MOI.LessThan{Float64}: 10
│ └ MOI.VariableIndex in MOI.Integer: 10
└ optimizer
A MOIB.LazyBridgeOptimizer{MultiObjectiveAlgorithms.Optimizer}
├ Variable bridges
│ └ none
├ Constraint bridges
│ ├ MOIB.Constraint.ScalarizeBridge{Float64, MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}}
│ └ MOIB.Constraint.ScalarizeBridge{Float64, MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}}
├ Objective bridges
│ └ none
└ model
A MultiObjectiveAlgorithms.Optimizer
├ ObjectiveSense
│ └ MAX_SENSE
├ ObjectiveFunctionType
│ └ MOI.VectorAffineFunction{Float64}
├ NumberOfVariables
│ └ 11
└ NumberOfConstraints
├ MOI.ScalarAffineFunction{Float64} in MOI.GreaterThan{Float64}: 10
├ MOI.ScalarAffineFunction{Float64} in MOI.LessThan{Float64}: 11
├ MOI.VariableIndex in MOI.GreaterThan{Float64}: 11
├ MOI.VariableIndex in MOI.LessThan{Float64}: 10
└ MOI.VariableIndex in MOI.Integer: 10
This looks nice! I wonder if it could be a bit more compact in cases when there's only 1 child? I've found too much scrolling in the REPL can make it harder to understand a printout. e.g.
A MOI.Utilities.CachingOptimizer:
├ state: ATTACHED_OPTIMIZER
├ mode: AUTOMATIC
├ model_cache: MOIU.UniversalFallback{MOIU.Model{Float64}}
│ ├ ObjectiveSense: MAX_SENSE
│ ├ ObjectiveFunctionType: MOI.VectorAffineFunction{Float64}
│ ├ NumberOfVariables: 11
│ └ NumberOfConstraints: 34
│ ├ MOI.ScalarAffineFunction{Float64} in MOI.LessThan{Float64}: 1
│ ├ MOI.VectorAffineFunction{Float64} in MOI.Nonnegatives: 1
│ ├ MOI.VectorAffineFunction{Float64} in MOI.Nonpositives: 1
│ ├ MOI.VariableIndex in MOI.GreaterThan{Float64}: 11
│ ├ MOI.VariableIndex in MOI.LessThan{Float64}: 10
│ └ MOI.VariableIndex in MOI.Integer: 10
└ optimizer: MOIB.LazyBridgeOptimizer{MultiObjectiveAlgorithms.Optimizer}
├ Variable bridges: none
├ Constraint bridges
│ ├ MOIB.Constraint.ScalarizeBridge{Float64, MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}}
│ └ MOIB.Constraint.ScalarizeBridge{Float64, MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}}
├ Objective bridges: none
└ model: MultiObjectiveAlgorithms.Optimizer
├ ObjectiveSense: MAX_SENSE
├ ObjectiveFunctionType: MOI.VectorAffineFunction{Float64}
├ NumberOfVariables: 11
└ NumberOfConstraints: 52
├ MOI.ScalarAffineFunction{Float64} in MOI.GreaterThan{Float64}: 10
├ MOI.ScalarAffineFunction{Float64} in MOI.LessThan{Float64}: 11
├ MOI.VariableIndex in MOI.GreaterThan{Float64}: 11
├ MOI.VariableIndex in MOI.LessThan{Float64}: 10
└ MOI.VariableIndex in MOI.Integer: 10
This is kinda already done for contraints, like MOI.ScalarAffineFunction{Float64} in MOI.GreaterThan{Float64}: 10
, so I think it could work also for NumberOfVariables
, ObjectiveSense
, ObjectiveFunctionType
, state
and mode
.
I think it would also make sense for none
s in things like Variable bridges
, Constraint bridges
.
I also tweaked it so instead of A MOIU.UniversalFallback{MOIU.Model{Float64}}
on the following line, I removed the A
and put it after a colon. To me the "A" was a bit confusing (I know it's the indefinite article, but the rest isn't really sentences so it's not clear if it means like some kind of "type A" vs "type B" or something). And IMO it reads a little better having it on the same line. I did the same for optimizer: MOIB.LazyBridgeOptimizer{MultiObjectiveAlgorithms.Optimizer}
and model: MultiObjectiveAlgorithms.Optimizer
.
Lastly I think it could also be nice to have NumberOfConstraints
print a total, like I added here.
@ericphanson done
It's now:
julia> using JuMP
julia> import HiGHS
julia> import MultiObjectiveAlgorithms as MOA
julia> model = Model(() -> MOA.Optimizer(HiGHS.Optimizer));
julia> set_silent(model)
julia> set_attribute(model, MOA.Algorithm(), MOA.Hierarchical())
julia> lb = ones(10); ub = fill(10, 10);
julia> @variables(model, begin
lb[i] <= z[i in 1:length(lb)] <= ub[i], Int
d >= 0
end);
julia> @constraints(model, begin
z - lb .- d >= 0
z - ub .+ d <= 0
sum(z) <= 500
end);
julia> @objective(model, Max, [sum(z), d]);
julia> optimize!(model)
julia> backend(model)
MOIU.CachingOptimizer
├ state: ATTACHED_OPTIMIZER
├ mode: AUTOMATIC
├ model_cache: MOIU.UniversalFallback{MOIU.Model{Float64}}
│ ├ ObjectiveSense: MAX_SENSE
│ ├ ObjectiveFunctionType: MOI.VectorAffineFunction{Float64}
│ ├ NumberOfVariables: 11
│ └ NumberOfConstraints: 34
│ ├ MOI.ScalarAffineFunction{Float64} in MOI.LessThan{Float64}: 1
│ ├ MOI.VectorAffineFunction{Float64} in MOI.Nonnegatives: 1
│ ├ MOI.VectorAffineFunction{Float64} in MOI.Nonpositives: 1
│ ├ MOI.VariableIndex in MOI.GreaterThan{Float64}: 11
│ ├ MOI.VariableIndex in MOI.LessThan{Float64}: 10
│ └ MOI.VariableIndex in MOI.Integer: 10
└ optimizer: MOIB.LazyBridgeOptimizer{MultiObjectiveAlgorithms.Optimizer}
├ Variable bridges: none
├ Constraint bridges:
│ ├ MOIB.Constraint.ScalarizeBridge{Float64, MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}}
│ └ MOIB.Constraint.ScalarizeBridge{Float64, MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}}
├ Objective bridges: none
└ model: MultiObjectiveAlgorithms.Optimizer
├ ObjectiveSense: MAX_SENSE
├ ObjectiveFunctionType: MOI.VectorAffineFunction{Float64}
├ NumberOfVariables: 11
└ NumberOfConstraints: 52
├ MOI.ScalarAffineFunction{Float64} in MOI.GreaterThan{Float64}: 10
├ MOI.ScalarAffineFunction{Float64} in MOI.LessThan{Float64}: 11
├ MOI.VariableIndex in MOI.GreaterThan{Float64}: 11
├ MOI.VariableIndex in MOI.LessThan{Float64}: 10
└ MOI.VariableIndex in MOI.Integer: 10
Here's a problem;
julia> using JuMP, SCS
julia> model = Model(SCS.Optimizer);
julia> set_silent(model)
julia> @variable(model, x[1:2, 1:2] >= 0, PSD);
julia> @constraint(model, sum(x) == 1);
julia> optimize!(model)
julia> backend(model)
MOIU.CachingOptimizer
├ state: ATTACHED_OPTIMIZER
├ mode: AUTOMATIC
├ model_cache: MOIU.UniversalFallback{MOIU.Model{Float64}}
│ ├ ObjectiveSense: FEASIBILITY_SENSE
│ ├ ObjectiveFunctionType: MOI.ScalarAffineFunction{Float64}
│ ├ NumberOfVariables: 3
│ └ NumberOfConstraints: 5
│ ├ MOI.ScalarAffineFunction{Float64} in MOI.EqualTo{Float64}: 1
│ ├ MOI.VectorOfVariables in MOI.PositiveSemidefiniteConeTriangle: 1
│ └ MOI.VariableIndex in MOI.GreaterThan{Float64}: 3
└ optimizer: MOIB.LazyBridgeOptimizer{MOIU.CachingOptimizer{SCS.Optimizer, MOIU.UniversalFallback{MOIU.Model{Float64}}}}
├ Variable bridges: none
├ Constraint bridges:
│ ├ MOIB.Constraint.SetDotScalingBridge{Float64, MOI.PositiveSemidefiniteConeTriangle, MOI.VectorAffineFunction{Float64}, MOI.VectorOfVariables}
│ ├ MOIB.Constraint.VectorizeBridge{Float64, MOI.VectorAffineFunction{Float64}, MOI.Nonnegatives, MOI.VariableIndex}
│ ├ MOIB.Constraint.VectorizeBridge{Float64, MOI.VectorAffineFunction{Float64}, MOI.Zeros, MOI.ScalarAffineFunction{Float64}}
│ └ SCS.ScaledPSDConeBridge{Float64, MOI.VectorAffineFunction{Float64}}
├ Objective bridges: none
└ model: MOIU.CachingOptimizer
├ state: EMPTY_OPTIMIZER
├ mode: AUTOMATIC
├ model_cache: MOIU.UniversalFallback{MOIU.Model{Float64}}
│ ├ ObjectiveSense: FEASIBILITY_SENSE
│ ├ ObjectiveFunctionType: MOI.ScalarAffineFunction{Float64}
│ ├ NumberOfVariables: 3
│ └ NumberOfConstraints: 5
│ ├ MOI.VectorAffineFunction{Float64} in MOI.Zeros: 1
│ ├ MOI.VectorAffineFunction{Float64} in MOI.Nonnegatives: 3
│ └ MOI.VectorAffineFunction{Float64} in SCS.ScaledPSDCone: 1
└ optimizer: SCS.Optimizer
├ ObjectiveSense: FEASIBILITY_SENSE
├ ObjectiveFunctionType: ?
├ NumberOfVariables: ?
└ NumberOfConstraints: 0
SCS doesn't support getting various attributes, so there is some weirdness. We could fix that though.
This is very nice, I like it ! It makes the MOI layers much less obscure
Now it's
julia> backend(model)
MOIU.CachingOptimizer
├ state: ATTACHED_OPTIMIZER
├ mode: AUTOMATIC
├ model_cache: MOIU.UniversalFallback{MOIU.Model{Float64}}
│ ├ ObjectiveSense: FEASIBILITY_SENSE
│ ├ ObjectiveFunctionType: MOI.ScalarAffineFunction{Float64}
│ ├ NumberOfVariables: 3
│ └ NumberOfConstraints: 5
│ ├ MOI.ScalarAffineFunction{Float64} in MOI.EqualTo{Float64}: 1
│ ├ MOI.VectorOfVariables in MOI.PositiveSemidefiniteConeTriangle: 1
│ └ MOI.VariableIndex in MOI.GreaterThan{Float64}: 3
└ optimizer: MOIB.LazyBridgeOptimizer{MOIU.CachingOptimizer{SCS.Optimizer, MOIU.UniversalFallback{MOIU.Model{Float64}}}}
├ Variable bridges: none
├ Constraint bridges:
│ ├ MOIB.Constraint.SetDotScalingBridge{Float64, MOI.PositiveSemidefiniteConeTriangle, MOI.VectorAffineFunction{Float64}, MOI.VectorOfVariables}
│ ├ MOIB.Constraint.VectorizeBridge{Float64, MOI.VectorAffineFunction{Float64}, MOI.Nonnegatives, MOI.VariableIndex}
│ ├ MOIB.Constraint.VectorizeBridge{Float64, MOI.VectorAffineFunction{Float64}, MOI.Zeros, MOI.ScalarAffineFunction{Float64}}
│ └ SCS.ScaledPSDConeBridge{Float64, MOI.VectorAffineFunction{Float64}}
├ Objective bridges: none
└ model: MOIU.CachingOptimizer
├ state: EMPTY_OPTIMIZER
├ mode: AUTOMATIC
├ model_cache: MOIU.UniversalFallback{MOIU.Model{Float64}}
│ ├ ObjectiveSense: FEASIBILITY_SENSE
│ ├ ObjectiveFunctionType: MOI.ScalarAffineFunction{Float64}
│ ├ NumberOfVariables: 3
│ └ NumberOfConstraints: 5
│ ├ MOI.VectorAffineFunction{Float64} in MOI.Zeros: 1
│ ├ MOI.VectorAffineFunction{Float64} in MOI.Nonnegatives: 3
│ └ MOI.VectorAffineFunction{Float64} in SCS.ScaledPSDCone: 1
└ optimizer: SCS.Optimizer
├ ObjectiveSense: unknown
├ ObjectiveFunctionType: unknown
├ NumberOfVariables: unknown
└ NumberOfConstraints: unknown
looks great!
Thoughts @blegat?
Maybe we should add a test printing an empty model that every solver runs. It should be safe with the _try_catch
but you never know, a solver could also segfault ^^
I think this is a really nice quality of life improvement. Don't know why we didn't do this earlier.
Closes #2504
show
: https://github.com/jump-dev/MathOptInterface.jl/actions/runs/9218665500