Pack groups of similar constraints of sum of similar terms into lightweight generators. The ExaModels extension passes these generators to ExaModels to accelerate automatic differentiation on CPU or GPU. See the presentation at JuMP-dev 2025.
GenOpt.jl is licensed under the MIT license.
Install GenOpt as follows:
import Pkg
Pkg.add("GenOpt")To create a group of constraints, give ParametrizedArray as container keyword:
using GenOpt
@constraint(
model,
[i in 1:n],
x[1, i] == x0[i],
container = ParametrizedArray,
)To create a grouped sum, use lazy_sum as follows:
@objective(
model,
Min,
lazy_sum(0.5 * R[j] * (u[i, j]^2) for i in 1:N, j in 1:p),
)Register the generator and scalar polynomial bridges to use affine and quadratic lazy sums with HiGHS:
using JuMP, GenOpt
import HiGHS
model = Model(HiGHS.Optimizer)
add_bridge(model, GenOpt.FunctionGeneratorBridge)
add_bridge(model, GenOpt.PolynomialFunctionBridge)
@variable(model, x[1:4])
@constraint(model, [i in 1:4], x[i] >= 0, container = ParametrizedArray)
@constraint(model, lazy_sum(x[i] + 1 for i in 1:4) <= 12)
@objective(model, Min, lazy_sum((x[i] - i)^2 for i in 1:4))
optimize!(model)GenOpt tracks known polynomial expressions using PolynomialFunction{F}, where
F is the affine or quadratic output type. The shared builders accumulate lazy
sum terms directly into that type when the optimizer needs it. HiGHS supports
quadratic objectives, but not quadratic constraints. Ordinary, untagged
MOI.ScalarNonlinearFunction inputs are not handled by these polynomial bridges.
Polynomial lowering currently requires filters to refer only to the iterators bound by their sum. A filter depending on an enclosing generator raises an explicit error.
Affine and quadratic constraint families are prepared once before their rows are expanded. GenOpt resolves coefficient and index columns, then fills independent scalar functions using typed loops. Functions are processed in blocks of 65,536 rows to bound temporary function storage. Forms that cannot be prepared use the scalar expansion path. Preparation supports common arithmetic, literal unit and square powers, and dense arrays with up to two dimensions. Other nonlinear numeric expressions keep MOI's scalar evaluation semantics.
Install ExaModels and a solver such as MadNLP:
import Pkg
Pkg.add(["ExaModels", "MadNLP"])Loading both GenOpt and ExaModels automatically enables the extension. Use
ExaModels.Optimizer to solve a JuMP model while preserving its grouped
constraints and sums:
using JuMP, GenOpt
import ExaModels
import MadNLP
model = Model(() -> ExaModels.Optimizer(MadNLP.madnlp))
@variable(model, x[1:4])
@constraint(model, [i in 1:4], x[i] >= 2, container = ParametrizedArray)
@constraint(model, [i in 1:4], x[i] <= 3, container = ParametrizedArray)
@objective(model, Min, lazy_sum((x[i] - i)^2 for i in 1:4))
optimize!(model)
value.(x) # approximately [2, 2, 3, 3]
objective_value(model) # approximately 2For an NVIDIA GPU, install and load CUDA, then pass its backend to the optimizer:
import Pkg
Pkg.add("CUDA")
using CUDA
set_optimizer(model, () -> ExaModels.Optimizer(MadNLP.madnlp, CUDABackend()))
optimize!(model)See the quadrotor and optimal power flow examples for larger models.