ParametricOptInterface.jl

ParametricOptInterface.jl is a package for managing parameters in JuMP and MathOptInterface.

License

ParametricOptInterface.jl is licensed under the MIT License.

Installation

Install ParametricOptInterface using Pkg.add:

import Pkg
Pkg.add("ParametricOptInterface")

Use with JuMP

Use ParametricOptInterface with JuMP by following this brief example:

julia> using JuMP, HiGHS
julia> import ParametricOptInterface as POI
julia> model = Model(() -> POI.Optimizer(HiGHS.Optimizer))A JuMP Model ├ solver: Parametric Optimizer with HiGHS attached ├ objective_sense: FEASIBILITY_SENSE ├ num_variables: 0 ├ num_constraints: 0 └ Names registered in the model: none
julia> @variable(model, x)x
julia> @variable(model, p in Parameter(1))p
julia> @constraint(model, p * x + p >= 3)p*x + p ≥ 3
julia> @objective(model, Min, 2x + p)2 x + p
julia> optimize!(model)Running HiGHS 1.13.1 (git hash: 1d267d97c): Copyright (c) 2026 under Apache 2.0 license terms Using BLAS: blastrampoline LP has 1 row; 1 col; 1 nonzero Coefficient ranges: Matrix [1e+00, 1e+00] Cost [2e+00, 2e+00] Bound [0e+00, 0e+00] RHS [2e+00, 2e+00] Presolving model 0 rows, 0 cols, 0 nonzeros 0s 0 rows, 0 cols, 0 nonzeros 0s Presolve reductions: rows 0(-1); columns 0(-1); nonzeros 0(-1) - Reduced to empty Performed postsolve Solving the original LP from the solution after postsolve Model status : Optimal Objective value : 5.0000000000e+00 P-D objective error : 0.0000000000e+00 HiGHS run time : 0.00
julia> value(x)2.0
julia> set_parameter_value(p, 2.0)
julia> optimize!(model)LP has 1 row; 1 col; 1 nonzero Coefficient ranges: Matrix [2e+00, 2e+00] Cost [2e+00, 2e+00] Bound [0e+00, 0e+00] RHS [1e+00, 1e+00] Solving LP with useful basis so presolve not used Model status : Optimal Objective value : 3.0000000000e+00 P-D objective error : 0.0000000000e+00 HiGHS run time : 0.00
julia> value(x)0.5

The dual of a parameter

In some applications you may need the dual of a parameter. The dual can be computed only if the parameter appears additively in the problem. Query the dual associated with the parameter as follows:

julia> using JuMP, HiGHS
julia> import ParametricOptInterface as POI
julia> model = Model(() -> POI.Optimizer(HiGHS.Optimizer()));
julia> set_silent(model)
julia> @variable(model, x);
julia> @variable(model, p in Parameter(1));
julia> @constraint(model, x + p >= 3);
julia> @objective(model, Min, 2x);
julia> optimize!(model)
julia> dual(VariableInSetRef(p))-2.0

Note how the dual is the same as the reduced_cost of an equivalent fixed variable:

julia> using JuMP, HiGHS
julia> model = Model(HiGHS.Optimizer);
julia> set_silent(model)
julia> @variable(model, x);
julia> @variable(model, p == 1);
julia> @constraint(model, x + p >= 3);
julia> @objective(model, Min, 2x);
julia> optimize!(model)
julia> reduced_cost(p)-2.0

Note that, even though ParametricOptInterface supports querying the dual of a parameter, this is not true in general. For example, Ipopt does not support querying the dual of a parameter. Moreover, the dual is available only if all of the parameters appear additively in the model; the dual is not available if there are multiplicative parameters.

Variable bounds

An ambiguity arises when the user writes a model like:

julia> using JuMP
julia> model = Model();
julia> @variable(model, x)x
julia> @variable(model, p in Parameter(2))p
julia> @constraint(model, x >= p)x - p ≥ 0

Did they mean an affine constraint like 1.0 * x + 0.0 in GreaterThan(2.0), or did they mean a variable bound like x in GreaterThan(2.0)?

By default, ParametricOptInterface does not attempt to simplify affine constraints involving parameters to variable bounds, but this behavior can be controlled using the ConstraintsInterpretation attribute.

For example, the default is:

julia> using JuMP, HiGHS
julia> import ParametricOptInterface as POI
julia> model = Model(() -> POI.Optimizer(HiGHS.Optimizer()));
julia> set_attribute(model, POI.ConstraintsInterpretation(), POI.ONLY_CONSTRAINTS)
julia> @variable(model, x);
julia> @variable(model, p in Parameter(1));
julia> @constraint(model, x >= p)x - p ≥ 0
julia> optimize!(model)Running HiGHS 1.13.1 (git hash: 1d267d97c): Copyright (c) 2026 under Apache 2.0 license terms Using BLAS: blastrampoline LP has 1 row; 1 col; 1 nonzero Coefficient ranges: Matrix [1e+00, 1e+00] Cost [0e+00, 0e+00] Bound [0e+00, 0e+00] RHS [1e+00, 1e+00] Presolving model 0 rows, 0 cols, 0 nonzeros 0s 0 rows, 0 cols, 0 nonzeros 0s Presolve reductions: rows 0(-1); columns 0(-1); nonzeros 0(-1) - Reduced to empty Performed postsolve Solving the original LP from the solution after postsolve Model status : Optimal Objective value : 0.0000000000e+00 P-D objective error : 0.0000000000e+00 HiGHS run time : 0.00

but by setting the ConstraintsInterpretation attribute to POI.BOUNDS_AND_CONSTRAINTS, we can solve a problem with one decision variable and zero constraint rows:

julia> using JuMP, HiGHS
julia> import ParametricOptInterface as POI
julia> model = Model(() -> POI.Optimizer(HiGHS.Optimizer()));
julia> set_attribute(model, POI.ConstraintsInterpretation(), POI.BOUNDS_AND_CONSTRAINTS)
julia> @variable(model, x);
julia> @variable(model, p in Parameter(1));
julia> @constraint(model, x >= p)x - p ≥ 0
julia> optimize!(model)Running HiGHS 1.13.1 (git hash: 1d267d97c): Copyright (c) 2026 under Apache 2.0 license terms Using BLAS: blastrampoline LP has 0 rows; 1 col; 0 nonzeros Solving unconstrained LP Solving an unconstrained LP with 1 columns Model status : Optimal Objective value : 0.0000000000e+00 P-D objective error : 0.0000000000e+00 HiGHS run time : 0.00

Parameters multiplying quadratic terms

POI supports parameters that multiply quadratic variable terms in objectives only. This creates cubic polynomial expressions of the form c * p * x * y where c is a number, p is a parameter, and x and y are variables. After parameter substitution, the objective is quadratic instead of cubic.

Note that the maximum degree is 3 (cubic), at least one factor in each cubic term must be a parameter, and pure cubic variable terms (for example, x * y * z with no parameters) are not supported.

julia> using JuMP, HiGHS
julia> import ParametricOptInterface as POI
julia> model = Model(() -> POI.Optimizer(HiGHS.Optimizer()))A JuMP Model ├ solver: Parametric Optimizer with HiGHS attached ├ objective_sense: FEASIBILITY_SENSE ├ num_variables: 0 ├ num_constraints: 0 └ Names registered in the model: none
julia> set_silent(model)
julia> @variable(model, 0 <= x <= 10)x
julia> @variable(model, p in Parameter(2))p
julia> @objective(model, Min, p * x^2 - 3x)(p * (x²)) - (3 x)
julia> optimize!(model)
julia> value(x) # x = 3 / 2p = 0.750.7499999812500006
julia> set_parameter_value(p, 3)
julia> optimize!(model)
julia> value(x) # x = 3 / 2p = 0.50.4999999916666667