From b30df263b48a1f8b1f2d050e5c161731cbeb2b99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Tue, 3 Mar 2026 12:24:41 +0100 Subject: [PATCH 01/10] Fix tests --- test/JuMP_wrapper.jl | 8 ++++---- test/Project.toml | 3 +++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/test/JuMP_wrapper.jl b/test/JuMP_wrapper.jl index 15188e4..089ab29 100644 --- a/test/JuMP_wrapper.jl +++ b/test/JuMP_wrapper.jl @@ -42,13 +42,13 @@ function test_container() @test con_it_expr isa ExprGenerator con_expr = con_it_expr.expr.expr @test sprint(show, con_ref) == - "$ParametrizedArray(((x + getindex($IteratorIndex(1), 2.0)) - getindex($IteratorIndex(1), 3.0)) - 0.0, $Iterator{Tuple{Symbol, Int64, Real}}(Tuple{Symbol, Int64, Real}[(:a, -1, π), (:b, 1, 0.0)]) ∈ MathOptInterface.Nonnegatives(2), $IteratorValues[iterator([:a, :b])])" + "$ParametrizedArray(((x + getindex($IteratorIndex(1), 2)) - getindex($IteratorIndex(1), 3)) - 0.0, $Iterator{Tuple{Symbol, Int64, Real}}(Tuple{Symbol, Int64, Real}[(:a, -1, π), (:b, 1, 0.0)]) ∈ MathOptInterface.Nonnegatives(2), $IteratorValues[iterator([:a, :b])])" @test sprint(show, MIME"text/latex"(), con_ref) == - "$ParametrizedArray(((x + getindex($IteratorIndex(1), 2.0)) - getindex($IteratorIndex(1), 3.0)) - 0.0, $Iterator{Tuple{Symbol, Int64, Real}}(Tuple{Symbol, Int64, Real}[(:a, -1, π), (:b, 1, 0.0)]) ∈ MathOptInterface.Nonnegatives(2), $IteratorValues[iterator([:a, :b])])" + "$ParametrizedArray(((x + getindex($IteratorIndex(1), 2)) - getindex($IteratorIndex(1), 3)) - 0.0, $Iterator{Tuple{Symbol, Int64, Real}}(Tuple{Symbol, Int64, Real}[(:a, -1, π), (:b, 1, 0.0)]) ∈ MathOptInterface.Nonnegatives(2), $IteratorValues[iterator([:a, :b])])" @test sprint(show, con_expr) == - "((x + getindex($IteratorIndex(1), 2.0)) - getindex($IteratorIndex(1), 3.0)) - 0.0" + "((x + getindex($IteratorIndex(1), 2)) - getindex($IteratorIndex(1), 3)) - 0.0" @test sprint(show, MIME"text/latex"(), con_expr) == - "\$ {\\left({\\left({x} + {\\textsf{getindex}\\left({$IteratorIndex(1)}, {2.0}\\right)}\\right)} - {\\textsf{getindex}\\left({$IteratorIndex(1)}, {3.0}\\right)}\\right)} - {0.0} \$" + "\$ {\\left({\\left({x} + {\\textsf{getindex}\\left({$IteratorIndex(1)}, {2}\\right)}\\right)} - {\\textsf{getindex}\\left({$IteratorIndex(1)}, {3.0}\\right)}\\right)} - {0.0} \$" i = GenOpt.iterator(keys) expr = x + d1[i] - d2[i] diff --git a/test/Project.toml b/test/Project.toml index b7625d1..3b980f2 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -2,3 +2,6 @@ GenOpt = "f2c049d8-7489-4223-990c-4f1c121a4cde" JuMP = "4076af6c-e467-56ae-b986-b466b2749572" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" + +[sources] +GenOpt = {path = ".."} From 78e38ca6e9cc9aad3cd2fa4f4f948cdb1070b848 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Tue, 3 Mar 2026 12:33:38 +0100 Subject: [PATCH 02/10] fix format --- examples/quadrotor.jl | 24 ++-- src/JuMP_wrapper.jl | 20 +++- src/MOI_wrapper.jl | 15 ++- src/examodels.jl | 249 +++++++++++++++++++++++++++++------------- src/operators.jl | 62 +++++++++-- src/print.jl | 2 +- test/operators.jl | 3 +- 7 files changed, 268 insertions(+), 107 deletions(-) diff --git a/examples/quadrotor.jl b/examples/quadrotor.jl index e5fbf13..54df84d 100644 --- a/examples/quadrotor.jl +++ b/examples/quadrotor.jl @@ -7,10 +7,11 @@ N = 3 n = 9 p = 4 -d(i, j, N) = - (j == 1 ? 1 * sin(2 * pi / N * i) : 0.0) + - (j == 3 ? 2 * sin(4 * pi / N * i) : 0.0) + - (j == 5 ? 2 * i / N : 0.0) +function d(i, j, N) + return (j == 1 ? 1 * sin(2 * pi / N * i) : 0.0) + + (j == 3 ? 2 * sin(4 * pi / N * i) : 0.0) + + (j == 5 ? 2 * i / N : 0.0) +end dt = 1/N R = fill(1 / 10, 4) Q = [1, 0, 1, 0, 1, 0, 1, 1, 1] @@ -27,12 +28,7 @@ model = Model() using GenOpt container = ParametrizedArray -@constraint( - model, - [i in 1:n], - x[1, i] == x0[i], - container = container, -) +@constraint(model, [i in 1:n], x[1, i] == x0[i], container = container,) @constraint( model, [i in 1:N], @@ -84,13 +80,17 @@ container = ParametrizedArray [i in 1:N], x[i+1, 7] == x[i, 7] + - (u[i, 2] * cos(x[i, 7]) / cos(x[i, 8]) + u[i, 3] * sin(x[i, 7]) / cos(x[i, 8])) * dt, + ( + u[i, 2] * cos(x[i, 7]) / cos(x[i, 8]) + + u[i, 3] * sin(x[i, 7]) / cos(x[i, 8]) + ) * dt, container = container, ) @constraint( model, [i in 1:N], - x[i+1, 8] == x[i, 8] + (-u[i, 2] * sin(x[i, 7]) + u[i, 3] * cos(x[i, 7])) * dt, + x[i+1, 8] == + x[i, 8] + (-u[i, 2] * sin(x[i, 7]) + u[i, 3] * cos(x[i, 7])) * dt, container = container, ) @constraint( diff --git a/src/JuMP_wrapper.jl b/src/JuMP_wrapper.jl index b5c589b..73d5d2f 100644 --- a/src/JuMP_wrapper.jl +++ b/src/JuMP_wrapper.jl @@ -35,17 +35,26 @@ end Base.size(array::ArrayOfVariables) = array.size function Base.getindex(A::ArrayOfVariables{T}, I...) where {T} - index = A.offset + Base._to_linear_index(Base.CartesianIndices(A.size), I...) + index = + A.offset + Base._to_linear_index(Base.CartesianIndices(A.size), I...) return JuMP.GenericVariableRef{T}(A.model, MOI.VariableIndex(index)) end JuMP._is_real(::ArrayOfVariables) = true -JuMP.moi_function(array::ArrayOfVariables) = ContiguousArrayOfVariables(array.offset, array.size) -function JuMP.jump_function(model::JuMP.GenericModel{T}, array::ContiguousArrayOfVariables{N}) where {T,N} +function JuMP.moi_function(array::ArrayOfVariables) + return ContiguousArrayOfVariables(array.offset, array.size) +end +function JuMP.jump_function( + model::JuMP.GenericModel{T}, + array::ContiguousArrayOfVariables{N}, +) where {T,N} return ArrayOfVariables{T,N}(model, array.offset, array.size) end -function Base.convert(::Type{ArrayOfVariables{T,N}}, array::Array{JuMP.GenericVariableRef{T},N}) where {T,N} +function Base.convert( + ::Type{ArrayOfVariables{T,N}}, + array::Array{JuMP.GenericVariableRef{T},N}, +) where {T,N} model = JuMP.owner_model(array[1]) offset = JuMP.index(array[1]).value - 1 for i in eachindex(array) @@ -106,7 +115,8 @@ end function Base.getindex(expr::ExprGenerator, i::Integer) idx = CartesianIndices(Base.OneTo.(_size(expr)))[i] - values = [expr.iterators[i].values[idx[i]] for i in eachindex(expr.iterators)] + values = + [expr.iterators[i].values[idx[i]] for i in eachindex(expr.iterators)] return index_iterators(expr.expr.expr, values) end diff --git a/src/MOI_wrapper.jl b/src/MOI_wrapper.jl index 58e4361..c7081ba 100644 --- a/src/MOI_wrapper.jl +++ b/src/MOI_wrapper.jl @@ -41,7 +41,10 @@ end function MOI.Utilities.is_canonical(f::FunctionGenerator) return MOI.Utilities.is_canonical(f.func) end -function MOI.Utilities.is_coefficient_type(::Type{FunctionGenerator{E}}, ::Type{T}) where {E,T} +function MOI.Utilities.is_coefficient_type( + ::Type{FunctionGenerator{E}}, + ::Type{T}, +) where {E,T} return MOI.Utilities.is_coefficient_type(E, T) end @@ -54,12 +57,18 @@ function Base.copy(f::SumGenerator{F}) where {F} return SumGenerator{F}(copy(f.func), f.iterators) end -function MOI.Utilities.map_indices(::MOI.Utilities.IndexMap, func::Union{FunctionGenerator,SumGenerator}) +function MOI.Utilities.map_indices( + ::MOI.Utilities.IndexMap, + func::Union{FunctionGenerator,SumGenerator}, +) # TODO check it's identity return func end -function MOI.Utilities.map_indices(::Function, func::Union{FunctionGenerator,SumGenerator}) +function MOI.Utilities.map_indices( + ::Function, + func::Union{FunctionGenerator,SumGenerator}, +) # TODO check it's identity return func end diff --git a/src/examodels.jl b/src/examodels.jl index 43d0caf..78ce0ea 100644 --- a/src/examodels.jl +++ b/src/examodels.jl @@ -8,7 +8,6 @@ # Use of this source code is governed by an MIT-style license that can be found # in the LICENSE.md file or at https://opensource.org/licenses/MIT. - import ExaModels import MathOptInterface @@ -18,7 +17,8 @@ const SUPPORTED_FUNC_TYPE{T} = Union{ MOI.ScalarQuadraticFunction{T}, MOI.ScalarNonlinearFunction, } -const SUPPORTED_FUNC_TYPE_WITH_VAR{T} = Union{SUPPORTED_FUNC_TYPE{T},MOI.VariableIndex} +const SUPPORTED_FUNC_TYPE_WITH_VAR{T} = + Union{SUPPORTED_FUNC_TYPE{T},MOI.VariableIndex} const SUPPORTED_FUNC_SET_TYPE{T} = Union{MOI.GreaterThan{T},MOI.LessThan{T},MOI.EqualTo{T},MOI.Interval{T}} const SUPPORTED_VAR_SET_TYPE{T} = @@ -70,7 +70,8 @@ function check_supported(T, moim) if F <: FunctionGenerator continue end - !(F <: SUPPORTED_FUNC_TYPE_WITH_VAR) && error("Unsupported function type $F.") + !(F <: SUPPORTED_FUNC_TYPE_WITH_VAR) && + error("Unsupported function type $F.") if F <: MOI.VariableIndex !(S <: SUPPORTED_VAR_SET_TYPE) && error("Unsupported variable index constraint $F in $S") @@ -102,17 +103,24 @@ function to_exacore(moim::MOI.ModelLike; backend = nothing, T = Float64) end function fill_variable_bounds!(moim, lvar, uvar, var_to_idx, T) - for ci in - MOI.get(moim, MOI.ListOfConstraintIndices{MOI.VariableIndex,MOI.GreaterThan{T}}()) + for ci in MOI.get( + moim, + MOI.ListOfConstraintIndices{MOI.VariableIndex,MOI.GreaterThan{T}}(), + ) vi = MOI.get(moim, MOI.ConstraintFunction(), ci) lvar[var_to_idx[vi]] = MOI.get(moim, MOI.ConstraintSet(), ci).lower end - for ci in - MOI.get(moim, MOI.ListOfConstraintIndices{MOI.VariableIndex,MOI.LessThan{T}}()) + for ci in MOI.get( + moim, + MOI.ListOfConstraintIndices{MOI.VariableIndex,MOI.LessThan{T}}(), + ) vi = MOI.get(moim, MOI.ConstraintFunction(), ci) uvar[var_to_idx[vi]] = MOI.get(moim, MOI.ConstraintSet(), ci).upper end - for ci in MOI.get(moim, MOI.ListOfConstraintIndices{MOI.VariableIndex,MOI.EqualTo{T}}()) + for ci in MOI.get( + moim, + MOI.ListOfConstraintIndices{MOI.VariableIndex,MOI.EqualTo{T}}(), + ) vi = MOI.get(moim, MOI.ConstraintFunction(), ci) fixed_val = MOI.get(moim, MOI.ConstraintSet(), ci).value lvar[var_to_idx[vi]] = fixed_val @@ -139,7 +147,10 @@ function fill_variable_start!(moim, x0, param_vis) end function _get_parameters(moim::MOI.ModelLike, T) - cis = MOI.get(moim, MOI.ListOfConstraintIndices{MOI.VariableIndex,MOI.Parameter{T}}()) + cis = MOI.get( + moim, + MOI.ListOfConstraintIndices{MOI.VariableIndex,MOI.Parameter{T}}(), + ) parameters = Vector{Tuple{MOI.VariableIndex,MOI.Parameter{T}}}() for ci in cis vi = MOI.get(moim, MOI.ConstraintFunction(), ci) @@ -186,9 +197,13 @@ function copy_objective!(c, moim, var_to_idx) obj_type = MOI.get(moim, MOI.ObjectiveFunctionType()) bin = BinNull() - bin = exafy_obj(MOI.get(moim, MOI.ObjectiveFunction{obj_type}()), bin, var_to_idx) + bin = exafy_obj( + MOI.get(moim, MOI.ObjectiveFunction{obj_type}()), + bin, + var_to_idx, + ) - build_objective!(c, bin) + return build_objective!(c, bin) end function copy_constraints!(c, moim, var_to_idx, T) @@ -204,8 +219,17 @@ function copy_constraints!(c, moim, var_to_idx, T) F <: MOI.VariableIndex && continue F <: FunctionGenerator && continue cis = MOI.get(moim, MOI.ListOfConstraintIndices{F,S}()) - bin, offset = - exafy_con(moim, cis, bin, offset, lcon, ucon, y0, var_to_idx, con_to_idx) + bin, offset = exafy_con( + moim, + cis, + bin, + offset, + lcon, + ucon, + y0, + var_to_idx, + con_to_idx, + ) end cons = ExaModels.constraint(c, offset; start = y0, lcon = lcon, ucon = ucon) build_constraint!(c, cons, bin) @@ -243,10 +267,15 @@ function exagen(f::MOI.ScalarNonlinearFunction, offsets) @assert isone(f.args[2]) return ExaModels.ParSource() else - return ExaModels.ParIndexed(ExaModels.ParSource(), offsets[v.value] + f.args[2]) + return ExaModels.ParIndexed( + ExaModels.ParSource(), + offsets[v.value] + f.args[2], + ) end else - error("Unexpected the first operatand of `getindex` to be of type `$(typeof(v))`") + error( + "Unexpected the first operatand of `getindex` to be of type `$(typeof(v))`", + ) end else return op(f.head)((exagen(e, offsets) for e in f.args)...) @@ -264,10 +293,16 @@ function _exagen(func::MOI.ScalarNonlinearFunction, iterators) cs = nothing pars = only.(iterators[].values) else - cs = [0; cumsum(lengths)[1:end-1]] - pars = vec(map(Base.Iterators.ProductIterator(ntuple(i -> iterators[i].values, length(iterators)))) do I - reduce((i, j) -> tuple(i..., j...), I) - end) + cs = [0; cumsum(lengths)[1:(end-1)]] + pars = vec( + map( + Base.Iterators.ProductIterator( + ntuple(i -> iterators[i].values, length(iterators)), + ), + ) do I + return reduce((i, j) -> tuple(i..., j...), I) + end, + ) end expr = exagen(func, cs) return expr, pars @@ -284,7 +319,13 @@ function copy_generator_constraints!(c, moim, cis, var_to_idx, con_to_idx, T) set = MOI.get(moim, MOI.ConstraintSet(), ci) con_to_idx[ci] = c.ncon expr, pars = _exagen(func.func, func.iterators) - ExaModels.constraint(c, expr, pars; lcon = _lower_bounds(set, T), ucon = _upper_bounds(set, T)) + ExaModels.constraint( + c, + expr, + pars; + lcon = _lower_bounds(set, T), + ucon = _upper_bounds(set, T), + ) end end @@ -363,13 +404,21 @@ function _exafy_con( end return bin end -function _exafy_con(i, c::C, bin, var_to_idx, con_to_idx; pos = true) where {C<:Real} +function _exafy_con( + i, + c::C, + bin, + var_to_idx, + con_to_idx; + pos = true, +) where {C<:Real} e = pos ? ExaModels.ParIndexed(ExaModels.ParSource(), 1) : -ExaModels.ParIndexed(ExaModels.ParSource(), 1) bin = update_bin!( bin, - ExaModels.ParIndexed(ExaModels.ParSource(), 2) => 0 * ExaModels.Var(1) + e, + ExaModels.ParIndexed(ExaModels.ParSource(), 2) => + 0 * ExaModels.Var(1) + e, (c, con_to_idx[i]), ) @@ -388,7 +437,7 @@ function exafy_con( con_to_idx, ) where {V<:Vector{<:MOI.ConstraintIndex}} l = sum(cons) do ci - MOI.dimension(MOI.get(moim, MOI.ConstraintSet(), ci)) + return MOI.dimension(MOI.get(moim, MOI.ConstraintSet(), ci)) end resize!(lcon, offset + l) @@ -398,9 +447,7 @@ function exafy_con( func = MOI.get(moim, MOI.ConstraintFunction(), ci) set = MOI.get(moim, MOI.ConstraintSet(), ci) con_to_idx[ci] = offset + 1 - start = if MOI.supports( - moim, MOI.ConstraintPrimalStart(), typeof(ci) - ) + start = if MOI.supports(moim, MOI.ConstraintPrimalStart(), typeof(ci)) MOI.get(moim, MOI.ConstraintPrimalStart(), ci) else nothing @@ -414,21 +461,33 @@ function exafy_con( end function _exafy_con_update_start(i, start, y0, con_to_idx) - y0[con_to_idx[i]] = start + return y0[con_to_idx[i]] = start end function _exafy_con_update_start(i, ::Nothing, y0, con_to_idx) - y0[con_to_idx[i]] = zero(eltype(y0)) + return y0[con_to_idx[i]] = zero(eltype(y0)) end -function _exafy_con_update_vector(i, e::MOI.Interval{T}, lcon, ucon, con_to_idx) where {T} +function _exafy_con_update_vector( + i, + e::MOI.Interval{T}, + lcon, + ucon, + con_to_idx, +) where {T} lcon[con_to_idx[i]] = e.lower - ucon[con_to_idx[i]] = e.upper + return ucon[con_to_idx[i]] = e.upper end -function _exafy_con_update_vector(i, e::MOI.LessThan{T}, lcon, ucon, con_to_idx) where {T} +function _exafy_con_update_vector( + i, + e::MOI.LessThan{T}, + lcon, + ucon, + con_to_idx, +) where {T} lcon[con_to_idx[i]] = -Inf - ucon[con_to_idx[i]] = e.upper + return ucon[con_to_idx[i]] = e.upper end function _exafy_con_update_vector( @@ -439,25 +498,30 @@ function _exafy_con_update_vector( con_to_idx, ) where {T} ucon[con_to_idx[i]] = Inf - lcon[con_to_idx[i]] = e.lower + return lcon[con_to_idx[i]] = e.lower end -function _exafy_con_update_vector(i, e::MOI.EqualTo{T}, lcon, ucon, con_to_idx) where {T} +function _exafy_con_update_vector( + i, + e::MOI.EqualTo{T}, + lcon, + ucon, + con_to_idx, +) where {T} lcon[con_to_idx[i]] = e.value - ucon[con_to_idx[i]] = e.value + return ucon[con_to_idx[i]] = e.value end - function build_constraint!(c, cons, bin) build_constraint!(c, cons, bin.inner) - ExaModels.constraint!(c, cons, bin.head, bin.data) + return ExaModels.constraint!(c, cons, bin.head, bin.data) end function build_constraint!(c, cons, ::BinNull) end function build_objective!(c, bin) build_objective!(c, bin.inner) - ExaModels.objective(c, bin.head, bin.data) + return ExaModels.objective(c, bin.head, bin.data) end function build_objective!(c, ::BinNull) end @@ -542,10 +606,13 @@ function _exafy(i::R, var_to_idx, p) where {R<:Real} end function _exafy(e::MOI.ScalarNonlinearFunction, var_to_idx, p = ()) - return op(e.head)((begin - c, p = _exafy(e, var_to_idx, p) - c - end for e in e.args)...), p + return op(e.head)(( + begin + c, p = _exafy(e, var_to_idx, p) + c + end for e in e.args + )...), + p end function _exafy(e::MOI.ScalarAffineFunction{T}, var_to_idx, p = ()) where {T} @@ -573,32 +640,38 @@ function _exafy(e::MOI.ScalarQuadraticFunction{T}, var_to_idx, p = ()) where {T} p = (p..., e.constant) if !isempty(e.affine_terms) - t += sum(begin - c1, p = _exafy(term, var_to_idx, p) - c1 - end for term in e.affine_terms) + t += sum( + begin + c1, p = _exafy(term, var_to_idx, p) + c1 + end for term in e.affine_terms + ) end if !isempty(e.quadratic_terms) - t += sum(begin - c1, p = _exafy(term, var_to_idx, p) - c1 - end for term in e.quadratic_terms) + t += sum( + begin + c1, p = _exafy(term, var_to_idx, p) + c1 + end for term in e.quadratic_terms + ) end return t, p end function _exafy(e::MOI.ScalarQuadraticTerm{T}, var_to_idx, p = ()) where {T} - if e.variable_1 == e.variable_2 v, p = _exafy(e.variable_1, var_to_idx, p) - return ExaModels.ParIndexed(ExaModels.ParSource(), length(p) + 1) * abs2(v), + return ExaModels.ParIndexed(ExaModels.ParSource(), length(p) + 1) * + abs2(v), (p..., e.coefficient / 2) # it seems that MOI assumes this by default else v1, p = _exafy(e.variable_1, var_to_idx, p) v2, p = _exafy(e.variable_2, var_to_idx, p) - return ExaModels.ParIndexed(ExaModels.ParSource(), length(p) + 1) * v1 * v2, + return ExaModels.ParIndexed(ExaModels.ParSource(), length(p) + 1) * + v1 * + v2, (p..., e.coefficient) end end @@ -756,7 +829,6 @@ function op(s::Symbol) end end - mutable struct ExaOptimizer{B,S} <: MOI.AbstractOptimizer solver::S backend::B @@ -793,19 +865,33 @@ end function MOI.supports(::ExaOptimizer, ::MOI.ObjectiveSense) return true end -function MOI.supports(::ExaOptimizer, ::MOI.ObjectiveFunction{<:SUPPORTED_FUNC_TYPE_WITH_VAR}) +function MOI.supports( + ::ExaOptimizer, + ::MOI.ObjectiveFunction{<:SUPPORTED_FUNC_TYPE_WITH_VAR}, +) return true end -function MOI.supports(::ExaOptimizer, ::MOI.VariablePrimalStart, ::Type{MOI.VariableIndex}) +function MOI.supports( + ::ExaOptimizer, + ::MOI.VariablePrimalStart, + ::Type{MOI.VariableIndex}, +) return true end function ExaOptimizer(solver, backend = nothing; kwargs...) - return ExaOptimizer(solver, backend, nothing, nothing, 0.0, Dict{Symbol,Any}(kwargs...)) + return ExaOptimizer( + solver, + backend, + nothing, + nothing, + 0.0, + Dict{Symbol,Any}(kwargs...), + ) end function MOI.empty!(model::ExaOptimizer) - model.model = nothing + return model.model = nothing end function MOI.copy_to(dest::ExaOptimizer, src::MOI.ModelLike) @@ -830,12 +916,24 @@ function MOI.optimize!(optimizer::ExaOptimizer) return optimizer end -MOI.get(optimizer::ExaOptimizer, ::MOI.TerminationStatus) = - ExaModels.termination_status_translator(optimizer.solver, optimizer.result.status) -MOI.get(model::ExaOptimizer, attr::Union{MOI.PrimalStatus,MOI.DualStatus}) = - ExaModels.result_status_translator(model.solver, model.result.status) +function MOI.get(optimizer::ExaOptimizer, ::MOI.TerminationStatus) + return ExaModels.termination_status_translator( + optimizer.solver, + optimizer.result.status, + ) +end +function MOI.get( + model::ExaOptimizer, + attr::Union{MOI.PrimalStatus,MOI.DualStatus}, +) + return ExaModels.result_status_translator(model.solver, model.result.status) +end -function MOI.get(model::ExaOptimizer, attr::MOI.VariablePrimal, vi::MOI.VariableIndex) +function MOI.get( + model::ExaOptimizer, + attr::MOI.VariablePrimal, + vi::MOI.VariableIndex, +) MOI.check_result_index_bounds(model, attr) if vi.value > PARAMETER_INDEX_THRESHOLD return model.model.θ[vi.value-PARAMETER_INDEX_THRESHOLD] @@ -862,7 +960,9 @@ function MOI.get( ) MOI.check_result_index_bounds(model, attr) # MOI.throw_if_not_valid(model, ci) - rc = model.result.multipliers_L[ci.value] - model.result.multipliers_U[ci.value] + rc = + model.result.multipliers_L[ci.value] - + model.result.multipliers_U[ci.value] return min(0.0, rc) end @@ -873,7 +973,9 @@ function MOI.get( ) MOI.check_result_index_bounds(model, attr) # MOI.throw_if_not_valid(model, ci) - rc = model.result.multipliers_L[ci.value] - model.result.multipliers_U[ci.value] + rc = + model.result.multipliers_L[ci.value] - + model.result.multipliers_U[ci.value] return max(0.0, rc) end @@ -884,11 +986,12 @@ function MOI.get( ) MOI.check_result_index_bounds(model, attr) # MOI.throw_if_not_valid(model, ci) - rc = model.result.multipliers_L[ci.value] - model.result.multipliers_U[ci.value] + rc = + model.result.multipliers_L[ci.value] - + model.result.multipliers_U[ci.value] return rc end - function MOI.get(model::ExaOptimizer, ::MOI.ResultCount) return (model.result !== nothing) ? 1 : 0 end @@ -901,10 +1004,9 @@ function MOI.get(model::ExaOptimizer, attr::MOI.ObjectiveValue) end MOI.get(model::ExaOptimizer, ::MOI.SolveTimeSec) = model.solve_time -MOI.get( - model::ExaOptimizer, - ::MOI.SolverName, -) = "$(string(model.solver)) running with ExaModels" +function MOI.get(model::ExaOptimizer, ::MOI.SolverName) + return "$(string(model.solver)) running with ExaModels" +end function MOI.set(model::ExaOptimizer, p::MOI.RawOptimizerAttribute, value) model.options[Symbol(p.name)] = value @@ -912,8 +1014,9 @@ function MOI.set(model::ExaOptimizer, p::MOI.RawOptimizerAttribute, value) return end - -_make_index_map(model::MOI.ModelLike, maps) = _make_index_map(model, maps[1], maps[2]) +function _make_index_map(model::MOI.ModelLike, maps) + return _make_index_map(model, maps[1], maps[2]) +end function _make_index_map(model::MOI.ModelLike, var_to_idx, con_to_idx) variables = MOI.get(model, MOI.ListOfVariableIndices()) map = MOI.Utilities.IndexMap() diff --git a/src/operators.jl b/src/operators.jl index aa143ba..1fd2308 100644 --- a/src/operators.jl +++ b/src/operators.jl @@ -27,7 +27,10 @@ function ExprTemplate{E}( return ExprTemplate{E,V}(expr, iterators) end -function Base.convert(::Type{ExprTemplate{E,V}}, expr::ExprTemplate{F,V}) where {E,F,V} +function Base.convert( + ::Type{ExprTemplate{E,V}}, + expr::ExprTemplate{F,V}, +) where {E,F,V} return ExprTemplate{E,V}(expr.expr, expr.iterators) end @@ -38,7 +41,9 @@ end JuMP.variable_ref_type(::Type{ExprTemplate{E,V}}) where {E,V} = V -JuMP.check_belongs_to_model(f::ExprTemplate, model) = JuMP.check_belongs_to_model(f.expr, model) +function JuMP.check_belongs_to_model(f::ExprTemplate, model) + return JuMP.check_belongs_to_model(f.expr, model) +end """ struct IteratorValues{I} @@ -127,7 +132,10 @@ for f in MOI.Nonlinear.DEFAULT_UNIVARIATE_OPERATORS end function prepare(it::IteratorValues) - return JuMP.GenericNonlinearExpr{JuMP.VariableRef}(:getindex, Any[it.index, it.value_index]) + return JuMP.GenericNonlinearExpr{JuMP.VariableRef}( + :getindex, + Any[it.index, it.value_index], + ) end _expr(f::JuMP.AbstractJuMPScalar) = f @@ -143,7 +151,9 @@ _iterators(::JuMP.AbstractJuMPScalar) = nothing _iterators(it::_ScalarWithIterator) = it.iterators _iterators(::Number) = nothing -_type(it::IteratorValues) = typeof(first(it.iterators[it.index.value].values)[it.value_index]) +function _type(it::IteratorValues) + return typeof(first(it.iterators[it.index.value].values)[it.value_index]) +end _type(::ExprTemplate{E}) where {E} = E _type(f::JuMP.AbstractJuMPScalar) = typeof(f) _type(f::Number) = typeof(f) @@ -173,7 +183,11 @@ function _multivariate(f, op, x, y) end # TODO move to JuMP -function JuMP._MA.promote_operation(::typeof(/), ::Type{JuMP.NonlinearExpr}, ::Type{JuMP.NonlinearExpr}) +function JuMP._MA.promote_operation( + ::typeof(/), + ::Type{JuMP.NonlinearExpr}, + ::Type{JuMP.NonlinearExpr}, +) return JuMP.NonlinearExpr end @@ -245,7 +259,10 @@ function JuMP.check_belongs_to_model(s::LazySum, model::JuMP.AbstractModel) end function JuMP.moi_function(s::LazySum{E}) where {E} - return SumGenerator{JuMP.moi_function_type(E)}(JuMP.moi_function(s.expr), s.iterators) + return SumGenerator{JuMP.moi_function_type(E)}( + JuMP.moi_function(s.expr), + s.iterators, + ) end _iterators(it::Base.Iterators.ProductIterator) = iterators(it.iterators) @@ -265,9 +282,13 @@ end function _new_values(f, iterators, index) iterator = iterators[index.value] iterators[index.value] = Iterator(map(iterator.values) do val - (val..., f(val)) + return (val..., f(val)) end) - return IteratorValues(iterators, index, length(first(iterators[index.value].values))) + return IteratorValues( + iterators, + index, + length(first(iterators[index.value].values)), + ) end function _getindex(d, it::IteratorValues) @@ -282,17 +303,34 @@ function Base.getindex(it::IteratorValues, i) return IteratorValues(it.iterators, it.index, i) end -function Base.getindex(v::Array{V}, i::Integer, j::_ScalarWithIterator) where {V<:JuMP.AbstractVariableRef} +function Base.getindex( + v::Array{V}, + i::Integer, + j::_ScalarWithIterator, +) where {V<:JuMP.AbstractVariableRef} nl = JuMP.GenericNonlinearExpr{V}(:getindex, to_generator(v), i, _expr(j)) return ExprTemplate{V}(nl, _iterators(j)) end -function Base.getindex(v::Array{V}, i::_ScalarWithIterator, j::Integer) where {V<:JuMP.AbstractVariableRef} +function Base.getindex( + v::Array{V}, + i::_ScalarWithIterator, + j::Integer, +) where {V<:JuMP.AbstractVariableRef} nl = JuMP.GenericNonlinearExpr{V}(:getindex, to_generator(v), _expr(i), j) return ExprTemplate{V}(nl, _iterators(i)) end -function Base.getindex(v::Array{V}, i::_ScalarWithIterator, j::_ScalarWithIterator) where {V<:JuMP.AbstractVariableRef} - nl = JuMP.GenericNonlinearExpr{V}(:getindex, to_generator(v), _expr(i), _expr(j)) +function Base.getindex( + v::Array{V}, + i::_ScalarWithIterator, + j::_ScalarWithIterator, +) where {V<:JuMP.AbstractVariableRef} + nl = JuMP.GenericNonlinearExpr{V}( + :getindex, + to_generator(v), + _expr(i), + _expr(j), + ) return ExprTemplate{V}(nl, _check_equal(_iterators(i), _iterators(j))) end diff --git a/src/print.jl b/src/print.jl index 7a931c4..199e6f1 100644 --- a/src/print.jl +++ b/src/print.jl @@ -65,7 +65,7 @@ function Base.show(io::IO, ::MIME"text/latex", a::ParametrizedArray) end function Base.show(io::IO, ::MIME"text/plain", v::ArrayOfVariables) - println(io, Base.summary(v), " with offset ", v.offset) + return println(io, Base.summary(v), " with offset ", v.offset) end function Base.show(io::IO, v::ArrayOfVariables) diff --git a/test/operators.jl b/test/operators.jl index a1ca4f9..001d2d4 100644 --- a/test/operators.jl +++ b/test/operators.jl @@ -28,7 +28,8 @@ end function _test_template(et, values) @test et isa ExprTemplate for i in eachindex(values) - @test index_iterators(et.expr, (et.iterators[1].values[i],)) == values[i] + @test index_iterators(et.expr, (et.iterators[1].values[i],)) == + values[i] end end From 6665c9d57ae616d82f2eadc59b3a5ff5727c338f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Tue, 3 Mar 2026 12:41:04 +0100 Subject: [PATCH 03/10] Fix format --- src/examodels.jl | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/examodels.jl b/src/examodels.jl index 78ce0ea..978b7d7 100644 --- a/src/examodels.jl +++ b/src/examodels.jl @@ -606,12 +606,10 @@ function _exafy(i::R, var_to_idx, p) where {R<:Real} end function _exafy(e::MOI.ScalarNonlinearFunction, var_to_idx, p = ()) - return op(e.head)(( - begin - c, p = _exafy(e, var_to_idx, p) - c - end for e in e.args - )...), + return op(e.head)((begin + c, p = _exafy(e, var_to_idx, p) + c + end for e in e.args)...), p end From 9fa628b6021f169e46c9087175b6343157b134c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Tue, 3 Mar 2026 12:44:39 +0100 Subject: [PATCH 04/10] Fix format --- src/examodels.jl | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/examodels.jl b/src/examodels.jl index 978b7d7..78ce0ea 100644 --- a/src/examodels.jl +++ b/src/examodels.jl @@ -606,10 +606,12 @@ function _exafy(i::R, var_to_idx, p) where {R<:Real} end function _exafy(e::MOI.ScalarNonlinearFunction, var_to_idx, p = ()) - return op(e.head)((begin - c, p = _exafy(e, var_to_idx, p) - c - end for e in e.args)...), + return op(e.head)(( + begin + c, p = _exafy(e, var_to_idx, p) + c + end for e in e.args + )...), p end From a123c46f4375c23c0e7d483c6584dafaa105e434 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Tue, 3 Mar 2026 12:49:00 +0100 Subject: [PATCH 05/10] Fix format --- src/examodels.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/examodels.jl b/src/examodels.jl index 78ce0ea..0883982 100644 --- a/src/examodels.jl +++ b/src/examodels.jl @@ -606,13 +606,13 @@ function _exafy(i::R, var_to_idx, p) where {R<:Real} end function _exafy(e::MOI.ScalarNonlinearFunction, var_to_idx, p = ()) - return op(e.head)(( + gen = ( begin c, p = _exafy(e, var_to_idx, p) c end for e in e.args - )...), - p + ) + return op(e.head)(gen...), p end function _exafy(e::MOI.ScalarAffineFunction{T}, var_to_idx, p = ()) where {T} From f28732c1014a03b9135d2000c89fba697c606566 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Tue, 3 Mar 2026 13:07:55 +0100 Subject: [PATCH 06/10] Fixes --- src/examodels.jl | 12 +++++------- test/JuMP_wrapper.jl | 6 +++--- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/examodels.jl b/src/examodels.jl index 0883982..0deacc9 100644 --- a/src/examodels.jl +++ b/src/examodels.jl @@ -606,13 +606,11 @@ function _exafy(i::R, var_to_idx, p) where {R<:Real} end function _exafy(e::MOI.ScalarNonlinearFunction, var_to_idx, p = ()) - gen = ( - begin - c, p = _exafy(e, var_to_idx, p) - c - end for e in e.args - ) - return op(e.head)(gen...), p + args = map(e.args) do expr + c, p = _exafy(expr, var_to_idx, p) + return c + end + return op(e.head)(args...), p end function _exafy(e::MOI.ScalarAffineFunction{T}, var_to_idx, p = ()) where {T} diff --git a/test/JuMP_wrapper.jl b/test/JuMP_wrapper.jl index 089ab29..b19e443 100644 --- a/test/JuMP_wrapper.jl +++ b/test/JuMP_wrapper.jl @@ -42,11 +42,11 @@ function test_container() @test con_it_expr isa ExprGenerator con_expr = con_it_expr.expr.expr @test sprint(show, con_ref) == - "$ParametrizedArray(((x + getindex($IteratorIndex(1), 2)) - getindex($IteratorIndex(1), 3)) - 0.0, $Iterator{Tuple{Symbol, Int64, Real}}(Tuple{Symbol, Int64, Real}[(:a, -1, π), (:b, 1, 0.0)]) ∈ MathOptInterface.Nonnegatives(2), $IteratorValues[iterator([:a, :b])])" + "$ParametrizedArray(((x + getindex($IteratorIndex(1), 2)) - getindex($IteratorIndex(1), 3)) - 0, $Iterator{Tuple{Symbol, Int64, Real}}(Tuple{Symbol, Int64, Real}[(:a, -1, π), (:b, 1, 0.0)]) ∈ MathOptInterface.Nonnegatives(2), $IteratorValues[iterator([:a, :b])])" @test sprint(show, MIME"text/latex"(), con_ref) == - "$ParametrizedArray(((x + getindex($IteratorIndex(1), 2)) - getindex($IteratorIndex(1), 3)) - 0.0, $Iterator{Tuple{Symbol, Int64, Real}}(Tuple{Symbol, Int64, Real}[(:a, -1, π), (:b, 1, 0.0)]) ∈ MathOptInterface.Nonnegatives(2), $IteratorValues[iterator([:a, :b])])" + "$ParametrizedArray(((x + getindex($IteratorIndex(1), 2)) - getindex($IteratorIndex(1), 3)) - 00, $Iterator{Tuple{Symbol, Int64, Real}}(Tuple{Symbol, Int64, Real}[(:a, -1, π), (:b, 1, 0.0)]) ∈ MathOptInterface.Nonnegatives(2), $IteratorValues[iterator([:a, :b])])" @test sprint(show, con_expr) == - "((x + getindex($IteratorIndex(1), 2)) - getindex($IteratorIndex(1), 3)) - 0.0" + "((x + getindex($IteratorIndex(1), 2)) - getindex($IteratorIndex(1), 3)) - 0" @test sprint(show, MIME"text/latex"(), con_expr) == "\$ {\\left({\\left({x} + {\\textsf{getindex}\\left({$IteratorIndex(1)}, {2}\\right)}\\right)} - {\\textsf{getindex}\\left({$IteratorIndex(1)}, {3.0}\\right)}\\right)} - {0.0} \$" From 4b8b4d6029e4a087cb3ae74c6563131cc2b52984 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Tue, 3 Mar 2026 14:16:08 +0100 Subject: [PATCH 07/10] Fix --- test/JuMP_wrapper.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/JuMP_wrapper.jl b/test/JuMP_wrapper.jl index b19e443..5aa697e 100644 --- a/test/JuMP_wrapper.jl +++ b/test/JuMP_wrapper.jl @@ -44,7 +44,7 @@ function test_container() @test sprint(show, con_ref) == "$ParametrizedArray(((x + getindex($IteratorIndex(1), 2)) - getindex($IteratorIndex(1), 3)) - 0, $Iterator{Tuple{Symbol, Int64, Real}}(Tuple{Symbol, Int64, Real}[(:a, -1, π), (:b, 1, 0.0)]) ∈ MathOptInterface.Nonnegatives(2), $IteratorValues[iterator([:a, :b])])" @test sprint(show, MIME"text/latex"(), con_ref) == - "$ParametrizedArray(((x + getindex($IteratorIndex(1), 2)) - getindex($IteratorIndex(1), 3)) - 00, $Iterator{Tuple{Symbol, Int64, Real}}(Tuple{Symbol, Int64, Real}[(:a, -1, π), (:b, 1, 0.0)]) ∈ MathOptInterface.Nonnegatives(2), $IteratorValues[iterator([:a, :b])])" + "$ParametrizedArray(((x + getindex($IteratorIndex(1), 2)) - getindex($IteratorIndex(1), 3)) - 0, $Iterator{Tuple{Symbol, Int64, Real}}(Tuple{Symbol, Int64, Real}[(:a, -1, π), (:b, 1, 0.0)]) ∈ MathOptInterface.Nonnegatives(2), $IteratorValues[iterator([:a, :b])])" @test sprint(show, con_expr) == "((x + getindex($IteratorIndex(1), 2)) - getindex($IteratorIndex(1), 3)) - 0" @test sprint(show, MIME"text/latex"(), con_expr) == From c759e18591c8706d484acc8b76e92830ec8b55c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Tue, 3 Mar 2026 15:04:13 +0100 Subject: [PATCH 08/10] Fix --- test/JuMP_wrapper.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/JuMP_wrapper.jl b/test/JuMP_wrapper.jl index 5aa697e..341002d 100644 --- a/test/JuMP_wrapper.jl +++ b/test/JuMP_wrapper.jl @@ -48,7 +48,7 @@ function test_container() @test sprint(show, con_expr) == "((x + getindex($IteratorIndex(1), 2)) - getindex($IteratorIndex(1), 3)) - 0" @test sprint(show, MIME"text/latex"(), con_expr) == - "\$ {\\left({\\left({x} + {\\textsf{getindex}\\left({$IteratorIndex(1)}, {2}\\right)}\\right)} - {\\textsf{getindex}\\left({$IteratorIndex(1)}, {3.0}\\right)}\\right)} - {0.0} \$" + "\$ {\\left({\\left({x} + {\\textsf{getindex}\\left({$IteratorIndex(1)}, {2}\\right)}\\right)} - {\\textsf{getindex}\\left({$IteratorIndex(1)}, {3.0}\\right)}\\right)} - {0} \$" i = GenOpt.iterator(keys) expr = x + d1[i] - d2[i] From d20d4e7bfe2902ec24f20b957225563f0c501ff0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Tue, 3 Mar 2026 15:09:13 +0100 Subject: [PATCH 09/10] Fix --- test/JuMP_wrapper.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/JuMP_wrapper.jl b/test/JuMP_wrapper.jl index 341002d..f512d2e 100644 --- a/test/JuMP_wrapper.jl +++ b/test/JuMP_wrapper.jl @@ -48,7 +48,7 @@ function test_container() @test sprint(show, con_expr) == "((x + getindex($IteratorIndex(1), 2)) - getindex($IteratorIndex(1), 3)) - 0" @test sprint(show, MIME"text/latex"(), con_expr) == - "\$ {\\left({\\left({x} + {\\textsf{getindex}\\left({$IteratorIndex(1)}, {2}\\right)}\\right)} - {\\textsf{getindex}\\left({$IteratorIndex(1)}, {3.0}\\right)}\\right)} - {0} \$" + "\$ {\\left({\\left({x} + {\\textsf{getindex}\\left({$IteratorIndex(1)}, {2}\\right)}\\right)} - {\\textsf{getindex}\\left({$IteratorIndex(1)}, {3}\\right)}\\right)} - {0} \$" i = GenOpt.iterator(keys) expr = x + d1[i] - d2[i] From 4e9dca201a528d6226f1a888a06b82c970191205 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Sun, 5 Apr 2026 10:55:31 +0200 Subject: [PATCH 10/10] Local drafts --- Project.toml | 2 ++ src/GenOpt.jl | 3 ++ src/JuMP_wrapper.jl | 41 -------------------------- src/array_of_variables.jl | 60 +++++++++++++++++++++++++++++++++++++++ src/print.jl | 8 ------ 5 files changed, 65 insertions(+), 49 deletions(-) create mode 100644 src/array_of_variables.jl diff --git a/Project.toml b/Project.toml index 0f5cda4..42ee136 100644 --- a/Project.toml +++ b/Project.toml @@ -6,10 +6,12 @@ authors = ["Benoît Legat "] [deps] ExaModels = "1037b233-b668-4ce9-9b63-f9f681f55dd2" JuMP = "4076af6c-e467-56ae-b986-b466b2749572" +LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee" [compat] ExaModels = "0.9" JuMP = "1.29" +LinearAlgebra = "1.12.0" MathOptInterface = "1.46" julia = "1.10" diff --git a/src/GenOpt.jl b/src/GenOpt.jl index 52f76fb..308be87 100644 --- a/src/GenOpt.jl +++ b/src/GenOpt.jl @@ -5,8 +5,11 @@ module GenOpt +import LinearAlgebra + include("MOI_wrapper.jl") include("JuMP_wrapper.jl") +include("array_of_variables.jl") include("examodels.jl") # Copied from JuMP.jl: diff --git a/src/JuMP_wrapper.jl b/src/JuMP_wrapper.jl index 73d5d2f..37f0b47 100644 --- a/src/JuMP_wrapper.jl +++ b/src/JuMP_wrapper.jl @@ -27,47 +27,6 @@ JuMP._is_real(::Union{IteratorInExpr,IteratorIndex}) = true JuMP.moi_function(i::Union{IteratorInExpr,IteratorIndex}) = i JuMP.jump_function(_, i::Union{IteratorInExpr,IteratorIndex}) = i -struct ArrayOfVariables{T,N} <: AbstractArray{JuMP.GenericVariableRef{T},N} - model::JuMP.GenericModel{T} - offset::Int64 - size::NTuple{N,Int64} -end - -Base.size(array::ArrayOfVariables) = array.size -function Base.getindex(A::ArrayOfVariables{T}, I...) where {T} - index = - A.offset + Base._to_linear_index(Base.CartesianIndices(A.size), I...) - return JuMP.GenericVariableRef{T}(A.model, MOI.VariableIndex(index)) -end - -JuMP._is_real(::ArrayOfVariables) = true -function JuMP.moi_function(array::ArrayOfVariables) - return ContiguousArrayOfVariables(array.offset, array.size) -end -function JuMP.jump_function( - model::JuMP.GenericModel{T}, - array::ContiguousArrayOfVariables{N}, -) where {T,N} - return ArrayOfVariables{T,N}(model, array.offset, array.size) -end - -function Base.convert( - ::Type{ArrayOfVariables{T,N}}, - array::Array{JuMP.GenericVariableRef{T},N}, -) where {T,N} - model = JuMP.owner_model(array[1]) - offset = JuMP.index(array[1]).value - 1 - for i in eachindex(array) - @assert JuMP.owner_model(array[i]) === model - @assert JuMP.index(array[i]).value == offset + i - end - return ArrayOfVariables{T,N}(model, offset, size(array)) -end - -function to_generator(array::Array{JuMP.GenericVariableRef{T},N}) where {T,N} - return convert(ArrayOfVariables{T,N}, array) -end - struct ExprGenerator{E,V<:JuMP.AbstractVariableRef} <: AbstractVector{JuMP.GenericNonlinearExpr{V}} expr::ExprTemplate{E,V} diff --git a/src/array_of_variables.jl b/src/array_of_variables.jl new file mode 100644 index 0000000..1d0e076 --- /dev/null +++ b/src/array_of_variables.jl @@ -0,0 +1,60 @@ +struct ArrayOfVariables{T,N} <: AbstractArray{JuMP.GenericVariableRef{T},N} + model::JuMP.GenericModel{T} + offset::Int64 + size::NTuple{N,Int64} +end + +Base.size(array::ArrayOfVariables) = array.size +function Base.getindex(A::ArrayOfVariables{T}, I...) where {T} + index = + A.offset + Base._to_linear_index(Base.CartesianIndices(A.size), I...) + return JuMP.GenericVariableRef{T}(A.model, MOI.VariableIndex(index)) +end + +function JuMP.Containers.container( + f::Function, + indices::JuMP.Containers.VectorizedProductIterator{NTuple{N,Base.OneTo{Int}}}, + ::Type{ArrayOfVariables}, +) where {N} + return to_generator(JuMP.Containers.container(f, indices, Array)) +end + +JuMP._is_real(::ArrayOfVariables) = true +function JuMP.moi_function(array::ArrayOfVariables) + return ContiguousArrayOfVariables(array.offset, array.size) +end +function JuMP.jump_function( + model::JuMP.GenericModel{T}, + array::ContiguousArrayOfVariables{N}, +) where {T,N} + return ArrayOfVariables{T,N}(model, array.offset, array.size) +end + +function Base.convert( + ::Type{ArrayOfVariables{T,N}}, + array::Array{JuMP.GenericVariableRef{T},N}, +) where {T,N} + model = JuMP.owner_model(array[1]) + offset = JuMP.index(array[1]).value - 1 + for i in eachindex(array) + @assert JuMP.owner_model(array[i]) === model + @assert JuMP.index(array[i]).value == offset + i + end + return ArrayOfVariables{T,N}(model, offset, size(array)) +end + +function to_generator(array::Array{JuMP.GenericVariableRef{T},N}) where {T,N} + return convert(ArrayOfVariables{T,N}, array) +end + +function LinearAlgebra.mul(A::ArrayOfVariables, B::Array) + return JuMP.NonlinearExpr(:*, Any[A, B]) +end + +function Base.show(io::IO, ::MIME"text/plain", v::ArrayOfVariables) + return println(io, Base.summary(v), " with offset ", v.offset) +end + +function Base.show(io::IO, v::ArrayOfVariables) + return show(io, MIME"text/plain"(), v) +end diff --git a/src/print.jl b/src/print.jl index 199e6f1..e735c33 100644 --- a/src/print.jl +++ b/src/print.jl @@ -63,11 +63,3 @@ end function Base.show(io::IO, ::MIME"text/latex", a::ParametrizedArray) return show(io, a) end - -function Base.show(io::IO, ::MIME"text/plain", v::ArrayOfVariables) - return println(io, Base.summary(v), " with offset ", v.offset) -end - -function Base.show(io::IO, v::ArrayOfVariables) - return show(io, MIME"text/plain"(), v) -end