Direct interface
opengen
version 0.6.4
.
The API is still young and is likely to change in versions 0.8.X
.
As we discussed previously there are various ways you can use the auto-generated optimizer. You can use the generated Rust code, access it over a TCP socket, and so on. In this section we will focus on how to access it directly.
The idea is that OpEn can generate a Python module that you can import
.
Generate a Python module for your optimizer
Consider the following parametric optimization problem:
import opengen as og
import casadi.casadi as cs
u = cs.SX.sym("u", 5)
p = cs.SX.sym("p", 2)
phi = og.functions.rosenbrock(u, p)
c = cs.vertcat(1.5 * u[0] - u[1],
cs.fmax(0.0, u[2] - u[3] + 0.1))
bounds = og.constraints.Ball2(radius=1.5)
problem = og.builder.Problem(u, p, phi) \
.with_penalty_constraints(c) \
.with_constraints(bounds)
build_config = og.config.BuildConfiguration() \
.with_build_directory("my_optimizers") \
.with_build_mode("debug") \
.with_build_python_bindings()
meta = og.config.OptimizerMeta() \
.with_optimizer_name("rosenbrock")
builder = og.builder.OpEnOptimizerBuilder(problem, meta,
build_config)
builder.build()
Note that we have used with_build_python_bindings()
.
This will allow us to import the auto-generated optimizer as a Python module!
Use the generated module
The above code generates an optimizer which is stored in my_optimizers/rosenbrock
.
In that directory you can find a file called rosenbrock.so
(or rosenbrock.pyd
on Windows).
This can be loaded as an autogenerated Python module.
However, mind that this directory is most likely not in your Python path,
so you will have to add it before you can import the optimizer.
This can be done very easily:
sys.path.insert(1, './my_optimizers/rosenbrock')
import rosenbrock
Then you will be able to use it as follows:
solver = rosenbrock.solver()
result = solver.run(p=[20., 1.])
u_star = result.solution
In the first line, solver = rosenbrock.solver()
, we obtain an instance of
Solver
, which can be used to solve parametric optimization problems.
In the second line, result = solver.run(p=[20., 1.])
, we call the solver
with parameter $p=(20, 1)$. Method run
accepts another three optional
arguments, namely:
initial_guess
(can be either a list or a numpy array),initial_lagrange_multipliers
, andinitial_penalty
The solver returns an object of type OptimizerSolution
with the following
properties:
Property | Explanation |
---|---|
exit_status | Exit status; can be (i) Converged or (ii) NotConvergedIterations , if the maximum number of iterations was reached, therefore, the algorithm did not converge up to the specified tolerances, or (iii) NotConvergedOutOfTime , if the solver did not have enough time to converge |
num_outer_iterations | Number of outer iterations |
num_inner_iterations | Total number of inner iterations (for all inner problems) |
last_problem_norm_fpr | Norm of the fixed-point residual of the last inner problem; this is a measure of the solution quality of the inner problem |
f1_infeasibility | Euclidean norm of $c^{-1}(y^+-y)$, which is equal to the distance between $F_1(u, p)$ and $C$ at the solution |
f2_norm | Euclidean norm of $F_2(u, p)$ at the solution |
solve_time_ms | Total execution time in milliseconds |
penalty | Last value of the penalty parameter |
solution | Solution |
cost | Cost function at solution |
lagrange_multipliers | Vector of Lagrange multipliers (if $n_1 > 0$) or an empty vector, otherwise |
These are the same properties as those of opengen.tcp.SolverStatus
.
Importing optimizer with variable name
Previously we used import rosenbrock
to import the auto-generated module.
The limitation of this syntax is that it makes it difficult to change the name of the optimizer, i.e., rosenbrock
is hard-coded.
A better syntax would be:
optimizers_dir = "my_optimizers"
optimizer_name = "rosenbrock"
sys.path.insert(1, os.path.join(optimizers_dir, optimizer_name))
rosenbrock = __import__(optimizer_name)