From b88dba9fb775585cde9a2dea09db9e519566e491 Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Mon, 16 Feb 2026 09:07:36 +1300 Subject: [PATCH 1/4] Add support for un-instantiated inner optimizers --- README.md | 2 +- benchmark/MOI_benchmarks.jl | 8 +- docs/Project.toml | 2 + docs/src/Examples/example.md | 453 ++++++++++++++++++++++++++++++++++ docs/src/manual.md | 114 +++++++++ src/MOI_wrapper.jl | 11 - src/ParametricOptInterface.jl | 78 ++++-- src/duals.jl | 2 +- test/test_JuMP.jl | 280 +++++---------------- test/test_MathOptInterface.jl | 76 ++---- 10 files changed, 721 insertions(+), 305 deletions(-) create mode 100644 docs/src/Examples/example.md create mode 100644 docs/src/manual.md diff --git a/README.md b/README.md index 58a9408e..eb11e2d3 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ Use ParametricOptInterface with JuMP by following this brief example: ```julia using JuMP, HiGHS import ParametricOptInterface as POI -model = direct_model(POI.Optimizer(HiGHS.Optimizer())) +model = direct_model(POI.Optimizer(HiGHS.Optimizer)) @variable(model, x) @variable(model, p in Parameter(1.0)) @constraint(model, cons, x + p >= 3) diff --git a/benchmark/MOI_benchmarks.jl b/benchmark/MOI_benchmarks.jl index ed354355..3dc91c22 100644 --- a/benchmark/MOI_benchmarks.jl +++ b/benchmark/MOI_benchmarks.jl @@ -165,11 +165,7 @@ function solve_moi( end function POI_OPTIMIZER() - return POI.Optimizer(SOLVER.Optimizer()) -end - -function MOI_OPTIMIZER() - return SOLVER.Optimizer() + return POI.Optimizer(SOLVER.Optimizer) end function solve_moi_loop( @@ -194,7 +190,7 @@ function solve_moi_loop( for _ in 1:loops solve_moi( data, - MOI_OPTIMIZER; + SOLVER.Optimizer(); vector_version = vector_version, params = params, ) diff --git a/docs/Project.toml b/docs/Project.toml index bd422b7d..b5f8bcfc 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -5,6 +5,7 @@ Ipopt = "b6b21f68-93f8-5de0-b562-5493be1d77c9" JuMP = "4076af6c-e467-56ae-b986-b466b2749572" MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee" ParametricOptInterface = "0ce4ce61-57bf-432b-a095-efac525d185e" +SCS = "c946c3f1-0d1f-5ce8-9dea-7daa1f7e2d13" [compat] Documenter = "1" @@ -12,3 +13,4 @@ HiGHS = "1" Ipopt = "1" JuMP = "1" MathOptInterface = "1.13.2" +SCS = "2" diff --git a/docs/src/Examples/example.md b/docs/src/Examples/example.md new file mode 100644 index 00000000..a928abfd --- /dev/null +++ b/docs/src/Examples/example.md @@ -0,0 +1,453 @@ +# Basic Examples + +## MOI example - step by step usage + +Let's write a step-by-step example of `POI` usage at the MOI level. + +First, we declare a [`ParametricOptInterface.Optimizer`](@ref) on top of a `MOI` +optimizer. In the example, we consider `HiGHS` as the underlying solver: + +```@example moi1 +import HiGHS +import MathOptInterface as MOI +import ParametricOptInterface as POI +optimizer = POI.Optimizer(HiGHS.Optimizer) +``` + +We declare the variable `x` as in a typical `MOI` model, and we add a +non-negativity constraint: + +```@example moi1 +x = MOI.add_variables(optimizer, 2) +for x_i in x + MOI.add_constraint(optimizer, x_i, MOI.GreaterThan(0.0)) +end +``` + +Now, let's consider 3 `MOI.Parameter`. Two of them, `y`, `z`, will be placed in +the constraints and one, `w`, in the objective function. We'll start all three +of them with a value equal to `0`: + +```@example moi1 +w, cw = MOI.add_constrained_variable(optimizer, MOI.Parameter(0.0)) +y, cy = MOI.add_constrained_variable(optimizer, MOI.Parameter(0.0)) +z, cz = MOI.add_constrained_variable(optimizer, MOI.Parameter(0.0)) +``` + +Let's add the constraints. Notice that we treat parameters and variables in the +same way when building the functions that will be placed in some set to create a +constraint (`Function-in-Set`): + +```@example moi1 +cons1 = MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([2.0, 1.0, 3.0], [x[1], x[2], y]), 0.0) +ci1 = MOI.add_constraint(optimizer, cons1, MOI.LessThan(4.0)) +cons2 = MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([1.0, 2.0, 0.5], [x[1], x[2], z]), 0.0) +ci2 = MOI.add_constraint(optimizer, cons2, MOI.LessThan(4.0)) +``` + +Finally, we declare and add the objective function, with its respective sense: + +```@example moi1 +obj_func = MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([4.0, 3.0, 2.0], [x[1], x[2], w]), 0.0) +MOI.set(optimizer, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), obj_func) +MOI.set(optimizer, MOI.ObjectiveSense(), MOI.MAX_SENSE) +``` + +Now we can optimize the model and assess its termination and primal status: + +```@example moi1 +MOI.optimize!(optimizer) +MOI.get(optimizer, MOI.TerminationStatus()) +MOI.get(optimizer, MOI.PrimalStatus()) +``` + +Given the optimized solution, we check that its value is, as expected, equal to +`28/3`, and the solution vector `x` is `[4/3, 4/3]`: + +```@example moi1 +isapprox(MOI.get(optimizer, MOI.ObjectiveValue()), 28/3, atol = 1e-4) +isapprox(MOI.get(optimizer, MOI.VariablePrimal(), x[1]), 4/3, atol = 1e-4) +isapprox(MOI.get(optimizer, MOI.VariablePrimal(), x[2]), 4/3, atol = 1e-4) +``` + +We can also retrieve the dual values associated to each parameter, +**as they are all additive**: + +```@example moi1 +MOI.get(optimizer, MOI.ConstraintDual(), cy) +MOI.get(optimizer, MOI.ConstraintDual(), cz) +MOI.get(optimizer, MOI.ConstraintDual(), cw) +``` + +Notice the direct relationship in this case between the parameters' duals and +the associated constraints' duals. + +The `y` parameter, for example, only appears in the `cons1`. If we compare +their duals, we can check that the dual of `y` is equal to its coefficient in +`cons1` multiplied by the constraint's dual itself, as expected: + +```@example moi1 +isapprox( + MOI.get(optimizer, MOI.ConstraintDual(), cy), + 3*MOI.get(optimizer, MOI.ConstraintDual(), ci1); + atol = 1e-4, +) +``` + +The same is valid for the remaining parameters. In case a parameter appears in +more than one constraint, or both some constraints and in the objective +function, its dual will be equal to the linear combination of the functions' +duals multiplied by the respective coefficients. + +So far, we only added some parameters that had no influence at first in solving +the model. Let's change the values associated to each parameter to assess its +implications. + +First, we set the value of parameters `y` and `z` to `1.0`. Notice that we are +changing the feasible set of the decision variables: + +```@example moi1 +MOI.set(optimizer, POI.ParameterValue(), y, 1.0) +MOI.set(optimizer, POI.ParameterValue(), z, 1.0) +``` + +However, if we check the optimized model now, there will be no changes in the +objective function value or the in the optimized decision variables: + +```@example moi1 +isapprox.(MOI.get(optimizer, MOI.ObjectiveValue()), 28/3, atol = 1e-4) +isapprox.(MOI.get(optimizer, MOI.VariablePrimal(), x[1]), 4/3, atol = 1e-4) +isapprox.(MOI.get(optimizer, MOI.VariablePrimal(), x[2]), 4/3, atol = 1e-4) +``` + +Although we changed the parameter values, we didn't optimize the model yet. +Thus, **to apply the parameters' changes, the model must be optimized again**: + +```@example moi1 +MOI.optimize!(optimizer) +``` + +The `MOI.optimize!()` function handles the necessary updates, properly fowarding +the new outer model (`POI` model) additions to the inner model (`MOI` model) +which will be handled by the solver. Now we can assess the updated optimized +information: + +```@example moi1 +isapprox.(MOI.get(optimizer, MOI.ObjectiveValue()), 3.0, atol = 1e-4) +MOI.get.(optimizer, MOI.VariablePrimal(), x) == [0.0, 1.0] +``` + +If we update the parameter `w`, associated to the objective function, we are +simply adding a constant to it. Notice how the new objective function is +precisely equal to the previous one plus the new value of `w`. In addition, as +we didn't update the feasible set, the optimized decision variables remain the +same. + +```@example moi1 +MOI.set(optimizer, POI.ParameterValue(), w, 2.0) +# Once again, the model must be optimized to incorporate the changes +MOI.optimize!(optimizer) +# Only the objective function value changes +isapprox.(MOI.get(optimizer, MOI.ObjectiveValue()), 7.0, atol = 1e-4) +MOI.get.(optimizer, MOI.VariablePrimal(), x) == [0.0, 1.0] +``` + +## JuMP Example - step by step usage + +Let's write a step-by-step example of `POI` usage at the JuMP level. + +First, we declare a `Model` on top of a `Optimizer` of an underlying solver. In +the example, we consider `HiGHS` as the underlying solver: + +```@example jump1 +using HiGHS +using JuMP + +using ParametricOptInterface +const POI = ParametricOptInterface + +model = Model(() -> ParametricOptInterface.Optimizer(HiGHS.Optimizer)) +``` + +We declare the variable `x` as in a typical `JuMP` model: + +```@example jump1 +@variable(model, x[i = 1:2] >= 0) +``` + +Now, let's consider 3 `MOI.Parameter`. Two of them, `y`, `z`, will be placed in +the constraints and one, `w`, in the objective function. We'll start all three +of them with a value equal to `0`: + +```@example jump1 +@variable(model, y in MOI.Parameter(0.0)) +@variable(model, z in MOI.Parameter(0.0)) +@variable(model, w in MOI.Parameter(0.0)) +``` + +Let's add the constraints. Notice that we treat parameters the same way we treat +variables when writing the model: + +```@example jump1 +@constraint(model, c1, 2x[1] + x[2] + 3y <= 4) +@constraint(model, c2, x[1] + 2x[2] + 0.5z <= 4) +``` + +Finally, we declare and add the objective function, with its respective sense: + +```@example jump1 +@objective(model, Max, 4x[1] + 3x[2] + 2w) +``` + +We can optimize the model and assess its termination and primal status: + +```@example jump1 +optimize!(model) +termination_status(model) +primal_status(model) +``` + +Given the optimized solution, we check that its value is, as expected, equal to +`28/3`, and the solution vector `x` is `[4/3, 4/3]`: + +```@example jump1 +isapprox(objective_value(model), 28/3) +isapprox(value.(x), [4/3, 4/3]) +``` + +We can also retrieve the dual values associated to each parameter, **as they are all additive**: + +```@example jump1 +MOI.get(model, POI.ParameterDual(), y) +MOI.get(model, POI.ParameterDual(), z) +MOI.get(model, POI.ParameterDual(), w) +``` + +Notice the direct relationship in this case between the parameters' duals and the associated constraints' duals. The `y` parameter, for example, only appears in the `c1`. If we compare their duals, we can check that the dual of `y` is equal to its coefficient in `c1` multiplied by the constraint's dual itself, as expected: + +```@example jump1 +dual_of_y = MOI.get(model, POI.ParameterDual(), y) +isapprox(dual_of_y, 3 * dual(c1)) +``` + +The same is valid for the remaining parameters. In case a parameter appears in more than one constraint, or both some constraints and in the objective function, its dual will be equal to the linear combination of the functions' duals multiplied by the respective coefficients. + +So far, we only added some parameters that had no influence at first in solving the model. Let's change the values associated to each parameter to assess its implications. First, we set the value of parameters `y` and `z` to `1.0`. Notice that we are changing the feasible set of the decision variables: + +```@example jump1 +MOI.set(model, POI.ParameterValue(), y, 1) +MOI.set(model, POI.ParameterValue(), z, 1) +# We can also query the value in the parameters +MOI.get(model, POI.ParameterValue(), y) +MOI.get(model, POI.ParameterValue(), z) +``` + +To apply the parameters' changes, the model must be optimized again: + +```@example jump1 +optimize!(model) +``` + +The `optimize!` function handles the necessary updates, properly fowarding the new outer model (`POI` model) additions to the inner model (`MOI` model) which will be handled by the solver. Now we can assess the updated optimized information: + +```@example jump1 +isapprox(objective_value(model), 3) +isapprox(value.(x), [0, 1]) +``` + +If we update the parameter `w`, associated to the objective function, we are simply adding a constant to it. Notice how the new objective function is precisely equal to the previous one plus the new value of `w`. In addition, as we didn't update the feasible set, the optimized decision variables remain the same. + +```@example jump1 +MOI.set(model, POI.ParameterValue(), w, 2) +# Once again, the model must be optimized to incorporate the changes +optimize!(model) +# Only the objective function value changes +isapprox(objective_value(model), 7) +isapprox(value.(x), [0, 1]) +``` + +## JuMP Example - Declaring vectors of parameters + +Many times it is useful to declare a vector of parameters just like we declare a vector of variables, the JuMP syntax for variables works with parameters too: + + +```@example jump2 +using JuMP +import HiGHS +import ParametricOptInterface as POI +model = Model(() -> ParametricOptInterface.Optimizer(HiGHS.Optimizer)) +@variable(model, x[i = 1:3] >= 0) +@variable(model, p1[i = 1:3] in MOI.Parameter(0.0)) +@variable(model, p2[i = 1:3] in MOI.Parameter.([1, 10, 45])) +@variable(model, p3[i = 1:3] in MOI.Parameter.(ones(3))) +``` + +## JuMP Example - Dealing with parametric expressions as variable bounds + +A very common pattern that appears when using ParametricOptInterface is to add variable and later add some expression with parameters that represent the variable bound. The following code illustrates the pattern: + +```@example jump3 +using HiGHS +using JuMP +using ParametricOptInterface +const POI = ParametricOptInterface + +model = direct_model(POI.Optimizer(HiGHS.Optimizer)) +@variable(model, x) +@variable(model, p in MOI.Parameter(0.0)) +@constraint(model, x >= p) +``` + +Since parameters are treated like variables JuMP lowers this to MOI as `x - p >= 0` which is not a variable bound but a linear constraint.This means that the current representation of this problem at the solver level is: + +```math +\begin{align} + & \min_{x} & 0 + \\ + & \;\;\text{s.t.} & x & \in \mathbb{R} \\ + & & x - p & \geq 0 +\end{align} +``` + +This behaviour might be undesirable because it creates extra rows in your problem. Users can set the [`ParametricOptInterface.ConstraintsInterpretation`](@ref) to control how the linear constraints should be interpreted. The pattern advised for users seeking the most performance out of ParametricOptInterface should use the followig pattern: + +```@example jump3 +using HiGHS +using JuMP +using ParametricOptInterface +const POI = ParametricOptInterface + +model = direct_model(POI.Optimizer(HiGHS.Optimizer)) +@variable(model, x) +@variable(model, p in MOI.Parameter(0.0)) + +# Indicate that all the new constraints will be valid variable bounds +MOI.set(model, POI.ConstraintsInterpretation(), POI.ONLY_BOUNDS) +@constraint(model, x >= p) +# The name of this constraint was different to inform users that this is a +# variable bound. + +# Indicate that all the new constraints will not be variable bounds +MOI.set(model, POI.ConstraintsInterpretation(), POI.ONLY_CONSTRAINTS) +# @constraint(model, ...) +``` + +This way the mathematical representation of the problem will be: + +```math +\begin{align} + & \min_{x} & 0 + \\ + & \;\;\text{s.t.} & x & \geq p +\end{align} +``` + +which might lead to faster solves. + +Users that just want everything to work can use the default value `POI.ONLY_CONSTRAINTS` or try to use `POI.BOUNDS_AND_CONSTRAINTS` and leave it to ParametricOptInterface to interpret the constraints as bounds when applicable and linear constraints otherwise. + +## MOI Example - Parameters multiplying Quadratic terms + +Let's start with a simple quadratic problem + +```@example moi2 +import Ipopt +import MathOptInterface as MOI +import ParametricOptInterface as POI +model = POI.Optimizer(Ipopt.Optimizer) +x, _ = MOI.add_constrained_variable(model, MOI.GreaterThan(0.0)) +y, _ = MOI.add_constrained_variable(model, MOI.GreaterThan(0.0)) +ci1 = MOI.add_constraint(model, 2.0 * x + 1.0 * y, MOI.LessThan(4.0)) +ci2 = MOI.add_constraint(model, 1.0 * x + 2.0 * y, MOI.LessThan(4.0)) +MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) +obj_func = 1.0 * x * x + 1.0 * y * y +MOI.set(model, MOI.ObjectiveFunction{typeof(obj_func)}(), obj_func) +``` + +To multiply a parameter in a quadratic term, the user will +need to use the `POI.QuadraticObjectiveCoef` model attribute. + +```@example moi2 +p, _ = MOI.add_constrained_variable.(model, MOI.Parameter(1.0)) +MOI.set(model, POI.QuadraticObjectiveCoef(), (x, y), p) +``` + +This function will add the term `p*xy` to the objective function. +It's also possible to multiply a scalar affine function to the quadratic term. + +```@example moi2 +MOI.set(model, POI.QuadraticObjectiveCoef(), (x, y), 2 * p + 3) +``` + +This will set the term `(2p+3)*xy` to the objective function (it overwrites the +last set). Then, just optimize the model. + +```@example moi2 +MOI.optimize!(model) +isapprox(MOI.get(model, MOI.ObjectiveValue()), 32/3, atol=1e-4) +isapprox(MOI.get(model, MOI.VariablePrimal(), x), 4/3, atol=1e-4) +isapprox(MOI.get(model, MOI.VariablePrimal(), y), 4/3, atol=1e-4) +``` + +To change the parameter just set `POI.ParameterValue` and optimize again. + +```@example moi2 +MOI.set(model, POI.ParameterValue(), p, 2.0) +MOI.optimize!(model) +isapprox(MOI.get(model, MOI.ObjectiveValue()), 128/9, atol=1e-4) +isapprox(MOI.get(model, MOI.VariablePrimal(), x), 4/3, atol=1e-4) +isapprox(MOI.get(model, MOI.VariablePrimal(), y), 4/3, atol=1e-4) +``` + +## JuMP Example - Parameters multiplying Quadratic terms + +Let's get the same MOI example + +```@example jump4 +using Ipopt +using JuMP +using ParametricOptInterface +const POI = ParametricOptInterface + +optimizer = POI.Optimizer(Ipopt.Optimizer) +model = direct_model(optimizer) + +@variable(model, x >= 0) +@variable(model, y >= 0) +@variable(model, p in MOI.Parameter(1.0)) +@constraint(model, 2x + y <= 4) +@constraint(model, x + 2y <= 4) +@objective(model, Max, (x^2 + y^2)/2) +``` + +We use the same MOI function to add the parameter multiplied to the quadratic term. + +```@example jump4 +MOI.set(backend(model), POI.QuadraticObjectiveCoef(), (index(x),index(y)), 2index(p)+3) +``` + +If the user print the `model`, the term `(2p+3)*xy` won't show. +It's possible to retrieve the parametric function multiplying the term `xy` with `MOI.get`. + +```@example jump4 +MOI.get(backend(model), POI.QuadraticObjectiveCoef(), (index(x),index(y))) +``` + +Then, just optimize the model + +```@example jump4 +optimize!(model) +isapprox(objective_value(model), 32/3, atol=1e-4) +isapprox(value(x), 4/3, atol=1e-4) +isapprox(value(y), 4/3, atol=1e-4) +``` + +To change the parameter just set `POI.ParameterValue` and optimize again. + +```@example jump4 +MOI.set(model, POI.ParameterValue(), p, 2.0) +optimize!(model) +isapprox(objective_value(model), 128/9, atol=1e-4) +isapprox(value(x), 4/3, atol=1e-4) +isapprox(value(y), 4/3, atol=1e-4) +``` diff --git a/docs/src/manual.md b/docs/src/manual.md new file mode 100644 index 00000000..8c0f47d1 --- /dev/null +++ b/docs/src/manual.md @@ -0,0 +1,114 @@ +```@meta +CurrentModule = ParametricOptInterface +``` + +# Manual + +## Why use parameters? + +A typical optimization model built using `MathOptInterface.jl` (`MOI`for short) +has two main components: + +1. Variables +2. Constants + +Using these basic elements, one can create functions and sets that, together, +form the desired optimization model. The goal of `POI` is the implementation of +a third type, parameters, which: + +* are declared similar to a variable, and inherits some functionalities (for + example, dual calculation) +* acts like a constant, in the sense that it has a fixed value that will remain + the same unless explicitely changed by the user + +A main concern is to efficiently implement this new type, as one typical usage +is to change its value to analyze the model behavior, without the need to build +a new one from scratch. + +## How it works + +The main idea applied in POI is that the interaction between the solver, for +example `HiGHS`, and the optimization model will be handled by `MOI` as usual. + +Because of that, `POI` is a higher level wrapper around `MOI`, responsible for +receiving variables, constants and parameters, and forwarding to the lower level +model only variables and constants. + +As `POI` receives parameters, it must analyze and decide how they should be +handled on the lower level optimization model (the `MOI` model). + +## Usage + +In this manual we describe how to interact with the optimization model at the +MOI level. In the _Examples_ section you can find some tutorials with the +JuMP usage. + +### Supported constraints + +This is a list of supported `MOI` constraint functions that can handle +parameters. If you try to add a parameter to a function that is not listed here, +it will return an unsupported error. + +| MOI Function | +| :------------------------ | +| `ScalarAffineFunction` | +| `ScalarQuadraticFunction` | +| `VectorAffineFunction` | + + +### Supported objective functions + +| MOI Function | +| :------------------------ | +| `ScalarAffineFunction` | +| `ScalarQuadraticFunction` | + +### Declare a Optimizer + +In order to use parameters, the user needs to declare an +[`Optimizer`](@ref) on top of a `MOI` optimizer, such as `HiGHS.Optimizer`. + +```@repl manual +import ParametricOptInterface as POI +import HiGHS +optimizer = POI.Optimizer(HiGHS.Optimizer) +``` + +### Parameters + +A `MOI.Parameter` is a set used to define a variable with a fixed value that +can be changed by the user. It is analogous to `MOI.EqualTo`, but can be used +by special methods like the ones in this package to remove the fixed variable +from the optimization problem. This permits the usage of multiplicative +parameters in linear models and might speedup solves since the number of +variables is reduced. + +### Adding a new parameter to a model + +To add a parameter to a model, we must use the `MOI.add_constrained_variable` +function, passing as its arguments the model and a `MOI.Parameter` with its +given value: + +```@repl manual +y, cy = MOI.add_constrained_variable(optimizer, MOI.Parameter(0.0)) +``` + +### Changing the parameter value + +To change a given parameter's value, access its `VariableIndex` and set it to +the new value using the `MOI.Parameter` structure. + +```@repl manual +MOI.set(optimizer, POI.ParameterValue(), y, MOI.Parameter(2.0)) +``` + +### Retrieving the dual of a parameter + +Given an optimized model, one can compute the dual associated to a parameter, +**as long as it is an additive term in the constraints or objective**. + +One can do so by getting the `MOI.ConstraintDual` attribute of the parameter's +`MOI.ConstraintIndex`: +```julia +julia> MOI.get(optimizer, POI.ParameterDual(), y) +``` diff --git a/src/MOI_wrapper.jl b/src/MOI_wrapper.jl index 6352876d..53200f03 100644 --- a/src/MOI_wrapper.jl +++ b/src/MOI_wrapper.jl @@ -1590,17 +1590,6 @@ function MOI.set( return MOI.set(model.optimizer, attr, optimizer_ci, val) end -function MOI.set( - model::Optimizer, - attr::MOI.AbstractConstraintAttribute, - c::MOI.ConstraintIndex{MOI.VariableIndex,MOI.Parameter{T}}, - val, -) where {T} - return error( - "Constraint attribute $attr cannot be set for $c in ParametricOptInterface.", - ) -end - function MOI.get( model::Optimizer, ::MOI.ConstraintPrimal, diff --git a/src/ParametricOptInterface.jl b/src/ParametricOptInterface.jl index 8ebe027f..10e18a26 100644 --- a/src/ParametricOptInterface.jl +++ b/src/ParametricOptInterface.jl @@ -81,28 +81,63 @@ const DoubleDictInner{F,S,T} = MOI.Utilities.DoubleDicts.DoubleDictInner{F,S,T} include("parametric_functions.jl") """ - Optimizer{T, OT <: MOI.ModelLike} <: MOI.AbstractOptimizer + Optimizer{T}( + optimizer::Union{MOI.ModelLike,Any}; + evaluate_duals::Bool = true, + save_original_objective_and_constraints::Bool = true, + with_bridge_type = nothing, + ) -Declares a `Optimizer`, which allows the handling of parameters in a +Create an `Optimizer`, which allows the handling of parameters in an optimization model. +If `optimizer` is not a `MOI.ModelLike,` the inner optimizer is constructed +using `MOI.instantiate(optimizer; with_bridge_type)`. + +The `{T}` type parameter is optional; it defaults to `Float64`. + ## Keyword arguments -- `evaluate_duals::Bool`: If `true`, evaluates the dual of parameters. Users might want to set it to `false` - to increase performance when the duals of parameters are not necessary. Defaults to `true`. +- `evaluate_duals::Bool`: If `true`, evaluates the dual of parameters. Set it to + `false` to increase performance when the duals of parameters are not + necessary. Defaults to `true`. + +- `save_original_objective_and_constraints`: If `true` saves the orginal + function and set of the constraints as well as the original objective function + inside [`Optimizer`](@ref). This is useful for printing the model but greatly + increases the memory footprint. Users might want to set it to `false` to + increase performance in applications where you don't need to query the + original expressions provided to the model in constraints or in the objective. + Note that this might break printing or queries such as + `MOI.get(model, MOI.ConstraintFunction(), c)`. Defaults to `true`. -- `save_original_objective_and_constraints`: If `true` saves the orginal function and set of the constraints - as well as the original objective function inside [`Optimizer`](@ref). This is useful for printing the model - but greatly increases the memory footprint. Users might want to set it to `false` to increase performance - in applications where you don't need to query the original expressions provided to the model in constraints - or in the objective. Note that this might break printing or queries such as `MOI.get(model, MOI.ConstraintFunction(), c)`. - Defaults to `true`. +- `with_bridge_type`: this is ignroed if `optimizer::MOI.ModelLike`, otherwise + it is passed to `MOI.instantiate`. ## Example ```julia-repl -julia> ParametricOptInterface.Optimizer(HiGHS.Optimizer()) -ParametricOptInterface.Optimizer{Float64,HiGHS.Optimizer} +julia> import ParametricOptInterface as POI + +julia> import HiGHS + +julia> POI.Optimizer(HiGHS.Optimizer(); evaluate_duals = true) +ParametricOptInterface.Optimizer{Float64, HiGHS.Optimizer} +├ ObjectiveSense: FEASIBILITY_SENSE +├ ObjectiveFunctionType: MOI.ScalarAffineFunction{Float64} +├ NumberOfVariables: 0 +└ NumberOfConstraints: 0 + +julia> POI.Optimizer( + HiGHS.Optimizer; + with_bridge_type = Float64, + evaluate_duals = false, + ) +ParametricOptInterface.Optimizer{Float64, MOIB.LazyBridgeOptimizer{HiGHS.Optimizer}} +├ ObjectiveSense: FEASIBILITY_SENSE +├ ObjectiveFunctionType: MOI.ScalarAffineFunction{Float64} +├ NumberOfVariables: 0 +└ NumberOfConstraints: 0 ``` """ mutable struct Optimizer{T,OT<:MOI.ModelLike} <: MOI.AbstractOptimizer @@ -183,11 +218,12 @@ mutable struct Optimizer{T,OT<:MOI.ModelLike} <: MOI.AbstractOptimizer # extension data ext::Dict{Symbol,Any} + function Optimizer{T}( optimizer::OT; evaluate_duals::Bool = true, save_original_objective_and_constraints::Bool = true, - ) where {T,OT} + ) where {T,OT<:MOI.ModelLike} return new{T,OT}( optimizer, MOI.Utilities.CleverDicts.CleverDict{ParameterIndex,T}( @@ -249,9 +285,23 @@ mutable struct Optimizer{T,OT<:MOI.ModelLike} <: MOI.AbstractOptimizer Dict{Symbol,Any}(), ) end + end -Optimizer(args...; kws...) = Optimizer{Float64}(args...; kws...) +Optimizer(arg; kwargs...) = Optimizer{Float64}(arg; kwargs...) + +function Optimizer{T}( + optimizer_fn; + with_bridge_type = nothing, + kwargs..., +) where {T} + inner = MOI.instantiate(optimizer_fn; with_bridge_type) + if !MOI.supports_incremental_interface(inner) + cache = MOI.default_cache(inner, T) + inner = MOI.Utilities.CachingOptimizer(cache, inner) + end + return Optimizer{T}(inner; kwargs...) +end function _next_parameter_index!(model::Optimizer) return model.last_parameter_index_added += 1 diff --git a/src/duals.jl b/src/duals.jl index b01633df..2f379a6c 100644 --- a/src/duals.jl +++ b/src/duals.jl @@ -149,7 +149,7 @@ function MOI.get( cp::MOI.ConstraintIndex{MOI.VariableIndex,MOI.Parameter{T}}, ) where {T} if !model.evaluate_duals - msg = "$attr not available when evaluate_duals is set to false. Create an optimizer such as `POI.Optimizer(HiGHS.Optimizer(); evaluate_duals = true)` to enable this feature." + msg = "$attr not available when evaluate_duals is set to false. Create an optimizer such as `POI.Optimizer(HiGHS.Optimizer; evaluate_duals = true)` to enable this feature." throw(MOI.GetAttributeNotAllowed(attr, msg)) elseif !_is_additive(model, cp) msg = "Cannot compute the dual of a multiplicative parameter" diff --git a/test/test_JuMP.jl b/test/test_JuMP.jl index 8c3591b9..71572806 100644 --- a/test/test_JuMP.jl +++ b/test/test_JuMP.jl @@ -32,7 +32,7 @@ function canonical_compare(f1, f2) end function test_jump_direct_affine_parameters() - optimizer = POI.Optimizer(HiGHS.Optimizer()) + optimizer = POI.Optimizer(HiGHS.Optimizer) model = direct_model(optimizer) @variable(model, x[i=1:2] >= 0) @variable(model, y in MOI.Parameter(0.0)) @@ -55,7 +55,7 @@ function test_jump_direct_affine_parameters() end function test_jump_direct_parameter_times_variable() - optimizer = POI.Optimizer(HiGHS.Optimizer()) + optimizer = POI.Optimizer(HiGHS.Optimizer) model = direct_model(optimizer) @variable(model, x[i=1:2] >= 0) @variable(model, y in MOI.Parameter(0.0)) @@ -78,7 +78,7 @@ function test_jump_direct_parameter_times_variable() end function test_jump_affine_parameters() - model = Model(() -> POI.Optimizer(HiGHS.Optimizer())) + model = Model(() -> POI.Optimizer(HiGHS.Optimizer)) @variable(model, x[i=1:2] >= 0) @variable(model, y in MOI.Parameter(0.0)) @variable(model, w in MOI.Parameter(0.0)) @@ -100,7 +100,7 @@ function test_jump_affine_parameters() end function test_jump_parameter_times_variable() - model = Model(() -> POI.Optimizer(HiGHS.Optimizer())) + model = Model(() -> POI.Optimizer(HiGHS.Optimizer)) @variable(model, x[i=1:2] >= 0) @variable(model, y in MOI.Parameter(0.0)) @variable(model, w in MOI.Parameter(0.0)) @@ -280,7 +280,7 @@ function test_jump_constraintfunction_getter() end function test_jump_interpret_parameteric_bounds() - model = Model(() -> POI.Optimizer(HiGHS.Optimizer())) + model = Model(() -> POI.Optimizer(HiGHS.Optimizer)) MOI.set(model, POI.ConstraintsInterpretation(), POI.ONLY_BOUNDS) @variable(model, x[i=1:2]) @variable(model, p[i=1:2] in MOI.Parameter.(-1.0)) @@ -309,7 +309,7 @@ function test_jump_interpret_parameteric_bounds() end function test_jump_interpret_parameteric_bounds_expression() - model = Model(() -> POI.Optimizer(HiGHS.Optimizer())) + model = Model(() -> POI.Optimizer(HiGHS.Optimizer)) MOI.set(model, POI.ConstraintsInterpretation(), POI.ONLY_BOUNDS) @variable(model, x[i=1:2]) @variable(model, p[i=1:2] in MOI.Parameter.(-1.0)) @@ -338,7 +338,7 @@ function test_jump_interpret_parameteric_bounds_expression() end function test_jump_direct_interpret_parameteric_bounds() - model = direct_model(POI.Optimizer(HiGHS.Optimizer())) + model = direct_model(POI.Optimizer(HiGHS.Optimizer)) MOI.set(model, POI.ConstraintsInterpretation(), POI.ONLY_BOUNDS) @variable(model, x[i=1:2]) @variable(model, p[i=1:2] in MOI.Parameter.(-1.0)) @@ -365,7 +365,7 @@ function test_jump_direct_interpret_parameteric_bounds() end function test_jump_direct_interpret_parameteric_bounds_no_interpretation() - model = direct_model(POI.Optimizer(HiGHS.Optimizer())) + model = direct_model(POI.Optimizer(HiGHS.Optimizer)) MOI.set(model, POI.ConstraintsInterpretation(), POI.ONLY_CONSTRAINTS) @variable(model, x[i=1:2]) @variable(model, p[i=1:2] in MOI.Parameter.(-1.0)) @@ -395,7 +395,7 @@ function test_jump_direct_interpret_parameteric_bounds_no_interpretation() end function test_jump_direct_interpret_parameteric_bounds_change() - model = direct_model(POI.Optimizer(HiGHS.Optimizer())) + model = direct_model(POI.Optimizer(HiGHS.Optimizer)) MOI.set(model, POI.ConstraintsInterpretation(), POI.ONLY_BOUNDS) @variable(model, x[i=1:2]) @variable(model, p[i=1:2] in MOI.Parameter.(-1.0)) @@ -413,7 +413,7 @@ function test_jump_direct_interpret_parameteric_bounds_change() end function test_jump_direct_interpret_parameteric_bounds_both() - model = direct_model(POI.Optimizer(HiGHS.Optimizer())) + model = direct_model(POI.Optimizer(HiGHS.Optimizer)) MOI.set(model, POI.ConstraintsInterpretation(), POI.BOUNDS_AND_CONSTRAINTS) @variable(model, x[i=1:2]) @variable(model, p[i=1:2] in MOI.Parameter.(-1.0)) @@ -429,7 +429,7 @@ function test_jump_direct_interpret_parameteric_bounds_both() end function test_jump_direct_interpret_parameteric_bounds_invalid() - model = direct_model(POI.Optimizer(HiGHS.Optimizer())) + model = direct_model(POI.Optimizer(HiGHS.Optimizer)) MOI.set(model, POI.ConstraintsInterpretation(), POI.ONLY_BOUNDS) @variable(model, x[i=1:2]) @variable(model, p[i=1:2] in MOI.Parameter.(-1.0)) @@ -442,17 +442,9 @@ function test_jump_direct_interpret_parameteric_bounds_invalid() end function test_jump_set_variable_start_value() - cached = MOI.Bridges.full_bridge_optimizer( - MOI.Utilities.CachingOptimizer( - MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()), - HiGHS.Optimizer(), - ), - Float64, - ) - optimizer = POI.Optimizer(cached) - model = direct_model(optimizer) + model = direct_model(POI.Optimizer(HiGHS.Optimizer)) @variable(model, x >= 0) - @variable(model, p in MOI.Parameter(0.0)) + @variable(model, p in Parameter(0.0)) set_start_value(x, 1.0) @test start_value(x) == 1 @test_throws ErrorException( @@ -463,7 +455,7 @@ function test_jump_set_variable_start_value() end function test_jump_direct_get_parameter_value() - model = direct_model(POI.Optimizer(HiGHS.Optimizer())) + model = direct_model(POI.Optimizer(HiGHS.Optimizer)) @variable(model, x, lower_bound = 0.0, upper_bound = 10.0) @variable(model, y, binary = true) @variable(model, z, set = MOI.Parameter(10.0)) @@ -474,7 +466,7 @@ function test_jump_direct_get_parameter_value() end function test_jump_get_parameter_value() - model = Model(() -> POI.Optimizer(HiGHS.Optimizer())) + model = Model(() -> POI.Optimizer(HiGHS.Optimizer)) @variable(model, x, lower_bound = 0.0, upper_bound = 10.0) @variable(model, y, binary = true) @variable(model, z, set = MOI.Parameter(10)) @@ -485,15 +477,7 @@ function test_jump_get_parameter_value() end function test_jump_sdp_scalar_parameter() - cached = MOI.Bridges.full_bridge_optimizer( - MOI.Utilities.CachingOptimizer( - MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()), - SCS.Optimizer(), - ), - Float64, - ) - optimizer = POI.Optimizer(cached) - m = direct_model(optimizer) + m = Model(() -> POI.Optimizer(SCS.Optimizer)) set_silent(m) @variable(m, p in MOI.Parameter(0.0)) @variable(m, x[1:2, 1:2], Symmetric) @@ -508,15 +492,7 @@ function test_jump_sdp_scalar_parameter() end function test_jump_sdp_matrix_parameter() - cached = MOI.Bridges.full_bridge_optimizer( - MOI.Utilities.CachingOptimizer( - MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()), - SCS.Optimizer(), - ), - Float64, - ) - optimizer = POI.Optimizer(cached) - m = direct_model(optimizer) + m = Model(() -> POI.Optimizer(SCS.Optimizer)) set_silent(m) P1 = [1 2; 2 3] @variable(m, p[1:2, 1:2] in MOI.Parameter.(P1)) @@ -533,7 +509,7 @@ function test_jump_sdp_matrix_parameter() end function test_jump_dual_basic() - model = Model(() -> POI.Optimizer(HiGHS.Optimizer())) + model = Model(() -> POI.Optimizer(HiGHS.Optimizer)) @variable(model, x[1:2] in MOI.Parameter.(ones(2) .* 4.0)) @variable(model, y[1:6]) @constraint(model, ctr1, 3 * y[1] >= 2 - 7 * x[1]) @@ -547,7 +523,7 @@ function test_jump_dual_basic() end function test_jump_dual_multiplicative_fail() - model = Model(() -> POI.Optimizer(HiGHS.Optimizer())) + model = Model(() -> POI.Optimizer(HiGHS.Optimizer)) @variable(model, x) @variable(model, p in MOI.Parameter(1.0)) @constraint(model, cons, x * p >= 3) @@ -562,7 +538,7 @@ function test_jump_dual_multiplicative_fail() end function test_jump_dual_objective_min() - model = Model(() -> POI.Optimizer(HiGHS.Optimizer())) + model = Model(() -> POI.Optimizer(HiGHS.Optimizer)) @variable(model, x) @variable(model, p in MOI.Parameter(1.0)) @constraint(model, cons, x >= 3 * p) @@ -573,7 +549,7 @@ function test_jump_dual_objective_min() end function test_jump_dual_objective_max() - model = Model(() -> POI.Optimizer(HiGHS.Optimizer())) + model = Model(() -> POI.Optimizer(HiGHS.Optimizer)) @variable(model, x) @variable(model, p in MOI.Parameter(1.0)) @constraint(model, cons, x >= 3 * p) @@ -584,7 +560,7 @@ function test_jump_dual_objective_max() end function test_jump_dual_multiple_parameters_1() - model = Model(() -> POI.Optimizer(HiGHS.Optimizer())) + model = Model(() -> POI.Optimizer(HiGHS.Optimizer)) @variable(model, x[1:6] in MOI.Parameter.(ones(6) .* 4.0)) @variable(model, y[1:6] <= 0.0) @constraint(model, ctr1, 3 * y[1] >= 2 - 7 * x[3]) @@ -618,7 +594,7 @@ function test_jump_dual_multiple_parameters_1() end function test_jump_duals_LessThan() - model = Model(() -> POI.Optimizer(HiGHS.Optimizer())) + model = Model(() -> POI.Optimizer(HiGHS.Optimizer)) @variable(model, α in MOI.Parameter(-1.0)) @variable(model, x) cref = @constraint(model, x ≤ α) @@ -637,7 +613,7 @@ function test_jump_duals_LessThan() end function test_jump_duals_EqualTo() - model = Model(() -> POI.Optimizer(HiGHS.Optimizer())) + model = Model(() -> POI.Optimizer(HiGHS.Optimizer)) @variable(model, α in MOI.Parameter(-1.0)) @variable(model, x) cref = @constraint(model, x == α) @@ -655,7 +631,7 @@ function test_jump_duals_EqualTo() end function test_jump_duals_GreaterThan() - model = Model(() -> POI.Optimizer(HiGHS.Optimizer())) + model = Model(() -> POI.Optimizer(HiGHS.Optimizer)) @variable(model, α in MOI.Parameter(1.0)) MOI.set(model, POI.ParameterValue(), α, -1.0) @variable(model, x) @@ -674,7 +650,7 @@ function test_jump_duals_GreaterThan() end function test_jump_dual_multiple_parameters_2() - model = Model(() -> POI.Optimizer(HiGHS.Optimizer())) + model = Model(() -> POI.Optimizer(HiGHS.Optimizer)) @variable(model, α[1:10] in MOI.Parameter.(ones(10))) @variable(model, x) cref = @constraint(model, x == sum(2 * α[i] for i in 1:10)) @@ -687,7 +663,7 @@ function test_jump_dual_multiple_parameters_2() end function test_jump_dual_mixing_params_and_vars_1() - model = Model(() -> POI.Optimizer(HiGHS.Optimizer())) + model = Model(() -> POI.Optimizer(HiGHS.Optimizer)) @variable(model, α[1:5] in MOI.Parameter.(ones(5))) @variable(model, x) cref = @constraint(model, sum(x for i in 1:5) == sum(2 * α[i] for i in 1:5)) @@ -700,7 +676,7 @@ function test_jump_dual_mixing_params_and_vars_1() end function test_jump_dual_mixing_params_and_vars_2() - model = Model(() -> POI.Optimizer(HiGHS.Optimizer())) + model = Model(() -> POI.Optimizer(HiGHS.Optimizer)) @variable(model, α[1:5] in MOI.Parameter.(ones(5))) @variable(model, x) cref = @constraint(model, 0.0 == sum(-x + 2 * α[i] for i in 1:5)) @@ -713,7 +689,7 @@ function test_jump_dual_mixing_params_and_vars_2() end function test_jump_dual_mixing_params_and_vars_3() - model = Model(() -> POI.Optimizer(HiGHS.Optimizer())) + model = Model(() -> POI.Optimizer(HiGHS.Optimizer)) @variable(model, α[1:5] in MOI.Parameter.(ones(5))) @variable(model, x) cref = @constraint(model, 0.0 == sum(-x + 2.0 + 2 * α[i] for i in 1:5)) @@ -726,7 +702,7 @@ function test_jump_dual_mixing_params_and_vars_3() end function test_jump_dual_add_after_solve() - model = Model(() -> POI.Optimizer(HiGHS.Optimizer())) + model = Model(() -> POI.Optimizer(HiGHS.Optimizer)) @variable(model, α in MOI.Parameter(1.0)) MOI.set(model, POI.ParameterValue(), α, -1.0) @variable(model, x) @@ -747,7 +723,7 @@ function test_jump_dual_add_after_solve() end function test_jump_dual_add_ctr_alaternative() - model = Model(() -> POI.Optimizer(HiGHS.Optimizer())) + model = Model(() -> POI.Optimizer(HiGHS.Optimizer)) @variable(model, α in MOI.Parameter(-1.0)) @variable(model, x) exp = x - α @@ -761,7 +737,7 @@ function test_jump_dual_add_ctr_alaternative() end function test_jump_dual_delete_constraint() - model = Model(() -> POI.Optimizer(HiGHS.Optimizer())) + model = Model(() -> POI.Optimizer(HiGHS.Optimizer)) @variable(model, α in MOI.Parameter(-1.0)) @variable(model, x) cref1 = @constraint(model, x ≤ α / 2) @@ -783,7 +759,7 @@ function test_jump_dual_delete_constraint() end function test_jump_dual_delete_constraint_2() - model = Model(() -> POI.Optimizer(HiGHS.Optimizer())) + model = Model(() -> POI.Optimizer(HiGHS.Optimizer)) @variable(model, α in MOI.Parameter(1.0)) @variable(model, β in MOI.Parameter(0.0)) @variable(model, x) @@ -882,7 +858,7 @@ function test_jump_dual_delete_constraint_3() end function test_jump_nlp() - model = Model(() -> POI.Optimizer(Ipopt.Optimizer())) + model = Model(() -> POI.Optimizer(Ipopt.Optimizer)) @variable(model, x) @variable(model, z in MOI.Parameter(10.0)) @constraint(model, x >= z) @@ -902,12 +878,7 @@ function test_jump_direct_vector_parameter_affine_nonnegatives() y* = t-2 obj = 2*t-3 """ - cached = MOI.Utilities.CachingOptimizer( - MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()), - SCS.Optimizer(), - ) - optimizer = POI.Optimizer(cached) - model = direct_model(optimizer) + model = Model(() -> POI.Optimizer(SCS.Optimizer)) set_silent(model) @variable(model, x) @variable(model, y) @@ -935,15 +906,7 @@ function test_jump_direct_vector_parameter_affine_nonpositives() y* = t-2 obj = 2*t-3 """ - cached = MOI.Bridges.full_bridge_optimizer( - MOI.Utilities.CachingOptimizer( - MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()), - SCS.Optimizer(), - ), - Float64, - ) - optimizer = POI.Optimizer(cached) - model = direct_model(optimizer) + model = Model(() -> POI.Optimizer(SCS.Optimizer)) set_silent(model) @variable(model, x) @variable(model, y) @@ -979,12 +942,7 @@ function test_jump_direct_soc_parameters() x* = p - 1/√2 y* = 1/√2 """ - cached = MOI.Utilities.CachingOptimizer( - MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()), - SCS.Optimizer(), - ) - optimizer = POI.Optimizer(cached) - model = direct_model(optimizer) + model = Model(() -> POI.Optimizer(SCS.Optimizer)) set_silent(model) @variable(model, x) @variable(model, y) @@ -1007,7 +965,7 @@ function test_jump_direct_soc_parameters() end function test_jump_direct_qp_objective() - optimizer = POI.Optimizer(Ipopt.Optimizer()) + optimizer = POI.Optimizer(Ipopt.Optimizer) model = direct_model(optimizer) MOI.set(model, MOI.Silent(), true) @variable(model, x >= 0) @@ -1098,16 +1056,7 @@ function test_jump_direct_rsoc_constraints() x* = 1/2*(max{1/√2,p}-p)^2 y* = max{1/√2,p} """ - cached = MOI.Bridges.full_bridge_optimizer( - MOI.Utilities.CachingOptimizer( - MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()), - SCS.Optimizer(), - ), - Float64, - ) - optimizer = POI.Optimizer(cached) - model = direct_model(optimizer) - MOI.set(model, MOI.Silent(), true) + model = Model(() -> POI.Optimizer(SCS.Optimizer)) @variable(model, x) @variable(model, y) @variable(model, t) @@ -1130,7 +1079,7 @@ function test_jump_direct_rsoc_constraints() end function test_jump_quadratic_interval() - optimizer = POI.Optimizer(HiGHS.Optimizer()) + optimizer = POI.Optimizer(HiGHS.Optimizer) # model = direct_model(optimizer) model = Model(() -> optimizer) MOI.set(model, MOI.Silent(), true) @@ -1155,16 +1104,8 @@ function test_jump_quadratic_interval() end function test_jump_quadratic_interval_cached() - cached = MOI.Bridges.full_bridge_optimizer( - MOI.Utilities.CachingOptimizer( - MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()), - HiGHS.Optimizer(), - ), - Float64, - ) - optimizer = POI.Optimizer(cached) - model = direct_model(optimizer) - # optimizer = POI.Optimizer(HiGHS.Optimizer()) + model = direct_model(POI.Optimizer(HiGHS.Optimizer)) + # optimizer = POI.Optimizer(HiGHS.Optimizer) # model = direct_model(optimizer) # model = Model(() -> optimizer) # MOI.set(model, MOI.Silent(), true) @@ -1189,7 +1130,7 @@ function test_jump_quadratic_interval_cached() end function test_affine_parametric_objective() - model = Model(() -> POI.Optimizer(HiGHS.Optimizer())) + model = Model(() -> POI.Optimizer(HiGHS.Optimizer)) @variable(model, p in MOI.Parameter(1.0)) @variable(model, 0 <= x <= 1) @objective(model, Max, (p + 0.5) * x) @@ -1200,7 +1141,7 @@ function test_affine_parametric_objective() end function test_abstract_optimizer_attributes() - model = Model(() -> POI.Optimizer(HiGHS.Optimizer())) + model = Model(() -> POI.Optimizer(HiGHS.Optimizer)) set_attribute(model, "time_limit", 60 * 1000.0) attr = MOI.RawOptimizerAttribute("time_limit") @test MOI.supports(unsafe_backend(model), attr) @@ -1209,7 +1150,7 @@ function test_abstract_optimizer_attributes() end function test_get_quadratic_constraint() - model = Model(() -> POI.Optimizer(HiGHS.Optimizer())) + model = Model(() -> POI.Optimizer(HiGHS.Optimizer)) @variable(model, x) @variable(model, p in Parameter(2.0)) @constraint(model, c, p * x <= 10) @@ -1219,7 +1160,7 @@ function test_get_quadratic_constraint() end function test_get_duals_from_multiplicative_parameters_1() - model = Model(() -> POI.Optimizer(HiGHS.Optimizer())) + model = Model(() -> POI.Optimizer(HiGHS.Optimizer)) @variable(model, x) @variable(model, p1 in Parameter(2.0)) @variable(model, p2 in Parameter(2.0)) @@ -1233,7 +1174,7 @@ function test_get_duals_from_multiplicative_parameters_1() end function test_get_duals_from_multiplicative_parameters_2() - model = Model(() -> POI.Optimizer(HiGHS.Optimizer())) + model = Model(() -> POI.Optimizer(HiGHS.Optimizer)) @variable(model, x) @variable(model, p1 in Parameter(40.0)) @variable(model, p2 in Parameter(2.0)) @@ -1247,7 +1188,7 @@ function test_get_duals_from_multiplicative_parameters_2() end function test_get_duals_from_multiplicative_parameters_3() - model = Model(() -> POI.Optimizer(HiGHS.Optimizer())) + model = Model(() -> POI.Optimizer(HiGHS.Optimizer)) @variable(model, x) @variable(model, p in Parameter(4.0)) @constraint(model, c, 3 * x >= p * p) @@ -1259,7 +1200,7 @@ function test_get_duals_from_multiplicative_parameters_3() end function test_parameters_cannot_be_nan_1() - model = Model(() -> POI.Optimizer(HiGHS.Optimizer())) + model = Model(() -> POI.Optimizer(HiGHS.Optimizer)) @variable(model, x) @variable(model, p in Parameter(NaN)) @constraint(model, c, 3 * x >= p * p) @@ -1270,7 +1211,7 @@ function test_parameters_cannot_be_nan_1() end function test_parameters_cannot_be_nan_2() - optimizer = POI.Optimizer(HiGHS.Optimizer()) + optimizer = POI.Optimizer(HiGHS.Optimizer) model = direct_model(optimizer) @variable(model, x[1:2]) @test_throws AssertionError @variable(model, p in Parameter(NaN)) @@ -1282,7 +1223,7 @@ function test_parameters_cannot_be_nan_2() end function test_parameter_Cannot_be_inf_1() - model = Model(() -> POI.Optimizer(HiGHS.Optimizer())) + model = Model(() -> POI.Optimizer(HiGHS.Optimizer)) @variable(model, x) @variable(model, p in Parameter(Inf)) @constraint(model, c, 3 * x >= p * p) @@ -1293,7 +1234,7 @@ function test_parameter_Cannot_be_inf_1() end function test_parameter_Cannot_be_inf_2() - optimizer = POI.Optimizer(HiGHS.Optimizer()) + optimizer = POI.Optimizer(HiGHS.Optimizer) model = direct_model(optimizer) @variable(model, x[1:2]) @test_throws AssertionError @variable(model, p in Parameter(Inf)) @@ -1343,15 +1284,7 @@ function test_jump_psd_cone_with_parameter_pv() end function test_jump_psd_cone_with_parameter_pp() - cached = MOI.Bridges.full_bridge_optimizer( - MOI.Utilities.CachingOptimizer( - MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()), - SCS.Optimizer(), - ), - Float64, - ) - optimizer = POI.Optimizer(cached) - model = direct_model(optimizer) + model = Model(() -> POI.Optimizer(SCS.Optimizer)) @variable(model, x) @variable(model, p in MOI.Parameter(1.0)) # @constraint( @@ -1374,15 +1307,7 @@ function test_jump_psd_cone_with_parameter_pp() end function test_jump_psd_cone_with_parameter_p() - cached = MOI.Bridges.full_bridge_optimizer( - MOI.Utilities.CachingOptimizer( - MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()), - SCS.Optimizer(), - ), - Float64, - ) - optimizer = POI.Optimizer(cached) - model = direct_model(optimizer) + model = Model(() -> POI.Optimizer(SCS.Optimizer)) @variable(model, x) @variable(model, p in MOI.Parameter(1.0)) # @constraint(model, con, [[0 (x - p)]; [(x - p) 0]] in JuMP.PSDCone()) @@ -1401,15 +1326,7 @@ function test_jump_psd_cone_with_parameter_p() end function test_jump_psd_cone_with_parameter_pv_v_pv() - cached = MOI.Bridges.full_bridge_optimizer( - MOI.Utilities.CachingOptimizer( - MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()), - SCS.Optimizer(), - ), - Float64, - ) - optimizer = POI.Optimizer(cached) - model = direct_model(optimizer) + model = Model(() -> POI.Optimizer(SCS.Optimizer)) @variable(model, x) @variable(model, p in MOI.Parameter(1.0)) @constraint( @@ -1436,15 +1353,7 @@ function test_jump_psd_cone_with_parameter_pv_v_pv() end function test_jump_psd_cone_with_parameter_pp_v_pv() - cached = MOI.Bridges.full_bridge_optimizer( - MOI.Utilities.CachingOptimizer( - MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()), - SCS.Optimizer(), - ), - Float64, - ) - optimizer = POI.Optimizer(cached) - model = direct_model(optimizer) + model = Model(() -> POI.Optimizer(SCS.Optimizer)) @variable(model, x) @variable(model, p in MOI.Parameter(1.0)) @constraint( @@ -1464,15 +1373,7 @@ function test_jump_psd_cone_with_parameter_pp_v_pv() end function test_jump_psd_cone_with_parameter_p_v_pv() - cached = MOI.Bridges.full_bridge_optimizer( - MOI.Utilities.CachingOptimizer( - MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()), - SCS.Optimizer(), - ), - Float64, - ) - optimizer = POI.Optimizer(cached) - model = direct_model(optimizer) + model = Model(() -> POI.Optimizer(SCS.Optimizer)) @variable(model, x) @variable(model, p in MOI.Parameter(1.0)) @constraint( @@ -1491,15 +1392,7 @@ function test_jump_psd_cone_with_parameter_p_v_pv() end function test_jump_psd_cone_with_parameter_p_v_pp() - cached = MOI.Bridges.full_bridge_optimizer( - MOI.Utilities.CachingOptimizer( - MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()), - SCS.Optimizer(), - ), - Float64, - ) - optimizer = POI.Optimizer(cached) - model = direct_model(optimizer) + model = Model(() -> POI.Optimizer(SCS.Optimizer)) @variable(model, x) @variable(model, p in MOI.Parameter(1.0)) @constraint( @@ -1518,15 +1411,7 @@ function test_jump_psd_cone_with_parameter_p_v_pp() end function test_jump_psd_cone_without_parameter_v_and_vv() - cached = MOI.Bridges.full_bridge_optimizer( - MOI.Utilities.CachingOptimizer( - MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()), - SCS.Optimizer(), - ), - Float64, - ) - optimizer = POI.Optimizer(cached) - model = direct_model(optimizer) + model = Model(() -> POI.Optimizer(SCS.Optimizer)) @variable(model, x) @variable(model, p in MOI.Parameter(1.0)) @constraint( @@ -1537,46 +1422,7 @@ function test_jump_psd_cone_without_parameter_v_and_vv() @test is_valid(model, con) optimize!(model) @test value(x) ≈ 0.50000 atol = 1e-5 - @test MOI.get( - backend(model), - MOI.ConstraintPrimalStart(), - index(ParameterRef(p)), - ) == 1.0 - delete(model, con) - unregister(model, :con) - @test_throws MOI.UnsupportedConstraint @constraint( - model, - con, - [p * p, x * (x - 1), p] in MOI.PositiveSemidefiniteConeTriangle(2) - ) - @test_throws MOI.UnsupportedConstraint @constraint( - model, - con, - [x * x, (x - 1), 0.0] in MOI.PositiveSemidefiniteConeTriangle(2) - ) - @test_throws MOI.UnsupportedConstraint @constraint( - model, - con, - [p, (x - 1), x * x] in MOI.PositiveSemidefiniteConeTriangle(2) - ) - @test_throws MOI.UnsupportedConstraint @constraint( - model, - con, - [x, p * (x - 1), x * x] in MOI.PositiveSemidefiniteConeTriangle(2) - ) - @test_throws ErrorException( - "Constraint attribute MathOptInterface.ConstraintName() cannot be set for $(index(ParameterRef(p))) in ParametricOptInterface.", - ) MOI.set( - backend(model), - MOI.ConstraintName(), - index(ParameterRef(p)), - "name", - ) - @test_throws MOI.UnsupportedAttribute MOI.supports( - backend(model), - MOI.ConstraintName(), - MOI.ConstraintIndex{MOI.VariableIndex,MOI.Parameter}, - ) + return end function test_variable_and_constraint_not_registered() @@ -1733,7 +1579,7 @@ function test_jump_errors() }(), ) - model = direct_model(POI.Optimizer(Ipopt.Optimizer())) + model = direct_model(POI.Optimizer(Ipopt.Optimizer)) @test_throws MOI.GetAttributeNotAllowed MOI.get( backend(model), @@ -1782,7 +1628,7 @@ function test_jump_errors() end function test_print() - model = direct_model(POI.Optimizer(HiGHS.Optimizer())) + model = direct_model(POI.Optimizer(HiGHS.Optimizer)) @variable(model, p in MOI.Parameter(1.0)) @variable(model, x) @constraint(model, con, x >= p + p * p + p * x) @@ -1803,7 +1649,7 @@ function test_print() end function test_set_normalized_coefficient() - model = direct_model(POI.Optimizer(HiGHS.Optimizer())) + model = direct_model(POI.Optimizer(HiGHS.Optimizer)) @variable(model, p in MOI.Parameter(1.0)) @variable(model, x) @constraint(model, con, x >= p) diff --git a/test/test_MathOptInterface.jl b/test/test_MathOptInterface.jl index b043de7c..938075ac 100644 --- a/test/test_MathOptInterface.jl +++ b/test/test_MathOptInterface.jl @@ -81,7 +81,7 @@ function test_basic_tests() x* = {2-y,0} obj = 2 """ - optimizer = POI.Optimizer(HiGHS.Optimizer()) + optimizer = POI.Optimizer(HiGHS.Optimizer) MOI.set(optimizer, MOI.Silent(), true) x = MOI.add_variables(optimizer, 2) y, cy = MOI.add_constrained_variable(optimizer, MOI.Parameter(0.0)) @@ -290,9 +290,9 @@ function test_modification_multiple() return end -function test_moi_highs() +function _test_moi_highs() model = MOI.Bridges.full_bridge_optimizer( - POI.Optimizer(HiGHS.Optimizer()), + POI.Optimizer(HiGHS.Optimizer), Float64, ) MOI.set(model, MOI.Silent(), true) @@ -308,11 +308,11 @@ function test_moi_highs() return end -function test_moi_ipopt() +function _test_moi_ipopt() model = MOI.Utilities.CachingOptimizer( MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()), MOI.Bridges.full_bridge_optimizer( - POI.Optimizer(Ipopt.Optimizer()), + POI.Optimizer(Ipopt.Optimizer), Float64, ), ) @@ -386,7 +386,7 @@ function test_moi_ListOfConstraintTypesPresent() end function test_production_problem_example() - optimizer = POI.Optimizer(HiGHS.Optimizer()) + optimizer = POI.Optimizer(HiGHS.Optimizer) c = [4.0, 3.0] A1 = [2.0, 1.0, 1.0] A2 = [1.0, 2.0, 1.0] @@ -464,7 +464,7 @@ function test_production_problem_example() end function test_production_problem_example_duals() - optimizer = POI.Optimizer(HiGHS.Optimizer()) + optimizer = POI.Optimizer(HiGHS.Optimizer) c = [4.0, 3.0] A1 = [2.0, 1.0, 3.0] A2 = [1.0, 2.0, 0.5] @@ -655,11 +655,7 @@ function test_vector_parameter_affine_nonnegatives() y* = t-2 obj = 2*t-3 """ - cached = MOI.Utilities.CachingOptimizer( - MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()), - SCS.Optimizer(), - ) - model = POI.Optimizer(cached) + model = POI.Optimizer(SCS.Optimizer) MOI.set(model, MOI.Silent(), true) x = MOI.add_variable(model) y = MOI.add_variable(model) @@ -711,14 +707,7 @@ function test_vector_parameter_affine_nonpositives() y* = t-2 obj = 2*t-3 """ - cached = MOI.Bridges.full_bridge_optimizer( - MOI.Utilities.CachingOptimizer( - MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()), - SCS.Optimizer(), - ), - Float64, - ) - model = POI.Optimizer(cached) + model = POI.Optimizer(SCS.Optimizer; with_bridge_type = Float64) MOI.set(model, MOI.Silent(), true) x = MOI.add_variable(model) y = MOI.add_variable(model) @@ -777,11 +766,7 @@ function test_vector_soc_parameters() x* = p - 1/√2 y* = 1/√2 """ - cached = MOI.Utilities.CachingOptimizer( - MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()), - SCS.Optimizer(), - ) - model = POI.Optimizer(cached) + model = POI.Optimizer(SCS.Optimizer) MOI.set(model, MOI.Silent(), true) x, y, t = MOI.add_variables(model, 3) p, cp = MOI.add_constrained_variable(model, MOI.Parameter(0.0)) @@ -860,14 +845,7 @@ function test_vector_soc_no_parameters() x* = 1/√2 y* = 1/√2 """ - cached = MOI.Bridges.full_bridge_optimizer( - MOI.Utilities.CachingOptimizer( - MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()), - SCS.Optimizer(), - ), - Float64, - ) - model = POI.Optimizer(cached) + model = POI.Optimizer(SCS.Optimizer; with_bridge_type = Float64) MOI.set(model, MOI.Silent(), true) x, y, t = MOI.add_variables(model, 3) MOI.set( @@ -1532,7 +1510,7 @@ function test_qp_objective_affine_parameter() end function test_qp_objective_parameter_in_quadratic_part() - model = POI.Optimizer(Ipopt.Optimizer()) + model = POI.Optimizer(Ipopt.Optimizer) MOI.set(model, MOI.Silent(), true) x = MOI.add_variable(model) y = MOI.add_variable(model) @@ -1583,7 +1561,7 @@ function test_qp_objective_parameter_in_quadratic_part() @test MOI.get(model, MOI.ObjectiveValue()) ≈ 128 / 9 atol = ATOL @test MOI.get(model, MOI.VariablePrimal(), x) ≈ 4 / 3 atol = ATOL @test MOI.get(model, MOI.VariablePrimal(), y) ≈ 4 / 3 atol = ATOL - model = POI.Optimizer(Ipopt.Optimizer()) + model = POI.Optimizer(Ipopt.Optimizer) MOI.set(model, MOI.Silent(), true) x = MOI.add_variable(model) y = MOI.add_variable(model) @@ -1621,7 +1599,7 @@ function test_qp_objective_parameter_in_quadratic_part() @test MOI.get(model, MOI.ObjectiveValue()) ≈ 77 / 9 atol = ATOL @test MOI.get(model, MOI.VariablePrimal(), x) ≈ 4 / 3 atol = ATOL @test MOI.get(model, MOI.VariablePrimal(), y) ≈ 4 / 3 atol = ATOL - model = POI.Optimizer(Ipopt.Optimizer()) + model = POI.Optimizer(Ipopt.Optimizer) MOI.set(model, MOI.Silent(), true) x = MOI.add_variable(model) y = MOI.add_variable(model) @@ -1647,7 +1625,7 @@ function test_qp_objective_parameter_in_quadratic_part() @test MOI.get(model, MOI.ObjectiveValue()) ≈ 44 / 9 atol = ATOL @test MOI.get(model, MOI.VariablePrimal(), x) ≈ 4 / 3 atol = ATOL @test MOI.get(model, MOI.VariablePrimal(), y) ≈ 4 / 3 atol = ATOL - model = POI.Optimizer(Ipopt.Optimizer()) + model = POI.Optimizer(Ipopt.Optimizer) MOI.set(model, MOI.Silent(), true) x = MOI.add_variable(model) y = MOI.add_variable(model) @@ -1817,7 +1795,7 @@ function test_duals_not_available() end function test_duals_without_parameters() - optimizer = POI.Optimizer(HiGHS.Optimizer()) + optimizer = POI.Optimizer(HiGHS.Optimizer) MOI.set(optimizer, MOI.Silent(), true) x = MOI.add_variables(optimizer, 3) y, cy = MOI.add_constrained_variable(optimizer, MOI.Parameter(0.0)) @@ -1854,11 +1832,7 @@ function test_duals_without_parameters() end function test_getters() - cached = MOI.Utilities.CachingOptimizer( - MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()), - SCS.Optimizer(), - ) - optimizer = POI.Optimizer(cached) + optimizer = POI.Optimizer(SCS.Optimizer) MOI.set(optimizer, MOI.Silent(), true) x = MOI.add_variable(optimizer) @test isempty( @@ -2077,7 +2051,7 @@ function test_getters() end function test_no_quadratic_terms() - optimizer = POI.Optimizer(HiGHS.Optimizer()) + optimizer = POI.Optimizer(HiGHS.Optimizer) MOI.set(optimizer, MOI.Silent(), true) x = MOI.add_variable(optimizer) func = MOI.Utilities.canonical(1.0 * x * x + 1.0 * x - 1.0 * x * x) @@ -2149,15 +2123,7 @@ function test_psd_cone_with_parameter() minobjective: 1x c1: [0, px + -1, 0] in PositiveSemidefiniteConeTriangle(2) =# - cached = MOI.Bridges.full_bridge_optimizer( - MOI.Utilities.CachingOptimizer( - MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()), - SCS.Optimizer(), - ), - Float64, - ) - - model = POI.Optimizer(cached) + model = POI.Optimizer(SCS.Optimizer; with_bridge_type = Float64) MOI.set(model, MOI.Silent(), true) x = MOI.add_variable(model) p = first.(MOI.add_constrained_variable.(model, MOI.Parameter(1.0))) @@ -2200,14 +2166,14 @@ function test_copy_model() x = MOI.add_variable(model) c = MOI.add_constraint(model, 1.0 * x, MOI.EqualTo(1.0)) MOI.set(model, MOI.ConstraintName(), c, "c") - poi = POI.Optimizer(HiGHS.Optimizer()) + poi = POI.Optimizer(HiGHS.Optimizer) MOI.copy_to(poi, model) MOI.optimize!(poi) @test MOI.get(poi, MOI.VariablePrimal(), x) ≈ 1.0 end function test_constrained_variable_HiGHS() - optimizer = POI.Optimizer(HiGHS.Optimizer()) + optimizer = POI.Optimizer(HiGHS.Optimizer) MOI.set(optimizer, MOI.Silent(), true) set = MOI.LessThan(1.0) @test MOI.supports_add_constrained_variable(optimizer, typeof(set)) From 38b79de0fe460705ee720cc2e6b4882d54fb1391 Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Mon, 16 Feb 2026 16:51:55 +1300 Subject: [PATCH 2/4] Update --- docs/make.jl | 5 +- docs/src/Examples/example.md | 453 ---------------------------------- docs/src/manual.md | 114 --------- src/ParametricOptInterface.jl | 1 - 4 files changed, 1 insertion(+), 572 deletions(-) delete mode 100644 docs/src/Examples/example.md delete mode 100644 docs/src/manual.md diff --git a/docs/make.jl b/docs/make.jl index 78432e32..cf0c4bf3 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -17,10 +17,7 @@ Documenter.makedocs(; ), sitename = "ParametricOptInterface.jl", authors = "Tomás Gutierrez, and contributors", - pages = [ - "Home" => "index.md", - "reference.md", - ], + pages = ["Home" => "index.md", "reference.md"], checkdocs = :none, ) diff --git a/docs/src/Examples/example.md b/docs/src/Examples/example.md deleted file mode 100644 index a928abfd..00000000 --- a/docs/src/Examples/example.md +++ /dev/null @@ -1,453 +0,0 @@ -# Basic Examples - -## MOI example - step by step usage - -Let's write a step-by-step example of `POI` usage at the MOI level. - -First, we declare a [`ParametricOptInterface.Optimizer`](@ref) on top of a `MOI` -optimizer. In the example, we consider `HiGHS` as the underlying solver: - -```@example moi1 -import HiGHS -import MathOptInterface as MOI -import ParametricOptInterface as POI -optimizer = POI.Optimizer(HiGHS.Optimizer) -``` - -We declare the variable `x` as in a typical `MOI` model, and we add a -non-negativity constraint: - -```@example moi1 -x = MOI.add_variables(optimizer, 2) -for x_i in x - MOI.add_constraint(optimizer, x_i, MOI.GreaterThan(0.0)) -end -``` - -Now, let's consider 3 `MOI.Parameter`. Two of them, `y`, `z`, will be placed in -the constraints and one, `w`, in the objective function. We'll start all three -of them with a value equal to `0`: - -```@example moi1 -w, cw = MOI.add_constrained_variable(optimizer, MOI.Parameter(0.0)) -y, cy = MOI.add_constrained_variable(optimizer, MOI.Parameter(0.0)) -z, cz = MOI.add_constrained_variable(optimizer, MOI.Parameter(0.0)) -``` - -Let's add the constraints. Notice that we treat parameters and variables in the -same way when building the functions that will be placed in some set to create a -constraint (`Function-in-Set`): - -```@example moi1 -cons1 = MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([2.0, 1.0, 3.0], [x[1], x[2], y]), 0.0) -ci1 = MOI.add_constraint(optimizer, cons1, MOI.LessThan(4.0)) -cons2 = MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([1.0, 2.0, 0.5], [x[1], x[2], z]), 0.0) -ci2 = MOI.add_constraint(optimizer, cons2, MOI.LessThan(4.0)) -``` - -Finally, we declare and add the objective function, with its respective sense: - -```@example moi1 -obj_func = MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([4.0, 3.0, 2.0], [x[1], x[2], w]), 0.0) -MOI.set(optimizer, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), obj_func) -MOI.set(optimizer, MOI.ObjectiveSense(), MOI.MAX_SENSE) -``` - -Now we can optimize the model and assess its termination and primal status: - -```@example moi1 -MOI.optimize!(optimizer) -MOI.get(optimizer, MOI.TerminationStatus()) -MOI.get(optimizer, MOI.PrimalStatus()) -``` - -Given the optimized solution, we check that its value is, as expected, equal to -`28/3`, and the solution vector `x` is `[4/3, 4/3]`: - -```@example moi1 -isapprox(MOI.get(optimizer, MOI.ObjectiveValue()), 28/3, atol = 1e-4) -isapprox(MOI.get(optimizer, MOI.VariablePrimal(), x[1]), 4/3, atol = 1e-4) -isapprox(MOI.get(optimizer, MOI.VariablePrimal(), x[2]), 4/3, atol = 1e-4) -``` - -We can also retrieve the dual values associated to each parameter, -**as they are all additive**: - -```@example moi1 -MOI.get(optimizer, MOI.ConstraintDual(), cy) -MOI.get(optimizer, MOI.ConstraintDual(), cz) -MOI.get(optimizer, MOI.ConstraintDual(), cw) -``` - -Notice the direct relationship in this case between the parameters' duals and -the associated constraints' duals. - -The `y` parameter, for example, only appears in the `cons1`. If we compare -their duals, we can check that the dual of `y` is equal to its coefficient in -`cons1` multiplied by the constraint's dual itself, as expected: - -```@example moi1 -isapprox( - MOI.get(optimizer, MOI.ConstraintDual(), cy), - 3*MOI.get(optimizer, MOI.ConstraintDual(), ci1); - atol = 1e-4, -) -``` - -The same is valid for the remaining parameters. In case a parameter appears in -more than one constraint, or both some constraints and in the objective -function, its dual will be equal to the linear combination of the functions' -duals multiplied by the respective coefficients. - -So far, we only added some parameters that had no influence at first in solving -the model. Let's change the values associated to each parameter to assess its -implications. - -First, we set the value of parameters `y` and `z` to `1.0`. Notice that we are -changing the feasible set of the decision variables: - -```@example moi1 -MOI.set(optimizer, POI.ParameterValue(), y, 1.0) -MOI.set(optimizer, POI.ParameterValue(), z, 1.0) -``` - -However, if we check the optimized model now, there will be no changes in the -objective function value or the in the optimized decision variables: - -```@example moi1 -isapprox.(MOI.get(optimizer, MOI.ObjectiveValue()), 28/3, atol = 1e-4) -isapprox.(MOI.get(optimizer, MOI.VariablePrimal(), x[1]), 4/3, atol = 1e-4) -isapprox.(MOI.get(optimizer, MOI.VariablePrimal(), x[2]), 4/3, atol = 1e-4) -``` - -Although we changed the parameter values, we didn't optimize the model yet. -Thus, **to apply the parameters' changes, the model must be optimized again**: - -```@example moi1 -MOI.optimize!(optimizer) -``` - -The `MOI.optimize!()` function handles the necessary updates, properly fowarding -the new outer model (`POI` model) additions to the inner model (`MOI` model) -which will be handled by the solver. Now we can assess the updated optimized -information: - -```@example moi1 -isapprox.(MOI.get(optimizer, MOI.ObjectiveValue()), 3.0, atol = 1e-4) -MOI.get.(optimizer, MOI.VariablePrimal(), x) == [0.0, 1.0] -``` - -If we update the parameter `w`, associated to the objective function, we are -simply adding a constant to it. Notice how the new objective function is -precisely equal to the previous one plus the new value of `w`. In addition, as -we didn't update the feasible set, the optimized decision variables remain the -same. - -```@example moi1 -MOI.set(optimizer, POI.ParameterValue(), w, 2.0) -# Once again, the model must be optimized to incorporate the changes -MOI.optimize!(optimizer) -# Only the objective function value changes -isapprox.(MOI.get(optimizer, MOI.ObjectiveValue()), 7.0, atol = 1e-4) -MOI.get.(optimizer, MOI.VariablePrimal(), x) == [0.0, 1.0] -``` - -## JuMP Example - step by step usage - -Let's write a step-by-step example of `POI` usage at the JuMP level. - -First, we declare a `Model` on top of a `Optimizer` of an underlying solver. In -the example, we consider `HiGHS` as the underlying solver: - -```@example jump1 -using HiGHS -using JuMP - -using ParametricOptInterface -const POI = ParametricOptInterface - -model = Model(() -> ParametricOptInterface.Optimizer(HiGHS.Optimizer)) -``` - -We declare the variable `x` as in a typical `JuMP` model: - -```@example jump1 -@variable(model, x[i = 1:2] >= 0) -``` - -Now, let's consider 3 `MOI.Parameter`. Two of them, `y`, `z`, will be placed in -the constraints and one, `w`, in the objective function. We'll start all three -of them with a value equal to `0`: - -```@example jump1 -@variable(model, y in MOI.Parameter(0.0)) -@variable(model, z in MOI.Parameter(0.0)) -@variable(model, w in MOI.Parameter(0.0)) -``` - -Let's add the constraints. Notice that we treat parameters the same way we treat -variables when writing the model: - -```@example jump1 -@constraint(model, c1, 2x[1] + x[2] + 3y <= 4) -@constraint(model, c2, x[1] + 2x[2] + 0.5z <= 4) -``` - -Finally, we declare and add the objective function, with its respective sense: - -```@example jump1 -@objective(model, Max, 4x[1] + 3x[2] + 2w) -``` - -We can optimize the model and assess its termination and primal status: - -```@example jump1 -optimize!(model) -termination_status(model) -primal_status(model) -``` - -Given the optimized solution, we check that its value is, as expected, equal to -`28/3`, and the solution vector `x` is `[4/3, 4/3]`: - -```@example jump1 -isapprox(objective_value(model), 28/3) -isapprox(value.(x), [4/3, 4/3]) -``` - -We can also retrieve the dual values associated to each parameter, **as they are all additive**: - -```@example jump1 -MOI.get(model, POI.ParameterDual(), y) -MOI.get(model, POI.ParameterDual(), z) -MOI.get(model, POI.ParameterDual(), w) -``` - -Notice the direct relationship in this case between the parameters' duals and the associated constraints' duals. The `y` parameter, for example, only appears in the `c1`. If we compare their duals, we can check that the dual of `y` is equal to its coefficient in `c1` multiplied by the constraint's dual itself, as expected: - -```@example jump1 -dual_of_y = MOI.get(model, POI.ParameterDual(), y) -isapprox(dual_of_y, 3 * dual(c1)) -``` - -The same is valid for the remaining parameters. In case a parameter appears in more than one constraint, or both some constraints and in the objective function, its dual will be equal to the linear combination of the functions' duals multiplied by the respective coefficients. - -So far, we only added some parameters that had no influence at first in solving the model. Let's change the values associated to each parameter to assess its implications. First, we set the value of parameters `y` and `z` to `1.0`. Notice that we are changing the feasible set of the decision variables: - -```@example jump1 -MOI.set(model, POI.ParameterValue(), y, 1) -MOI.set(model, POI.ParameterValue(), z, 1) -# We can also query the value in the parameters -MOI.get(model, POI.ParameterValue(), y) -MOI.get(model, POI.ParameterValue(), z) -``` - -To apply the parameters' changes, the model must be optimized again: - -```@example jump1 -optimize!(model) -``` - -The `optimize!` function handles the necessary updates, properly fowarding the new outer model (`POI` model) additions to the inner model (`MOI` model) which will be handled by the solver. Now we can assess the updated optimized information: - -```@example jump1 -isapprox(objective_value(model), 3) -isapprox(value.(x), [0, 1]) -``` - -If we update the parameter `w`, associated to the objective function, we are simply adding a constant to it. Notice how the new objective function is precisely equal to the previous one plus the new value of `w`. In addition, as we didn't update the feasible set, the optimized decision variables remain the same. - -```@example jump1 -MOI.set(model, POI.ParameterValue(), w, 2) -# Once again, the model must be optimized to incorporate the changes -optimize!(model) -# Only the objective function value changes -isapprox(objective_value(model), 7) -isapprox(value.(x), [0, 1]) -``` - -## JuMP Example - Declaring vectors of parameters - -Many times it is useful to declare a vector of parameters just like we declare a vector of variables, the JuMP syntax for variables works with parameters too: - - -```@example jump2 -using JuMP -import HiGHS -import ParametricOptInterface as POI -model = Model(() -> ParametricOptInterface.Optimizer(HiGHS.Optimizer)) -@variable(model, x[i = 1:3] >= 0) -@variable(model, p1[i = 1:3] in MOI.Parameter(0.0)) -@variable(model, p2[i = 1:3] in MOI.Parameter.([1, 10, 45])) -@variable(model, p3[i = 1:3] in MOI.Parameter.(ones(3))) -``` - -## JuMP Example - Dealing with parametric expressions as variable bounds - -A very common pattern that appears when using ParametricOptInterface is to add variable and later add some expression with parameters that represent the variable bound. The following code illustrates the pattern: - -```@example jump3 -using HiGHS -using JuMP -using ParametricOptInterface -const POI = ParametricOptInterface - -model = direct_model(POI.Optimizer(HiGHS.Optimizer)) -@variable(model, x) -@variable(model, p in MOI.Parameter(0.0)) -@constraint(model, x >= p) -``` - -Since parameters are treated like variables JuMP lowers this to MOI as `x - p >= 0` which is not a variable bound but a linear constraint.This means that the current representation of this problem at the solver level is: - -```math -\begin{align} - & \min_{x} & 0 - \\ - & \;\;\text{s.t.} & x & \in \mathbb{R} \\ - & & x - p & \geq 0 -\end{align} -``` - -This behaviour might be undesirable because it creates extra rows in your problem. Users can set the [`ParametricOptInterface.ConstraintsInterpretation`](@ref) to control how the linear constraints should be interpreted. The pattern advised for users seeking the most performance out of ParametricOptInterface should use the followig pattern: - -```@example jump3 -using HiGHS -using JuMP -using ParametricOptInterface -const POI = ParametricOptInterface - -model = direct_model(POI.Optimizer(HiGHS.Optimizer)) -@variable(model, x) -@variable(model, p in MOI.Parameter(0.0)) - -# Indicate that all the new constraints will be valid variable bounds -MOI.set(model, POI.ConstraintsInterpretation(), POI.ONLY_BOUNDS) -@constraint(model, x >= p) -# The name of this constraint was different to inform users that this is a -# variable bound. - -# Indicate that all the new constraints will not be variable bounds -MOI.set(model, POI.ConstraintsInterpretation(), POI.ONLY_CONSTRAINTS) -# @constraint(model, ...) -``` - -This way the mathematical representation of the problem will be: - -```math -\begin{align} - & \min_{x} & 0 - \\ - & \;\;\text{s.t.} & x & \geq p -\end{align} -``` - -which might lead to faster solves. - -Users that just want everything to work can use the default value `POI.ONLY_CONSTRAINTS` or try to use `POI.BOUNDS_AND_CONSTRAINTS` and leave it to ParametricOptInterface to interpret the constraints as bounds when applicable and linear constraints otherwise. - -## MOI Example - Parameters multiplying Quadratic terms - -Let's start with a simple quadratic problem - -```@example moi2 -import Ipopt -import MathOptInterface as MOI -import ParametricOptInterface as POI -model = POI.Optimizer(Ipopt.Optimizer) -x, _ = MOI.add_constrained_variable(model, MOI.GreaterThan(0.0)) -y, _ = MOI.add_constrained_variable(model, MOI.GreaterThan(0.0)) -ci1 = MOI.add_constraint(model, 2.0 * x + 1.0 * y, MOI.LessThan(4.0)) -ci2 = MOI.add_constraint(model, 1.0 * x + 2.0 * y, MOI.LessThan(4.0)) -MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) -obj_func = 1.0 * x * x + 1.0 * y * y -MOI.set(model, MOI.ObjectiveFunction{typeof(obj_func)}(), obj_func) -``` - -To multiply a parameter in a quadratic term, the user will -need to use the `POI.QuadraticObjectiveCoef` model attribute. - -```@example moi2 -p, _ = MOI.add_constrained_variable.(model, MOI.Parameter(1.0)) -MOI.set(model, POI.QuadraticObjectiveCoef(), (x, y), p) -``` - -This function will add the term `p*xy` to the objective function. -It's also possible to multiply a scalar affine function to the quadratic term. - -```@example moi2 -MOI.set(model, POI.QuadraticObjectiveCoef(), (x, y), 2 * p + 3) -``` - -This will set the term `(2p+3)*xy` to the objective function (it overwrites the -last set). Then, just optimize the model. - -```@example moi2 -MOI.optimize!(model) -isapprox(MOI.get(model, MOI.ObjectiveValue()), 32/3, atol=1e-4) -isapprox(MOI.get(model, MOI.VariablePrimal(), x), 4/3, atol=1e-4) -isapprox(MOI.get(model, MOI.VariablePrimal(), y), 4/3, atol=1e-4) -``` - -To change the parameter just set `POI.ParameterValue` and optimize again. - -```@example moi2 -MOI.set(model, POI.ParameterValue(), p, 2.0) -MOI.optimize!(model) -isapprox(MOI.get(model, MOI.ObjectiveValue()), 128/9, atol=1e-4) -isapprox(MOI.get(model, MOI.VariablePrimal(), x), 4/3, atol=1e-4) -isapprox(MOI.get(model, MOI.VariablePrimal(), y), 4/3, atol=1e-4) -``` - -## JuMP Example - Parameters multiplying Quadratic terms - -Let's get the same MOI example - -```@example jump4 -using Ipopt -using JuMP -using ParametricOptInterface -const POI = ParametricOptInterface - -optimizer = POI.Optimizer(Ipopt.Optimizer) -model = direct_model(optimizer) - -@variable(model, x >= 0) -@variable(model, y >= 0) -@variable(model, p in MOI.Parameter(1.0)) -@constraint(model, 2x + y <= 4) -@constraint(model, x + 2y <= 4) -@objective(model, Max, (x^2 + y^2)/2) -``` - -We use the same MOI function to add the parameter multiplied to the quadratic term. - -```@example jump4 -MOI.set(backend(model), POI.QuadraticObjectiveCoef(), (index(x),index(y)), 2index(p)+3) -``` - -If the user print the `model`, the term `(2p+3)*xy` won't show. -It's possible to retrieve the parametric function multiplying the term `xy` with `MOI.get`. - -```@example jump4 -MOI.get(backend(model), POI.QuadraticObjectiveCoef(), (index(x),index(y))) -``` - -Then, just optimize the model - -```@example jump4 -optimize!(model) -isapprox(objective_value(model), 32/3, atol=1e-4) -isapprox(value(x), 4/3, atol=1e-4) -isapprox(value(y), 4/3, atol=1e-4) -``` - -To change the parameter just set `POI.ParameterValue` and optimize again. - -```@example jump4 -MOI.set(model, POI.ParameterValue(), p, 2.0) -optimize!(model) -isapprox(objective_value(model), 128/9, atol=1e-4) -isapprox(value(x), 4/3, atol=1e-4) -isapprox(value(y), 4/3, atol=1e-4) -``` diff --git a/docs/src/manual.md b/docs/src/manual.md deleted file mode 100644 index 8c0f47d1..00000000 --- a/docs/src/manual.md +++ /dev/null @@ -1,114 +0,0 @@ -```@meta -CurrentModule = ParametricOptInterface -``` - -# Manual - -## Why use parameters? - -A typical optimization model built using `MathOptInterface.jl` (`MOI`for short) -has two main components: - -1. Variables -2. Constants - -Using these basic elements, one can create functions and sets that, together, -form the desired optimization model. The goal of `POI` is the implementation of -a third type, parameters, which: - -* are declared similar to a variable, and inherits some functionalities (for - example, dual calculation) -* acts like a constant, in the sense that it has a fixed value that will remain - the same unless explicitely changed by the user - -A main concern is to efficiently implement this new type, as one typical usage -is to change its value to analyze the model behavior, without the need to build -a new one from scratch. - -## How it works - -The main idea applied in POI is that the interaction between the solver, for -example `HiGHS`, and the optimization model will be handled by `MOI` as usual. - -Because of that, `POI` is a higher level wrapper around `MOI`, responsible for -receiving variables, constants and parameters, and forwarding to the lower level -model only variables and constants. - -As `POI` receives parameters, it must analyze and decide how they should be -handled on the lower level optimization model (the `MOI` model). - -## Usage - -In this manual we describe how to interact with the optimization model at the -MOI level. In the _Examples_ section you can find some tutorials with the -JuMP usage. - -### Supported constraints - -This is a list of supported `MOI` constraint functions that can handle -parameters. If you try to add a parameter to a function that is not listed here, -it will return an unsupported error. - -| MOI Function | -| :------------------------ | -| `ScalarAffineFunction` | -| `ScalarQuadraticFunction` | -| `VectorAffineFunction` | - - -### Supported objective functions - -| MOI Function | -| :------------------------ | -| `ScalarAffineFunction` | -| `ScalarQuadraticFunction` | - -### Declare a Optimizer - -In order to use parameters, the user needs to declare an -[`Optimizer`](@ref) on top of a `MOI` optimizer, such as `HiGHS.Optimizer`. - -```@repl manual -import ParametricOptInterface as POI -import HiGHS -optimizer = POI.Optimizer(HiGHS.Optimizer) -``` - -### Parameters - -A `MOI.Parameter` is a set used to define a variable with a fixed value that -can be changed by the user. It is analogous to `MOI.EqualTo`, but can be used -by special methods like the ones in this package to remove the fixed variable -from the optimization problem. This permits the usage of multiplicative -parameters in linear models and might speedup solves since the number of -variables is reduced. - -### Adding a new parameter to a model - -To add a parameter to a model, we must use the `MOI.add_constrained_variable` -function, passing as its arguments the model and a `MOI.Parameter` with its -given value: - -```@repl manual -y, cy = MOI.add_constrained_variable(optimizer, MOI.Parameter(0.0)) -``` - -### Changing the parameter value - -To change a given parameter's value, access its `VariableIndex` and set it to -the new value using the `MOI.Parameter` structure. - -```@repl manual -MOI.set(optimizer, POI.ParameterValue(), y, MOI.Parameter(2.0)) -``` - -### Retrieving the dual of a parameter - -Given an optimized model, one can compute the dual associated to a parameter, -**as long as it is an additive term in the constraints or objective**. - -One can do so by getting the `MOI.ConstraintDual` attribute of the parameter's -`MOI.ConstraintIndex`: -```julia -julia> MOI.get(optimizer, POI.ParameterDual(), y) -``` diff --git a/src/ParametricOptInterface.jl b/src/ParametricOptInterface.jl index 10e18a26..2c1e0151 100644 --- a/src/ParametricOptInterface.jl +++ b/src/ParametricOptInterface.jl @@ -285,7 +285,6 @@ mutable struct Optimizer{T,OT<:MOI.ModelLike} <: MOI.AbstractOptimizer Dict{Symbol,Any}(), ) end - end Optimizer(arg; kwargs...) = Optimizer{Float64}(arg; kwargs...) From b88f67d0df944f29f8b8da1cac77f7d9d5355d6b Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Mon, 16 Feb 2026 16:53:25 +1300 Subject: [PATCH 3/4] Update --- docs/Project.toml | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/Project.toml b/docs/Project.toml index b5f8bcfc..bd422b7d 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -5,7 +5,6 @@ Ipopt = "b6b21f68-93f8-5de0-b562-5493be1d77c9" JuMP = "4076af6c-e467-56ae-b986-b466b2749572" MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee" ParametricOptInterface = "0ce4ce61-57bf-432b-a095-efac525d185e" -SCS = "c946c3f1-0d1f-5ce8-9dea-7daa1f7e2d13" [compat] Documenter = "1" @@ -13,4 +12,3 @@ HiGHS = "1" Ipopt = "1" JuMP = "1" MathOptInterface = "1.13.2" -SCS = "2" From 88d87f546385d7f55f18ad89e82e7eda7ea8bf16 Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Tue, 17 Feb 2026 08:57:01 +1300 Subject: [PATCH 4/4] Update --- test/test_MathOptInterface.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_MathOptInterface.jl b/test/test_MathOptInterface.jl index 938075ac..b171e167 100644 --- a/test/test_MathOptInterface.jl +++ b/test/test_MathOptInterface.jl @@ -290,7 +290,7 @@ function test_modification_multiple() return end -function _test_moi_highs() +function test_moi_highs() model = MOI.Bridges.full_bridge_optimizer( POI.Optimizer(HiGHS.Optimizer), Float64, @@ -308,7 +308,7 @@ function _test_moi_highs() return end -function _test_moi_ipopt() +function test_moi_ipopt() model = MOI.Utilities.CachingOptimizer( MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()), MOI.Bridges.full_bridge_optimizer(