Getting started
The code snippets below present the Python and JuMP implementations of the hs015 instance.
Importing the package
Building the solver
The solver is created as follows:
Setting a preset
Presets are strategy combinations that mimic existing solvers (like filterSQP or IPOPT). A preset may be set as follows:
Setting options
Additional options may be set with the following syntax:
A comprehensive list of options can be found here.
Building a model
Building an optimization model is incremental: you may attach bounds, an objective, constraints, and an initial primal-dual iterate to a particular model, as well as a Lagrangian Hessian, a Lagrangian Hessian operator, and a Lagrangian convention when the model is defined manually instead of via a modeling framework. By default, a model is unconstrained, has no objective, no Hessian, and has the Lagrangian convention similar to \(\mathcal{L} = f(x) - y^T c(x) - z^T x\).
Variables and bounds
In Python, the bounds are attached via the functions set_variables_lower_bounds and set_variables_upper_bounds, while in Julia, the bounds are passed upon the creation of the variables.
Objective
We then attach an objective function. The Python interface expects callback of the objective and its gradient. The derivatives are computed automatically via AD by the JuMP backend.
Constraints
We then attach the constraints and information about the constraint Jacobian, including the sparsity pattern in COO format:
def constraints(x, constraint_values):
constraint_values[:] = [x[0]*x[1], x[0] + x[1]**2]
def jacobian(x, jacobian_values):
jacobian_values[:] = [x[1], 1., x[0], 2.*x[1]]
number_constraints = 2
constraints_lower_bounds = [1., 0.]
constraints_upper_bounds = [Inf, Inf]
number_jacobian_nonzeros = 4
jacobian_row_indices = [0, 1, 0, 1]
jacobian_column_indices = [0, 0, 1, 1]
model.set_constraints(number_constraints, constraints, constraints_lower_bounds,
constraints_upper_bounds, number_jacobian_nonzeros, jacobian_row_indices,
jacobian_column_indices, jacobian)
Lagrangian Hessian
If the Hessian of the Lagrangian is available explicitly, it may be passed to Python as a callback along with its sparsity pattern in COO format:
def lagrangian_hessian(x, objective_multiplier, multipliers, hessian_values):
hessian_values[:] = [objective_multiplier*(1200*x[0]**2 - 400.*x[1] + 2.),
-400.*objective_multiplier*x[0] - multipliers[0],
200.*objective_multiplier - 2.*multipliers[1]]
number_hessian_nonzeros = 3
hessian_row_indices = [0, 1, 1]
hessian_column_indices = [0, 0, 1]
model.set_lagrangian_hessian(number_hessian_nonzeros, unopy.LOWER_TRIANGLE,
hessian_row_indices, hessian_column_indices, lagrangian_hessian)
Some of the methods implemented in Uno (e.g., SQP methods using the BQPD QP solver) support a Hessian-vector operator:
def lagrangian_hessian_operator(x, evaluate_at_x, objective_multiplier, multipliers,
vector, result):
# the modeler should throw an exception if the function cannot be evaluated
hessian00 = objective_multiplier*(1200*x[0]**2. - 400.*x[1] + 2.)
hessian10 = -400.*objective_multiplier*x[0] - multipliers[0]
hessian11 = 200.*objective_multiplier - 2.*multipliers[1]
result[:] = [hessian00*vector[0] + hessian10*vector[1],
hessian10*vector[0] + hessian11*vector[1]]
model.set_lagrangian_hessian_operator(lagrangian_hessian_operator)
The convention of the Lagrangian should be specified in Python whenever the problem has simple bounds (in which cases the corresponding dual variables will be returned with the correct signs) or general constraints (the convention should match the construction of the Lagrangian Hessian if provided).
Initial point
Solving the model
The model is solved by calling the optimize function:
Inspecting the result
The termination status is given by:
The objective at the solution is given by:
The primal solution is given by:
The dual solution is given by: