Optimisation Model File Formats
There are a number of different file formats around for persisting optimisation models, ojAlgo even has its own model file format.
One of the file formats stands out as “the” standard. MPS (Mathematical Programming System) is a file format for describing linear programming (LP) and mixed integer programming (MIP) problems. It’s an old format developed by IBM for one of their early optimisation products, but it’s still widely used and supported by all the major commercial LP solvers. There has been several extensions to the original specification. Most notably the possibility to describe QP models.
WikipediA has a description of the format as well as a very small example file. (I’ve seen this particular model/file used in other places, and we’ll use it for this example.) It’s a text file so it is human readable. If you just know a little bit about linear programming you can read the model (since it’s so small).
ojAlgo supports reading MPS files. The main reason for that is to be able to make use of industry standard test model collections. A large part of ojAlgo’s optimisation test cases are based on (standard) MPS files found in various such collections. In particular there are tests from:
- http://www.netlib.org/lp/data/
- http://miplib.zib.de
- ftp://ftp.numerical.rl.ac.uk/pub/cutest//marosmeszaros/marmes.html
- https://people.sc.fsu.edu/~jburkardt/datasets/mps/
To persist an ExpressionsBasedModel, in order to later read it back, you need to use ojAlgo’s own file format (EBM). It’s the only file format currently supported to write a model.
- MPS: read (with the most important format extensions supported)
- EBM: read/write (it’s ojAlgo’s own file format)
testprob.mpsNAME TESTPROB
ROWS
N COST
L LIM1
G LIM2
E MYEQN
COLUMNS
XONE COST 1 LIM1 1
XONE LIM2 1
YTWO COST 4 LIM1 1
YTWO MYEQN -1
ZTHREE COST 9 LIM2 1
ZTHREE MYEQN 1
RHS
RHS1 LIM1 5 LIM2 10
RHS1 MYEQN 7
BOUNDS
UP BND1 XONE 4
LO BND1 YTWO -1
UP BND1 YTWO 1
ENDATA
Example Code
UsingMPS.javaimport java.io.File;
import org.ojalgo.OjAlgoUtils;
import org.ojalgo.netio.BasicLogger;
import org.ojalgo.optimisation.Expression;
import org.ojalgo.optimisation.ExpressionsBasedModel;
import org.ojalgo.optimisation.Optimisation.Result;
import org.ojalgo.optimisation.Variable;
/**
* A program that shows how to use an MPS file with ojAlgo
*
* @see https://www.ojalgo.org/2022/05/optimisation-model-file-formats/
* @see https://github.com/optimatika/ojAlgo/wiki/Using-MPS
*/
public class UsingMPS {
public static void main(final String[] args) {
BasicLogger.debug();
BasicLogger.debug(UsingMPS.class);
BasicLogger.debug(OjAlgoUtils.getTitle());
BasicLogger.debug(OjAlgoUtils.getDate());
BasicLogger.debug();
// Get a reference to the MPS-file
File mpsFile = new File("./testprob.mps");
// Instantiate the MPS-model
ExpressionsBasedModel model = ExpressionsBasedModel.parse(mpsFile);
// Optionally validate the model
if (model.validate()) {
BasicLogger.debug("MPS-model ok!");
} else {
BasicLogger.debug("MPS-model problem!");
}
BasicLogger.debug(model);
// The MPS format does not include a standard way to specify if the
// model/problem is meant to be minimised or maximised. There is a convention
// to include an OBJSENSE section where this would be specified, but this
// is not a requirement.
Result minimiseResult = model.minimise();
// Print the result
BasicLogger.debug("Minimised => " + minimiseResult);
// The solution variable values are returned in the order the columns/variables
// were defined in the MPS-file.
Result maximiseResult = model.maximise();
BasicLogger.debug("Maximised => " + maximiseResult);
/*
* Reading an MPS creates an ExpressionsBasedModel just as if you'd created it programatically, and
* you may continue work on that model.
*/
BasicLogger.debug();
BasicLogger.debug("=== Variables ===");
for (Variable var : model.getVariables()) {
BasicLogger.debug(var);
}
BasicLogger.debug();
BasicLogger.debug("=== Expressions ===");
for (Expression exp : model.getExpressions()) {
BasicLogger.debug(exp);
}
BasicLogger.debug();
// Alter the two inequalities to allow a slightly wider range
model.getExpression("LIM1").upper(5.5); // Change from 5.0 to 5.5
model.getExpression("LIM2").lower(9.5); // Change from 10.0 to 9.5
BasicLogger.debug("Modified => " + model.minimise());
// Now the solution is no longer integer valued (as it happened to be before),
// but we can add that requirement to each of the variables...
for (Variable var : model.getVariables()) {
var.integer(true);
}
// We get the old solution back
BasicLogger.debug("Integer constrained => " + model.minimise());
File modifiedModel = new File("/Users/apete/Developer/data/testprob.ebm");
/*
* We can also write the model to file, but now we have to use the EBM file format.
*/
model.writeTo(modifiedModel);
/*
* That model can be read back the same way we read the MPS file before.
*/
model = ExpressionsBasedModel.parse(modifiedModel);
BasicLogger.debug();
BasicLogger.debug("Modified model");
BasicLogger.debug(model);
}
}
Console
class UsingMPS
ojAlgo
2022-05-21
MPS-model ok!
############################################
0 <= XONE <= 4
-1 <= YTWO <= 1
0 <= ZTHREE
7 <= MYEQN: 0.0 <= 7
10 <= LIM2: 2.0
COST: 2.0 (1)
LIM1: 2.0 <= 5
############################################
Minimised => OPTIMAL 54.0 @ { 4, -1, 6 }
Maximised => OPTIMAL 80.0 @ { 4, 1, 8 }
=== Variables ===
2 <= XONE: 4 (1) <= 4
-1 <= YTWO: 1 (4) <= 1
6 <= ZTHREE: 8 (9) <= 8
=== Expressions ===
7 <= MYEQN <= 7
10 <= LIM2
COST
LIM1 <= 5
Modified => OPTIMAL 53.5 @ { 3.5, -1, 6 }
Integer constrained => INFEASIBLE 56.0 @ { 2, 0, 6 }
Modified model
############################################
2 <= XONE (1) <= 4
-1 <= YTWO (4) <= 1
6 <= ZTHREE (9) <= 8
7 <= MYEQN: 7.0 <= 7
9.5 <= LIM2: 10.0
COST: 66.0
LIM1: 3.0 <= 5.5
############################################