Skip to content

dandxy89/lp_parser_rs

Repository files navigation

Rust LP File Parser, Writer, and Diff Tool

Cargo Test Crates.io Documentation PyPI version PyPI Downloads

A robust Rust library and CLI for parsing, analysing, modifying, and writing Linear Programming (LP) files. Built on LALRPOP; grammar lives in lp.lalrpop.

Supported specifications: IBM CPLEX v22.1.1, FICO Xpress, Gurobi, Mosek.

Features

  • Parsing & writing — round-trip LP files (parse → modify → write → parse) with configurable formatting; MPS files can also be written via mps::writer (write_mps_string), letting LP and MPS problems convert to either format
  • Problem modification — rename / update / remove objectives, constraints, variables, coefficients, and RHS values
  • Variable types — integer, general, bounded, free, semi-continuous
  • Analysis — statistics, matrix density, sparsity, coefficient ranges, issue detection with configurable thresholds
  • Diff (diff feature) — structural and numeric comparison between two LP problems, callable from the library via LpProblem::diff or diff::compare
  • Serialisation (serde feature) — JSON / YAML support
  • External solvers (lp-solvers feature) — CBC, Gurobi, CPLEX, GLPK via the lp-solvers crate

Library Usage

Add to Cargo.toml:

[dependencies]
lp_parser_rs = { version = "4.1.0", features = ["serde", "diff"] } # x-release-please-version

Parse and inspect:

use lp_parser_rs::{parser::parse_file, problem::LpProblem};
use std::path::Path;

let content = parse_file(Path::new("problem.lp"))?;
let problem = LpProblem::parse(&content)?;
println!("{} objectives, {} constraints, {} variables",
    problem.objective_count(), problem.constraint_count(), problem.variable_count());

Modify and write:

use lp_parser_rs::{problem::LpProblem, writer::write_lp_string, model::VariableType};

let mut problem = LpProblem::parse(&std::fs::read_to_string("problem.lp")?)?;

problem.update_objective_coefficient("profit", "x1", 5.0)?;
problem.rename_objective("profit", "total_profit")?;
problem.update_constraint_coefficient("capacity", "x1", 2.0)?;
problem.update_constraint_rhs("capacity", 200.0)?;
problem.rename_variable("x1", "production_a")?;
problem.update_variable_type("production_a", VariableType::Integer)?;

std::fs::write("modified.lp", write_lp_string(&problem))?;

Available modification methods on LpProblem: update_objective_coefficient, rename_objective, remove_objective, update_constraint_coefficient, update_constraint_rhs, rename_constraint, remove_constraint, rename_variable, update_variable_type, remove_variable.

Writer options: write_lp_string_with_options(&problem, &LpWriterOptions { include_problem_name, max_line_length, decimal_precision, include_section_spacing }).

Command-Line Interface (lp_parser)

Install

cargo install lp_parser_rs --all-features
# Or from source
git clone https://github.com/dandxy89/lp_parser_rs.git
cd lp_parser_rs/rust && cargo build --release --all-features

Global options

Flag Description
-v, --verbose Increase output verbosity (repeatable)
-q, --quiet Suppress non-essential output
-h, --help / -V, --version Print help / version

parse — display file structure

Option Default Description
<FILE> Path to the LP file (required)
-o, --output <PATH> stdout Write output to file
-f, --format <FMT> text text, json (serde), yaml (serde)
--pretty off Pretty-print JSON/YAML
lp_parser parse problem.lp
lp_parser parse problem.lp --format yaml -o problem.yaml

info — summary statistics

Adds to the parse options:

Option Description
--variables List all variables with their types
--constraints List all constraints
--objectives List all objectives
lp_parser info problem.lp
lp_parser info problem.lp --variables --constraints --objectives
lp_parser info problem.lp --format json --pretty

analyze — structural analysis & issue detection

Adds to the parse options:

Option Default Description
--issues-only off Skip full analysis; show warnings/errors only
--large-coeff-threshold <F> 1e9 Warn on coefficients larger than this
--small-coeff-threshold <F> 1e-9 Warn on coefficients smaller than this
--ratio-threshold <F> 1e6 Warn on coefficient scaling ratios above this
lp_parser analyze problem.lp
lp_parser analyze problem.lp --issues-only
lp_parser analyze problem.lp --large-coeff-threshold 1e8 --ratio-threshold 1e5
lp_parser analyze problem.lp --format yaml -o analysis.yaml
Example output
summary: { name: diet, sense: Minimize, objective_count: 1, constraint_count: 7, variable_count: 16, density: 0.571 }
variables: { type_distribution: { upper_bounded: 9, double_bounded: 7 }, discrete_variable_count: 0 }
constraints: { type_distribution: { equality: 7 }, rhs_range: { min: 30.0, max: 50000.0 } }
coefficients: { constraint_coeff_range: { min: 0.1, max: 3055.2 }, coefficient_ratio: 101840.0 }
issues: []

diff — compare two LP files (requires diff feature)

