\* Rust · CLI · TUI · Python *\
Parse, analyse, modify, write, diff, and solve Linear Programming files. Built on a LALRPOP grammar covering the CPLEX 22.1.1, Gurobi, FICO Xpress, and Mosek LP specifications.
\* everything below round-trips:
parse → modify → write → parse *\
Minimize
cost: 0.1 x1 + 0.2 x2 + 3055.2 x3
Subject To
capacity: 2 x1 + 4.5 x2 <= 200
demand: x1 + x2 >= 30
Bounds
0 <= x1 <= 40
x2 free
General
x3
End
What's here
Parse, inspect, modify, and write LP problems programmatically, with optional diff, serde, and solver features.
Six subcommands: parse, info, analyze, diff, convert, solve. Text, JSON, or YAML out.
Side-by-side coefficient-level diffs of LP/MPS files with fuzzy search, filters, and integrated HiGHS solving.
The same Rust engine from Python, with full type hints. pip install parse_lp.
The Rust library
diff feature) with numeric tolerances and regex name rewriting.serde feature).lp-solvers feature.use lp_parser_rs::{parser::parse_file, problem::LpProblem};
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());
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)?)?;
The full modification surface 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 knobs live on LpWriterOptions: include_problem_name, max_line_length, decimal_precision, include_section_spacing.
| Feature | Adds |
|---|---|
| diff | Structural comparison between two LP problems. |
| serde | JSON / YAML serialisation of problems, analyses, and diffs. |
| lp-solvers | External solver integration: CBC, GLPK, Gurobi, CPLEX. |
The command line
lp_parserEvery subcommand writes to stdout or a file (-o) as text, JSON, or YAML (-f, plus --pretty). Global flags: -v/--verbose (repeatable), -q/--quiet.
lp_parser parse problem.lp
lp_parser parse problem.lp --format yaml -o problem.yaml
Counts plus optional full listings via --variables, --constraints, --objectives.
lp_parser info problem.lp --variables --constraints --objectives
lp_parser info problem.lp --format json --pretty
| Option | Default | Description |
|---|---|---|
| --issues-only | off | Skip full analysis; show warnings/errors only |
| --large-coeff-threshold | 1e9 | Warn on coefficients larger than this |
| --small-coeff-threshold | 1e-9 | Warn on coefficients smaller than this |
| --ratio-threshold | 1e6 | Warn on coefficient scaling ratios above this |
lp_parser analyze problem.lp --issues-only
lp_parser analyze problem.lp --large-coeff-threshold 1e8 --ratio-threshold 1e5
# 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: []
| Option | Default | Description |
|---|---|---|
| --abs-tol | 0.0 | Absolute tolerance for numeric comparisons |
| --rel-tol | 0.0 | Relative tolerance: |a−b| ≤ rel_tol · max(|a|, |b|) |
| --rename P R | — | Regex rewrite applied to names in both files before matching; repeatable |
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
Targets: lp, csv, json, yaml. LP output honours --precision, --max-line-length, --no-problem-name, and --compact; CSV writes constraints.csv, objectives.csv, and variables.csv to a directory.
lp_parser convert problem.lp --format lp --precision 4 --compact
lp_parser convert problem.lp --format csv --output ./out
Solves with CBC (default) or GLPK from your PATH (-s/--solver). Multiple objectives and strict inequalities error; SOS constraints are ignored with a warning.
lp_parser solve problem.lp --solver glpk --format json --pretty
The terminal UI
lp_diffAn interactive ratatui explorer and diff viewer for LP and MPS files. One file opens a single-model explorer; two files diff them, in any mix of formats (lp_diff model.lp model.mps). It has grown well past a diff viewer: there is a background solver, in-memory what-if edits, solution-preserving rewrites, and a diagnostics pane that names the constraints slowing a solve down.
r: regex, s: substring, and c: content modes. Plus a command palette and a 100-entry jumplist.Python bindings
parse_lpThe same Rust engine behind a typed Python API: parsing, data access, modification, LP writing, analysis with configurable thresholds, CSV export, and problem-to-problem comparison.
from parse_lp import LpParser
parser = LpParser("problem.lp")
parser.parse()
print(parser.name, parser.sense, parser.variable_count())
parser.update_objective_coefficient("OBJ", "x1", 5.0)
parser.rename_variable("x2", "production")
parser.update_constraint_rhs("C1", 100.0)
parser.save_to_file("modified_problem.lp")
analysis = parser.analyze()
print(analysis["summary"]["density"])
Getting it
| Where | Command |
|---|---|
| Rust crate | lp_parser_rs = { version = "4.1.0", features = ["serde", "diff"] } |
| CLI | cargo install lp_parser_rs --all-features |
| TUI | cargo install --path tui (from a clone) |
| Python | pip install parse_lp |
Develop with cargo insta test --all-features and review snapshot changes with cargo insta review. Contributions welcome: open a pull request.