I/O

Export and import oximo models with MPS, LP or NL files.

The oximo-io crate writes Models to the standard text formats MPS, LP, and NL, and can read all three formats back into a Model. All I/O is gated on the io Cargo feature, which is on by default.

Use this when you want to:

  • Hand a Model to a solver oximo doesn't bundle (COPT, SCIP, CPLEX, ...)
  • Feed a nonlinear model to an AMPL-compatible solver via NL
  • Reproduce a bug report against a third-party tool
  • Archive the exact problem instance for later inspection
  • Inspect the model

🔗To a string

use oximo::io;

let mps = io::to_mps_string(&m)?;
let lp  = io::to_lp_string(&m)?;
let nl  = io::to_nl_string(&m)?;

Each returns a String you can log, hash, or feed into something else in memory.

🔗To a file

use oximo::io;
use std::fs::File;

io::write_mps(&m, &mut File::create("model.mps")?)?;
io::write_lp(&m, &mut File::create("model.lp")?)?;
io::write_nl(&m, &mut File::create("model.nl")?)?;

Each writer accepts anything that implements std::io::Write.

🔗Picking a format

MPS and LP describe linear and quadratic models. In general, you should use LP and reach for NL when the model has nonlinear expressions that MPS and LP cannot represent.

FormatProsWhen to pick
MPSUniversal, column-oriented, fixed historical formatMaximum solver compatibility, archival
LPHuman-readable, row-oriented, mirrors algebraic notationQuick inspection, sharing in bug reports
NLCompact, carries nonlinear structureNLP/MINLP models, AMPL-compatible solvers

🔗NL options

The NL writer is the most configurable of the three. write_nl_with and to_nl_string_with take a WriteOptions to select the NlFormat (binary or ASCII) and attach solver metadata: suffixes, defined variables, imported functions, and complementarity pairs.

use oximo::io::{NlFormat, WriteOptions, write_nl_with};
use std::fs::File;

let opts = WriteOptions { format: NlFormat::Ascii, ..Default::default() };
write_nl_with(&m, &mut File::create("model.nl")?, &opts)?;

write_nl_files emits the .nl alongside its companion .col/.row name files, which is what most AMPL-compatible solvers expect when you want readable names in the solution.

🔗NL operator support

The NL writer emits supported nonlinear operations as exact prefix opcode trees. The reader uses the same mapping for ASCII and little-endian binary NL:

OpcodeOperationoximo support
o13floorNot represented by the expression model
o14ceilNot represented by the expression model
o15absRead and written
o16unary -Read and written
o34logical notLogical expressions are not represented
o37tanhRead and written
o38tanRead and written
o39sqrtRead and written
o40sinhRead and written
o41sinRead and written
o42log10Read and written
o43logRead and written
o44expRead and written
o45coshRead and written
o46cosRead and written
o47atanhRead and written
o49atanRead and written
o50asinhRead and written
o51asinRead and written
o52acoshRead and written
o53acosRead and written

exp2(x) is written exactly as the power expression 2 ^ x (o5), because NL has no separate exp2 opcode.

The oximo operations cbrt, expm1, log1p, and log2 also have no direct NL opcode. Writing one returns IoError::UnsupportedNonlinearOperator. Reading floor, ceil, logical not, or another well-formed but unrepresentable opcode returns IoError::UnsupportedNl.

🔗Names round-trip

All writers preserve the Variable and constraint names from your model, so exported files cross-reference cleanly with SolverResult lookups such as dual_of and reduced_costs (see Results).

🔗Reading MPS models

Use read_mps_file for a path or read_mps for any text stream:

use oximo::io::{read_mps, read_mps_file};
use std::fs::File;

let model = read_mps_file("model.mps")?;
let model_from_stream = read_mps(File::open("model.mps")?)?;

The reader accepts the standard linear sections, range rows, integer markers, binary and semi-variable bounds, SOS sections, INDICATORS records, and the QUADOBJ, QMATRIX, QCMATRIX, and QSECTION quadratic extensions. An MPS indicator references an ordinary affine row using IF row binary 0|1; ranged indicator bodies are represented as two rows. MPS does not identify the coefficient scaling used by quadratic constraints, so the default is the Gurobi convention. Select CPLEX or MOSEK scaling explicitly when needed:

use oximo::io::{
    MpsQuadraticFormat, MpsReadOptions, read_mps_file_with,
};

let options = MpsReadOptions {
    quadratic_format: MpsQuadraticFormat::Cplex,
};
let model = read_mps_file_with("cplex-model.mps", &options)?;

Malformed input returns IoError::InvalidMps. Multiple alternative RHS, range, or bounds vectors and unsupported MPS constructs return IoError::UnsupportedMps.

🔗Reading NL models

The NL reader imports models produced by oximo or compatible AMPL-style tools. Use read_nl_file for a path or read_nl for any byte stream:

use oximo::io::{read_nl, read_nl_file};
use std::fs::File;

let model = read_nl_file("model.nl")?;
let model_from_stream = read_nl(File::open("model.nl")?)?;

Both ASCII and little-endian binary NL encodings are accepted. When a .row or .col sidecar exists beside the file, it supplies the original row and column names; otherwise deterministic names are generated. Interval rows and initial values are preserved when they can be represented by the core model.

The reader rejects malformed input with IoError::InvalidNl and well-formed NL sections that the core model cannot represent with IoError::UnsupportedNl. Imported functions, defined variables, logical/network constraints, complementarity sections, and unsupported expression opcodes are intentionally rejected.

🔗Reading LP models

LP files can be imported from a byte stream or a path with read_lp and read_lp_file:

use oximo::io::{read_lp, read_lp_file};
use std::fs::File;

let model = read_lp_file("model.lp")?;
let model_from_stream = read_lp(File::open("model.lp")?)?;

The reader supports the CPLEX LP linear and quadratic subset represented by the core model, including objectives, constraints, bounds, integer/binary and semicontinuous domains, quadratic terms, SOS sections, and inline indicator rows of the form binary = 0|1 -> affine relation. Ranged indicator bodies are written as two indicator rows. Malformed input returns IoError::InvalidLp with its source line and column. Unsupported LP sections return IoError::UnsupportedLp.

NL has no native segment for the indicator representation used by oximo. Writing a model with an active indicator returns IoError::UnsupportedNl.

🔗Skipping the writers

If you don't need file export, opt out of the io feature to drop the dependency:

[dependencies]
oximo = { version = "0.7", default-features = false, features = ["highs"] }

🔗Semidefinite constraints

The MPS, LP, and NL writers do not support active PSD constraints. Writing such a model returns IoError::Conic, including when other expressions make its model kind QCP or NLP. Inactive PSD blocks are skipped.

Solve SDP models directly with Clarabel or MOSEK.