MOSEK from Java with ojAlgo

MOSEK is a commercial solver for linear, quadratic, conic and mixed-integer optimisation. ojAlgo's modelling API, ExpressionsBasedModel, builds LP, QP and MIP models in Java and other JVM languages such as Kotlin and Scala. ojAlgo solves them with its own pure Java solvers, and the integration module ojalgo-mosek lets the same models be solved by MOSEK instead, without changing the model code. MOSEK also solves models with convex quadratic constraints (QCLP and QCQP), which many solvers, ojAlgo's own included, do not handle. For an application that already uses ojAlgo, moving to MOSEK takes two dependencies and one line of code.

Upgrading an existing ojAlgo application

When an application's models outgrow ojAlgo's built-in solvers, they can be handed to MOSEK without rewriting them:

  1. Add ojalgo-mosek and MOSEK's own Java package to the project, as described below. Keep your existing ojAlgo dependency.
  2. Register MOSEK once, at start-up: ExpressionsBasedModel.addIntegration(SolverMosek.INTEGRATION);
  3. Nothing else changes. Models are built, solved and read as before, and the time limit, gap tolerance and number of threads set in model.options are passed on to MOSEK.

To try MOSEK on one model first, leave out the registration and call model.maximise(SolverMosek.INTEGRATION) (or minimise) on that model. The same model solved with model.maximise() uses ojAlgo's own solvers, so the two results can be compared directly.

Installation

Prerequisites. Java 17 or later, and a MOSEK licence, set up as described in MOSEK's licensing guide. ojalgo-mosek is built and tested with MOSEK 11.2.4.

Add the integration to your project. The module is on Maven Central and brings ojAlgo with it:

<dependency>
    <groupId>org.ojalgo</groupId>
    <artifactId>ojalgo-mosek</artifactId>
    <version>3.3.0</version>
</dependency>

ojAlgo is released more often than the integration. To use a newer ojAlgo than the one ojalgo-mosek was built with, declare ojAlgo as well; the version you declare takes precedence:

<dependency>
    <groupId>org.ojalgo</groupId>
    <artifactId>ojalgo</artifactId>
    <version>57.4.0</version>
</dependency>

Add MOSEK. MOSEK publishes its Java package on Maven Central, with the native libraries included, so nothing else needs installing or configuring. ojalgo-mosek does not bring it with it; add it with the MOSEK version the integration is built with:

<dependency>
    <groupId>com.mosek</groupId>
    <artifactId>mosek</artifactId>
    <version>11.2.4</version>
</dependency>

To use a local MOSEK installation instead, put mosek.jar from its tools/platform/<platform>/bin directory on the classpath, and pass that directory to the JVM with -Djava.library.path.

Example

Register MOSEK once; from then on ExpressionsBasedModel uses it to solve the models.

import org.ojalgo.optimisation.*;
import org.ojalgo.optimisation.solver.mosek.SolverMosek;

ExpressionsBasedModel.addIntegration(SolverMosek.INTEGRATION);

ExpressionsBasedModel model = new ExpressionsBasedModel();

// Whole units only; the weight is the profit per unit
Variable chairs = model.newVariable("Chairs").lower(0).integer().weight(45);
Variable tables = model.newVariable("Tables").lower(0).integer().weight(80);
// Making any tables means running the table line, at a fixed cost of 900
Variable tableLine = model.newVariable("TableLine").binary().weight(-900);

model.newExpression("MachineHours").upper(400).set(chairs, 2).set(tables, 5);
model.newExpression("Wood").upper(450).set(chairs, 4).set(tables, 3);
model.newExpression("LineCapacity").upper(0).set(tables, 1).set(tableLine, -60);

Optimisation.Result result = model.maximise();   // OPTIMAL 6475.0: 75 chairs, 50 tables

Models can also be read from MPS or LP files with ExpressionsBasedModel.parse(file).

Parameters

ojalgo-mosek passes these model options on to MOSEK. MOSEK's log is shown with model.options.debug(SolverMosek.class).

ojAlgo option (model.options) MOSEK parameter
time_suffice / time_abort (the shorter one) optimizer_max_time
parallelism(n) num_threads
feasibility, when changed from the default basis_tol_x, mio_tol_feas, mio_tol_abs_relax_int
the integer strategy's gap tolerance, when changed mio_tol_rel_gap, mio_tol_abs_gap

Any other MOSEK parameter is set with a configurator: extend SolverMosek.Configurator, override newTask, and register it.

import mosek.*;
import org.ojalgo.optimisation.Optimisation;
import org.ojalgo.optimisation.solver.mosek.SolverMosek;

public class MyMosekConfigurator extends SolverMosek.Configurator {

    @Override
    protected Task newTask(final Env environment, final int nbConstraints, final int nbVariables, final Optimisation.Options options) {
        Task task = super.newTask(environment, nbConstraints, nbVariables, options);
        task.putdouparam(dparam.mio_tol_rel_gap, 1e-6);
        return task;
    }
}

ExpressionsBasedModel.setConfigurator(new MyMosekConfigurator());

More