ExpressionsBasedModel and OptModel, side by side

Two ways to build an optimisation model in Java and solve it on the Optimisation Service. They are separate implementations, but can be used almost the same way. An OptModel has one way to do each thing; this page shows that way, side by side with the same thing done with an ExpressionsBasedModel — most of the time in the very same code. Where they differ, it says so. The ExpressionsBasedModel code needs ojAlgo 58 or later; if yours is written the traditional way, the last section shows what to change.

ExpressionsBasedModel

ojAlgo’s model, in use for many years. It solves with ojAlgo’s own solvers, with any solver an integration brings in, or remotely, on the Optimisation Service. ojAlgo requires a recent Java version, and keeps moving ahead to use what new versions bring.

Choose it if you already use ojAlgo, want to move between solving locally and on the Service, or need what only it can do.

OptModel

The model of the Service’s client library, which has no dependencies at all. It solves on the Optimisation Service only, and has one way to do each thing. It is more conservative about Java versions, and currently requires only Java 11.

Choose it if all your solving is done on the Service, you want nothing else on the classpath, or you are on an older Java.

Creating a model

An ExpressionsBasedModel can be created simply with its constructor. It then uses ojAlgo’s default environment. For more advanced features, such as solving remotely on the Service, you have to create an environment first, and get the models from it.

An OptModel always comes from a client, which knows the Service’s address, and your licence key if you have one.

ExpressionsBasedModel
OptModel
The simplest way
ExpressionsBasedModel
ExpressionsBasedModel model = new ExpressionsBasedModel();
OptModel
OptClientV1 client = OptClientV1.newInstance(
        "https://service-api.optimatika.se");

OptModel model = client.newModel();
Solving on the Service
ExpressionsBasedModel
OptClientV1 client = OptClientV1.newInstance(
        "https://service-api.optimatika.se", licenceKey);

Optimisation.Environment environment = Optimisation.newEnvironment();
environment.setRemoteSolver(client::putOnQueue, client::pollResult);

ExpressionsBasedModel model = environment.newModel();
OptModel
OptClientV1 client = OptClientV1.newInstance(
        "https://service-api.optimatika.se", licenceKey);

OptModel model = client.newModel();

The environment and the client play the same part: each knows how its models are solved, and hands them out with newModel(). An ExpressionsBasedModel that solves on the Service is given the client’s two methods — the same client an OptModel comes from.

Variables

Both have a factory for each kind of variable: real, integer and binary. A real or integer variable is free when it is created — no lower and no upper bound — and a binary one is between 0 and 1.

An ExpressionsBasedModel has one class of variable, Variable, whichever factory made it. An OptModel’s variables come in three classes, and only real and integer ones have bounds you can set.

ExpressionsBasedModel
OptModel
A real variable
ExpressionsBasedModel
Variable x = model.newRealVariable("x");
OptModel
RealVariable x = model.newRealVariable("x");
An integer variable
ExpressionsBasedModel
Variable n = model.newIntegerVariable("n");
OptModel
IntegerVariable n = model.newIntegerVariable("n");
A binary variable
ExpressionsBasedModel
Variable b = model.newBinaryVariable("b");
OptModel
BinaryVariable b = model.newBinaryVariable("b");
Bounds
ExpressionsBasedModel
Variable x = model.newRealVariable("x").lower(0).upper(10);

x.upper(null); // no upper bound again
OptModel
RealVariable x = model.newRealVariable("x").lower(0).upper(10);

x.upper(null); // no upper bound again

A bound that is null is no bound, in both. Most models need their variables to be non-negative, so remember the lower(0).

Constraints

A constraint is a linear expression in the variables, with a lower bound, an upper bound, or both — the same bound as both makes it an equality. In an ExpressionsBasedModel it is an Expression, in an OptModel an OptConstraint. The code is the same.

ExpressionsBasedModel
OptModel
x + y ≤ 10
ExpressionsBasedModel
Expression budget = model.newConstraint("budget").set(x, 1).set(y, 1).upper(10);
OptModel
OptConstraint budget = model.newConstraint("budget").set(x, 1).set(y, 1).upper(10);
2 ≤ x − y ≤ 8
ExpressionsBasedModel
model.newConstraint("range").set(x, 1).set(y, -1).lower(2).upper(8);
OptModel
model.newConstraint("range").set(x, 1).set(y, -1).lower(2).upper(8);
2x + 3y = 12
ExpressionsBasedModel
model.newConstraint("exact").set(x, 2).set(y, 3).level(12);
OptModel
model.newConstraint("exact").set(x, 2).set(y, 3).level(12);

The objective

How the objective is put together differs by design between the two, but with a recent ojAlgo they can be used the same way: getObjective() returns the objective, and you set its terms. Whether it is minimised or maximised is decided when solving, in both.

ExpressionsBasedModel
OptModel
3x + 5y
ExpressionsBasedModel
model.getObjective().set(x, 3).set(y, 5);
OptModel
model.getObjective().set(x, 3).set(y, 5);
x² + y² − 2x − 4y, with quadratic terms
ExpressionsBasedModel
model.getObjective().set(x, x, 1).set(y, y, 1).set(x, -2).set(y, -4);
OptModel
model.getObjective().set(x, x, 1).set(y, y, 1).set(x, -2).set(y, -4);

