numnom 0.3.0

A fast MPS file parser for mathematical programming
Documentation

numnom

crates.io docs.rs license

A fast MPS file parser written in Rust.

Features

  • Fast: 745 MB/s throughput on raw MPS text
  • Zero-copy parsing: borrows from input buffer — minimal allocations during parsing
  • Direct CSC output: builds sparse column-wise matrix during parsing with no intermediate storage
  • Compressed files: reads .mps.gz with SIMD-accelerated decompression (zlib-rs)
  • Well-tested: 0 failures across all 1065 MIPLIB 2017 instances

Installation

# As a library
cargo add numnom

# As a CLI tool
cargo install numnom

Usage

CLI

# Parse and display summary
numnom problem.mps

# Parse gzipped file
numnom problem.mps.gz

# Quiet mode (validate only, no output)
numnom problem.mps.gz -q

Example output:

  Loading s82.mps.gz (decompressing)... done in 1.35s

  s82 (min)

  Statistics
  ----------

  Rows              87.9K    Cols             1.7M
  Nonzeros           7.0M    Density       0.0047%
  Continuous           54
  Binary             1.7M

  Timing
  ------

  Decompress   49.5MB -> 884.8MB in 299.8ms
  Parse        884.8MB in 1.05s
    Rows            6.9ms  (1%)
    Columns       580.9ms  (56%)
    RHS             0.1ms  (0%)
    Bounds        398.7ms  (38%)
    Finalize       43.0ms  (4%)

Library

use numnom::{parse_mps_file, parse_mps_str, Model, VarType};

// Parse from file (supports .mps and .mps.gz)
let model = parse_mps_file("problem.mps.gz").unwrap();

// Parse from string
let model = parse_mps_str(mps_content).unwrap();

// Access model data
println!("Rows: {}, Cols: {}", model.num_row, model.num_col);
println!("Nonzeros: {}", model.a_matrix.value.len());

// Sparse matrix in CSC format
let col = 0;
let start = model.a_matrix.start[col] as usize;
let end = model.a_matrix.start[col + 1] as usize;
for i in start..end {
    let row = model.a_matrix.index[i];
    let val = model.a_matrix.value[i];
    println!("  A[{}, {}] = {}", row, col, val);
}

// Variable info
for (i, name) in model.col_names.iter().enumerate() {
    let lb = model.col_lower[i];
    let ub = model.col_upper[i];
    let cost = model.col_cost[i];
    let vtype = model.col_integrality[i];
    println!("{}: [{}, {}] cost={} type={:?}", name, lb, ub, cost, vtype);
}

Model structure

pub struct Model {
    pub name: String,
    pub num_row: u32,
    pub num_col: u32,
    pub obj_sense_minimize: bool,
    pub obj_offset: f64,
    pub objective_name: String,
    pub col_cost: Vec<f64>,        // Objective coefficients
    pub col_lower: Vec<f64>,       // Variable lower bounds
    pub col_upper: Vec<f64>,       // Variable upper bounds
    pub row_lower: Vec<f64>,       // Constraint lower bounds
    pub row_upper: Vec<f64>,       // Constraint upper bounds
    pub a_matrix: SparseMatrix,    // Constraint matrix (CSC)
    pub col_names: Vec<String>,
    pub row_names: Vec<String>,
    pub col_integrality: Vec<VarType>,
}

pub struct SparseMatrix {
    pub start: Vec<u32>,   // Column pointers
    pub index: Vec<u32>,   // Row indices
    pub value: Vec<f64>,   // Nonzero values
}

MPS sections supported

Section Status
NAME supported
OBJSENSE supported
ROWS supported
COLUMNS supported
RHS supported
BOUNDS (LO/UP/FX/FR/MI/PL/BV/LI/UI/SC/SI) supported
RANGES supported
INDICATORS, SOS, QUADOBJ, etc. skipped

Benchmarks

Tested on Apple M-series, single-threaded. All times include gzip decompression.

MIPLIB 2017 benchmark set (240 instances):

Metric numnom SCIP
Shifted geomean (s=10ms) 19ms 82ms
Median 10ms 38ms
P90 118ms 243ms
Max 930ms 2.9s
Speedup (shifted geomean) 4.2x

Correctness validated against SCIP via russcip on all 1065 MIPLIB 2017 instances (0 failures).

Per-instance results: benchmarks.csv

License

Licensed under the Apache License, Version 2.0. See LICENSE for details.