Option Default Description
<FILE1> <FILE2> Base and comparison files
-o, --output <PATH> stdout Write output to file
-f, --format <FMT> text text, json, yaml
--pretty off Pretty-print structured output
--abs-tol <F> 0.0 Absolute tolerance for numeric comparisons
--rel-tol <F> 0.0 Relative tolerance: `
--rename <PATTERN> <REPL> Regex rewrite applied to names in both files before matching; repeatable
lp_parser diff old.lp new.lp
lp_parser diff old.lp new.lp --abs-tol 1e-6 --rel-tol 1e-9
lp_parser diff old.lp new.lp --rename '\[\d+\]$' '[N]' --format json --pretty

CI usage

Both diff and analyze follow the GNU diff exit-code convention, so a pipeline can gate on unexpected model changes or structural errors:

  • diff exits 0 when the problems match, 1 when they differ, 2 on failure.
  • analyze exits 0 when clean, 1 when any error-severity issue is found, 2 on failure.
lp_parser diff baseline.lp candidate.lp --format json > model_diff.json || echo "model changed"
lp_parser analyze candidate.lp --issues-only || exit 1

convert — translate to another format

Option Default Description
<FILE> Path to the LP or MPS file (.mps extension reads MPS)
-o, --output <PATH> stdout Output file or directory (required for CSV)
-f, --format <FMT> lp lp, mps, csv, json, yaml
--pretty off Pretty-print JSON/YAML
--precision <N> 6 Decimal precision for numbers
--max-line-length <N> 80 Line-wrap threshold for LP output
--no-problem-name off Omit problem-name comment in LP output
--compact off No section spacing
lp_parser convert problem.lp --format lp --precision 4 --compact
lp_parser convert problem.lp --format csv --output ./out     # writes constraints.csv, objectives.csv, variables.csv
lp_parser convert problem.lp --format json --pretty -o problem.json
lp_parser convert problem.lp --format mps -o problem.mps     # LP -> MPS
lp_parser convert problem.mps --format mps -o rewritten.mps  # MPS -> MPS

Caveat (LP → MPS): a variable that appears only in constraints and is never given an explicit bound defaults to Free internally, which is written out as an MPS FR bound. LP format's own default for such a variable is [0, +inf), not free -- so an unbounded-looking variable can widen to include negative values once converted to MPS. Declare bounds explicitly (even a redundant x >= 0) in the LP source if this distinction matters to the downstream solver.

Caveat (MPS integer defaults): an integer variable declared inside 'MARKER' INTORG/INTEND with no explicit BOUNDS entry defaults to [0, 1], following the CPLEX MPS specification. Gurobi instead defaults such variables to [0, +inf), so the same file can yield different models in different readers. Declare bounds explicitly if the wider range is intended.

solve — run an external solver (requires lp-solvers feature)

Option Default Description
<FILE> Path to the LP file
-s, --solver <NAME> cbc cbc, glpk
-o, --output <PATH> stdout Write solution to file
-f, --format <FMT> text text, json, yaml
--pretty off Pretty-print structured output
lp_parser solve problem.lp
lp_parser solve problem.lp --solver glpk
lp_parser solve problem.lp --format json --pretty

The selected solver binary must be installed on your PATH. The compatibility layer does not support multiple objectives (errors), strict inequalities (<, >), or SOS constraints (ignored with a warning).

LP Model Explorer and Diff Viewer (lp_diff)

A terminal UI for exploring a single LP/MPS model or comparing two, with coefficient-level side-by-side diffs, fuzzy search, filtering, and integrated HiGHS solving. Built with ratatui. Pass one file to inspect a model, or two files to diff them.

lp_diff demo

The demo diffs an MPS file against an LP file: side-by-side constraint diffs, filters, sort and live tolerance cycling, fuzzy search, Numerics, the keybindings pop-up, and a parallel HiGHS solve with solution diff.

cargo install --path tui
lp_diff model.lp                       # inspect a single model
lp_diff base.lp modified.lp            # diff two files
lp_diff model.lp --summary             # non-interactive single-model summary
lp_diff base.lp modified.lp --summary  # non-interactive diff summary

Tolerance & rename (parity with lp_parser diff)

lp_diff accepts the same name-rewrite and numeric-tolerance flags as the CLI diff command. Active options are shown on the Summary panel (and in --summary output) so results are reproducible.

Option Default Description
--abs-tol <F> 0.0 Absolute tolerance for RHS & coefficient comparisons
--rel-tol <F> 0.0 Relative tolerance: `
--rename <PATTERN> <REPL> Regex rewrite applied to names in both files before matching; repeatable
# Collapse indexed names (e.g. x[1,2,foo] / x[9,9,baz] → x[idx]) so structural diffs survive renumbering
lp_diff base.lp modified.lp --rename '\[\d+,\d+,[^]]*\]$' '[idx]'

# Hide near-equal RHS/coefficient drift
lp_diff base.lp modified.lp --abs-tol 1e-4 --rel-tol 1e-6

# Combine with --summary for scripting
lp_diff base.lp modified.lp --summary --rename '\[\d+\]$' '[idx]' --abs-tol 1e-6

Highlights: three-panel layout, five sections (Summary / Variables / Constraints / Objectives / Numerics), filtering (a/+/-/m/=, plus o to ignore coefficient order), sort cycling (s), live tolerance cycling (t/T), telescope-style search (fuzzy, r: regex, s: substring), HiGHS solve-and-compare with infeasibility diagnosis (S), vim-style navigation and jumplist, clipboard yank (y/Y), CSV export (w), ? for full help.

See tui/README.md for the complete reference.

Development

cargo insta test --all-features   # run tests
cargo insta review                # review snapshot changes

Test data sources: Jplex, LPWriter.jl, Lp-Parser.

Contributing

Contributions are welcome — please open a Pull Request.

About

Rust LP File Parser and Diff tool

Topics

Resources

License

Apache-2.0, MIT licenses found

Licenses found

Apache-2.0
LICENSE-APACHE
MIT
LICENSE-MIT

Stars

5 stars

Watchers

2 watching

Forks

Contributors