MATLAB
IMPORTANT NOTE: IN OpEn version 0.6.0 we have added support
for the augmented Lagrangian and penalty methods; this is not available through the
MATLAB interface at the moment. We would advise that the users use opengen
in Python
instead. The MATLAB interface will be updated soon - stay tuned
About the MATLAB interface
If you haven't done so already, you first need to install the MATLAB interface.
This inteface allows the designers to prepare their parametric optimizers in MATLAB and then use the code generation tool we provide to build their parametric optimizer in Rust. The generated code can either be used directly in Rust, or, it can be interfaced from any software.
In other words, MATLAB is used to design a parametric optimizer, which runs in Rust and can be interfaced from any programming language or operating system.
The generated optimzer runs in Rust and exports its functionality using UDP sockets.
Let's now walk through the necessary steps to design a parametric optimizer.
Problem definition
Recall that we need to define a parametric optimization problem of the form
where $u$ is the decision variable of the problem and $p$ is a parameter.
We, therefore, need to define the cost function and the set of constraints. The gradient of the cost function will be computed automatically (CasADi takes care of that).
Cost function
Determining the cost function is straightforward. We simply need to create two CasADi symbols, u
and p
, for the decision variable and the parameter respectively. These symbols need to have specified dimensions (nu
and np
respectively).
nu = 6; % number of decision variables
np = 2; % number of parameters
u = casadi.SX.sym('u', nu); % decision variables
p = casadi.SX.sym('p', np); % parameters
phi = rosenbrock(u, p); % cost function phi(u; p)
Constraints
Constraints fall into two sub-categories: non-parametric constraints and parametric ones (which are under development). Non-parametric constraints are specified during design time and cannot be updated in real time. Parametric constraints depend on the parameter p
and are updated at every invocation of the solver.
Non-parametric constraints are implemented by @OpEnConstraints
, a MATLAB class which follows the Factory pattern. Constraint objects are created by calling a static function. For example, to create the constraint
we construct an instance of OpEnConstraints
as follows:
constraints = OpEnConstraints.make_ball_at_origin(1.0);
Building a parametric optimizer
Code Generation
Code generation is as simple as
builder = OpEnOptimizerBuilder().with_problem(u, p, phi, constraints);
optimizer = builder.build();
As shown above, we first need to create an instance of OpEnOptimizerBuilder; this is a builder that will take care of code generation and will allow us to call the generated parametric optimizer from MATLAB.
The builder needs to know the problem specifications, that is, the cost function and the constraints (which we provide using with_problem
).
We then call build()
to create an optimizer -- an instance of OpEnOptimizer
.
Using the Rust module
The auto-generated Rust module exposes its functionality over a JSON-based communication protocol over a UDP socket. For more information, read the UDP communication documentation page.
The auto-generated Rust module can be used directly from MATLAB; it can be started, used and terminated.
First, we start the module:
optimizer.run();
Then, we connect to the optimizer to be able to use it:
optimizer.connect();
We may now use the parametric optimizer:
p = [1, 250]; % parameter
out = optimizer.consume(p);
This request returns a MATLAB structure like the following one:
p: [2×1 double]
u: [6×1 double]
n: 77
f: -5.369481020006234
dt: '19.276626ms'
where p
is the provided parameter, u
is the solution, n
is the number of iterations, f
is the logarithm of the fixed-point residual and dt
is the elapsed time (with units of measurement).
We may call the server several times. Once we're done, we should close the UDP connection and stop the server.
optimizer.disconnect();
optimizer.stop();
Advanced configuration
Having created a builder (an instance of OpEnOptimizerBuilder
) we may override some of the default properties using certain methods as explained in what follows.
Solver parameters
We may specify/update the length of the L-BFGS buffer, the tolerance and the maximum number of iterations as follows:
builder.with_lbfgs_memory(15)...
.with_max_iterations(2500)...
.with_fpr_tolerance(1e-8);
Connection properties
By default the UDP interface binds at the local address 127.0.0.1
and the default port 3498
.
We may update the port using:
builder.with_local_udp_at_port(4567);
or, we may also allow the module to bind on any IP. This can be done if we intend to access the parametric optimizer remotely:
builder.with_public_udp_at_port(4567);
We may also specify a custom IP at which the optimizer should bind using:
builder.with_bind_address('10.8.1.6', 4567);
Metadata and other properties
The generated optimizer will be stored in {OPTIMIZER_BUILD_PATH}/{OPTIMIZER_NAME}
; we may specify a custom path and a custom name for your optimizer as follows:
builder.with_build_path('/path/to/build/directory')...
.with_build_name('turbo_optimizer');
The default path is {OPTIMIZATION_ENGINE_ROOT}/build/autogenerated_optimizer/
.
You may also choose whether the optimizer should be compiled in debug
(default) or release
mode. For best performance, change the build mode to release
as follows
builder.with_build_mode('release');
You can choose whether the optimizer will run on your system or whether it should be cross-compiled for some other system (e.g., a Raspberry Pi, or some ARM-based system).
For example, to cross-compile for Raspberry Pi specify the following target:
builder.with_target_system('rpi');
You may also specify the authors and license of this optimizer using
builder.with_authors('P. Sopasakis <p.sopasakis@example.com>', ...
'E. Fresk <e.fresk@example.com>')...
.with_license('MIT and APACHE-v2')
.with_version('0.1.5');