Exporting Models to MPS and LP Formats#
discopt models can be exported to standard file formats for use with external solvers such as Gurobi, CPLEX, MOSEK, HiGHS, and SCIP. This is useful when you want to formulate a model using discopt’s expressive Python API but solve it with a specialized solver that reads industry-standard formats.
Two formats are supported:
MPS (Mathematical Programming System): the most widely supported format across commercial and open-source solvers. Supports LP, MILP, and QP problems.
CPLEX LP: a human-readable format with named variables and constraints, useful for debugging and inspection.
Both exporters work with linear and quadratic models. Nonlinear expressions (e.g.,
dm.exp, dm.log, dm.sin) will raise a ValueError since these formats cannot
represent arbitrary nonlinear functions.
For background on MPS format, see [Murtagh and Saunders, 1983]. The LP format follows the CPLEX convention documented in [IBM, 2022].
Basic LP Export#
Let us start with a simple linear program and export it to both formats.
import discopt.modeling as dm
m = dm.Model("production")
x = m.continuous("steel", lb=0, ub=100)
y = m.continuous("aluminum", lb=0, ub=80)
m.minimize(25 * x + 30 * y)
m.subject_to(x + y >= 50, name="demand")
m.subject_to(2 * x + y <= 120, name="capacity")
# Export to LP format (returns a string)
lp_string = m.to_lp()
print(lp_string)
\ Problem: production
Minimize
obj: 25 steel + 30 aluminum
Subject To
demand: - steel - aluminum <= -50
capacity: 2 steel + aluminum <= 120
Bounds
0 <= steel <= 100
0 <= aluminum <= 80
End
# Export to MPS format
mps_string = m.to_mps()
print(mps_string)
NAME production
ROWS
N OBJ
L demand
L capacity
COLUMNS
steel OBJ 25
steel demand -1
steel capacity 2
aluminum OBJ 30
aluminum demand -1
aluminum capacity 1
RHS
RHS demand -50
RHS capacity 120
BOUNDS
LO BND steel 0
UP BND steel 100
LO BND aluminum 0
UP BND aluminum 80
ENDATA
Writing to Files#
Both methods accept an optional file path. When a path is provided, the output is
written to disk and the method returns None.
import os
import tempfile
with tempfile.TemporaryDirectory() as tmpdir:
mps_path = os.path.join(tmpdir, "production.mps")
lp_path = os.path.join(tmpdir, "production.lp")
m.to_mps(mps_path)
m.to_lp(lp_path)
print(f"MPS file size: {os.path.getsize(mps_path)} bytes")
print(f"LP file size: {os.path.getsize(lp_path)} bytes")
MPS file size: 350 bytes
LP file size: 203 bytes
MILP Export with Binary and Integer Variables#
The exporters handle binary and integer variables correctly. Binary variables appear
in the Binaries section of the LP file and are marked with INTORG/INTEND
markers in MPS. Integer variables appear in the Generals section of LP and also
use integer markers in MPS.
m2 = dm.Model("facility_location")
x = m2.continuous("flow", shape=(3,), lb=0, ub=100)
y = m2.binary("open", shape=(3,))
m2.minimize(dm.sum(lambda i: 10 * x[i] + 50 * y[i], over=range(3)))
for i in range(3):
m2.subject_to(x[i] <= 100 * y[i], name=f"link_{i}")
m2.subject_to(x[0] + x[1] + x[2] >= 80, name="demand")
print(m2.to_lp())
\ Problem: facility_location
Minimize
obj: 10 flow_0 + 10 flow_1 + 10 flow_2 + 50 open_0 + 50 open_1 + 50 open_2
Subject To
link_0: flow_0 - 100 open_0 <= 0
link_1: flow_1 - 100 open_1 <= 0
link_2: flow_2 - 100 open_2 <= 0
demand: - flow_0 - flow_1 - flow_2 <= -80
Bounds
0 <= flow_0 <= 100
0 <= flow_1 <= 100
0 <= flow_2 <= 100
Binaries
open_0 open_1 open_2
End
QP Export#
Quadratic objectives are supported. The MPS exporter writes a QUADOBJ section,
and the LP exporter writes the quadratic terms in the standard [ ... ] / 2
notation.
m3 = dm.Model("portfolio")
w = m3.continuous("w", shape=(2,), lb=0, ub=1)
# Minimize variance: w1^2 + 0.5*w1*w2 + w2^2
m3.minimize(w[0] * w[0] + 0.5 * w[0] * w[1] + w[1] * w[1])
m3.subject_to(w[0] + w[1] == 1, name="budget")
print("=== MPS (with QUADOBJ) ===")
print(m3.to_mps())
print()
print("=== LP (with quadratic section) ===")
print(m3.to_lp())
=== MPS (with QUADOBJ) ===
NAME portfolio
ROWS
N OBJ
E budget
COLUMNS
w_0 budget 1
w_1 budget 1
RHS
RHS budget 1
BOUNDS
LO BND w_0 0
UP BND w_0 1
LO BND w_1 0
UP BND w_1 1
QUADOBJ
w_0 w_0 2
w_0 w_1 0.5
w_1 w_1 2
ENDATA
=== LP (with quadratic section) ===
\ Problem: portfolio
Minimize
obj: 0
+ [
2 w_0 ^ 2 + w_0 * w_1 + 2 w_1 ^ 2
] / 2
Subject To
budget: w_0 + w_1 = 1
Bounds
0 <= w_0 <= 1
0 <= w_1 <= 1
End
Round-Trip Verification with HiGHS#
If highspy is installed, you can verify that the exported file produces the same
solution when read back by an external solver. This provides a useful correctness
check for the export process.
try:
import highspy
m_rt = dm.Model("roundtrip")
x = m_rt.continuous("x", lb=0, ub=10)
y = m_rt.continuous("y", lb=0, ub=10)
m_rt.minimize(3 * x + 2 * y)
m_rt.subject_to(x + y >= 5, name="demand")
with tempfile.NamedTemporaryFile(suffix=".mps", delete=False) as f:
mps_path = f.name
m_rt.to_mps(mps_path)
h = highspy.Highs()
h.setOptionValue("output_flag", False)
h.readModel(mps_path)
h.run()
obj = h.getInfoValue("objective_function_value")[1]
print(f"HiGHS objective from exported MPS: {obj}")
print("Expected: 10.0 (y=5, x=0)")
os.unlink(mps_path)
except ImportError:
print("highspy not installed; skipping round-trip test")
HiGHS objective from exported MPS: 10.0
Expected: 10.0 (y=5, x=0)
Limitations#
The MPS and LP exporters only support linear and quadratic models. Attempting to
export a model with nonlinear terms will raise a ValueError with a descriptive
message indicating which expression type is unsupported.
m_nl = dm.Model("nonlinear_example")
x = m_nl.continuous("x", lb=0.1, ub=10)
m_nl.minimize(dm.log(x))
m_nl.subject_to(x >= 1)
try:
m_nl.to_mps()
except ValueError as e:
print(f"Expected error: {e}")
Expected error: Expression type 'FunctionCall' cannot be exported. Only linear and quadratic expressions are supported.