Solving

Both send the model to the Service and return a Future of the result. An OptModel always solves on the Service, so that is what its minimise() and maximise() do. An ExpressionsBasedModel does it with submit(Optimisation.Sense.MIN) or submit(Optimisation.Sense.MAX), which hands the model to its environment’s remote solver.

Here the code differs, for a reason: an ExpressionsBasedModel’s minimise() and maximise() solve right there, in your JVM, with ojAlgo’s own solvers or an integration, and return the result when they are done. They never use a remote solver, not even in an environment set up with one.

ExpressionsBasedModel
OptModel
On the Service
ExpressionsBasedModel
Future<Optimisation.Result> future = model.submit(Optimisation.Sense.MAX);

Optimisation.Result result = future.get();
OptModel
Future<OptResult> future = model.maximise();

OptResult result = future.get();

Both futures can be cancelled with future.cancel(true). An OptModel’s cancel also aborts the solve on the Service; an ExpressionsBasedModel’s only stops the waiting, and the Service solves on.

Reading the result

The result says whether the solution is optimal, or at least feasible, and holds the objective function’s value and the variables’ values — read the same way in both: the objective function’s value without an index, a variable’s value with one, in the order the variables were created. The variables have their values too, once the solve is done.

ExpressionsBasedModel
OptModel
Optimal, or at least feasible
ExpressionsBasedModel
if (result.isOptimal()) { … }
if (result.isFeasible()) { … }
OptModel
if (result.isOptimal()) { … }
if (result.isFeasible()) { … }
The objective function’s value
ExpressionsBasedModel
double value = result.doubleValue();
OptModel
double value = result.doubleValue();
A variable’s value
ExpressionsBasedModel
double xValue = x.doubleValue();
int nValue = n.intValue();
boolean bValue = b.booleanValue();
OptModel
double xValue = x.doubleValue();
int nValue = n.intValue();
boolean bValue = b.booleanValue();
The same, by index
ExpressionsBasedModel
double xValue = result.doubleValue(0);
BigDecimal exactly = result.get(0);
OptModel
double xValue = result.doubleValue(0);
BigDecimal exactly = result.get(0);

Where they differ

Most of a model reads the same in both. These are the differences to know about when moving a model from one to the other.

ExpressionsBasedModelOptModel
Where it solvesLocally, with ojAlgo’s solvers or an integration, or remotely, on the ServiceOn the Service only
Solvingminimise() returns the result when done; submit(…) returns a Futureminimise() returns a Future
CancellingStops the waiting; the Service solves onAlso aborts the solve on the Service
The objectiveWeights on variables and expressions, or getObjective() — they add upgetObjective() only
Constant terms–constant(…) on the objective and on constraints
Coefficientsset(x, 2) replaces, add(x, 2) adds to what is thereset(x, 2) only
Quadratic termsIn the objective and in constraints — given a solver that handles quadratic constraintsIn the objective only
Variable classesOne, Variable, made integer or binary as it is created or afterwardsThree, RealVariable, IntegerVariable and BinaryVariable, fixed when created
Values as they arrive–A receiver given when a variable is created: model.newRealVariable("x", value -> …)
JavaA recent version, and moving aheadJava 11 or later
DependenciesojAlgo, and the client library to solve on the ServiceThe client library only

From traditional ExpressionsBasedModel code

Code written for an ExpressionsBasedModel the traditional way still works as it always has. To make it read like the code on this page — the way an OptModel is written — and to solve it on the Service, this is what you change. The typed variable factories, newConstraint, getObjective() and the new ways to read values are in ojAlgo 58 and later.

Traditional
As on this page
Variables
Traditional
Variable x = model.newVariable("x");
Variable n = model.newVariable("n").integer();
Variable b = model.newVariable("b").binary();
As on this page
Variable x = model.newRealVariable("x");
Variable n = model.newIntegerVariable("n");
Variable b = model.newBinaryVariable("b");
Constraints
Traditional
model.newExpression("budget").set(x, 1).set(y, 1).upper(10);
As on this page
model.newConstraint("budget").set(x, 1).set(y, 1).upper(10);
The objective
Traditional
x.weight(3);
y.weight(5);
As on this page
model.getObjective().set(x, 3).set(y, 5);
Solving on the Service, not in your JVM
Traditional
ExpressionsBasedModel model = new ExpressionsBasedModel();
// …
Optimisation.Result result = model.maximise();
As on this page
ExpressionsBasedModel model = environment.newModel();
// …
Optimisation.Result result =
        model.submit(Optimisation.Sense.MAX).get();
Reading the result
Traditional
if (result.getState().isOptimal()) { … }
double value = result.getValue();
double xValue = x.getValue().doubleValue();
As on this page
if (result.isOptimal()) { … }
double value = result.doubleValue();
double xValue = x.doubleValue();

Weights and getObjective() add up, so when an objective moves to getObjective(), take the weights off the variables and expressions. The environment is the one set up with the client, as in Creating a model; to go on solving in your JVM, keep new ExpressionsBasedModel() and maximise(). From ojAlgo 58 the variables have their values after submit(…) too, not only after minimise() and maximise().