\* Rust · CLI · TUI · LSP · Python *\

A parser for LP files,and the four tools built on it.

Read LP and MPS files into a model you can inspect, edit, analyse, diff and write back out. The LP side is a LALRPOP grammar that follows the CPLEX 22.1.1, Gurobi, FICO Xpress and Mosek LP specifications.

crates.io lp_parser_rs PyPI parse-lp
\* 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

The crate and its tools

lp_parser_rs — Rust crate

Parse, inspect, modify and write LP and MPS problems from Rust. Diff, serde and external solvers are optional features.

lp_parser — CLI

Six subcommands: parse, info, analyze, diff, convert, solve. LP or MPS in; text, JSON or YAML out.

lp_diff — interactive TUI

Explore one LP or MPS model, or diff two down to the coefficient. Solves with HiGHS in the background.

parse_lp — Python package

The Rust parser and writer from Python, with type stubs. pip install parse_lp.

lp-lsp — language server

Diagnostics, navigation, rename, completion, formatting, and code actions for .lp files in VS Code, Neovim, Helix, Zed, or any LSP client.

The Rust library

Library capabilities

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 back

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

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)?)?; // errors on names LP cannot represent

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 live on LpWriterOptions (include_problem_name, max_line_length, decimal_precision, include_section_spacing) and are passed to write_lp_string_with_options. decimal_precision defaults to None, which writes each number in its shortest exact form.

Cargo features

FeatureAdds
cliBuilds the lp_parser binary. diff, solve, CSV output and JSON/YAML output each also need the matching feature below.
csvCSV export of constraints, objectives, and variables.
mmapparser::MappedFile: memory-maps an input file so it can be parsed without first copying it into a String.
diffLpProblem::diff and diff::compare. Turns on serde.
serdeSerde derives on problems, analyses and diffs.
lp-solversAdapter for the lp-solvers crate: CBC, GLPK, Gurobi, CPLEX.

The command line

CLI: lp_parser

Every subcommand reads LP or MPS (a .mps extension reads MPS) and writes to stdout or a file (-o). -f picks text, JSON or YAML, and --pretty indents JSON. Global flags: -v/--verbose, -q/--quiet.

parse — display file structure

lp_parser parse problem.lp
lp_parser parse problem.lp --format yaml -o problem.yaml

info — summary statistics

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

analyze — structural analysis and warnings

OptionDefaultDescription
--issues-onlyoffSkip full analysis; show warnings/errors only
--large-coeff-threshold1e9Warn on coefficients larger than this
--small-coeff-threshold1e-9Warn on non-zero coefficients smaller than this
--large-rhs-threshold1e9Warn on right-hand sides larger in magnitude than this
--ratio-threshold1e6Warn 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: []

diff — compare two LP or MPS files

OptionDefaultDescription
--abs-tol0.0Absolute tolerance for numeric comparisons
--rel-tol0.0Relative 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

Both diff and analyze follow GNU diff's exit codes, so CI can gate on them: diff exits 0 when the problems match, 1 when they differ and 2 on failure; analyze exits 0 when clean, 1 on an error-severity issue and 2 on failure.

convert — translate to another format

Targets: lp, mps, csv, json, yaml. Without --precision, numbers are written in their shortest exact form. LP output honours --precision, --max-line-length, --no-problem-name, and --compact; MPS output honours --precision; 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

solve — run an external solver

Solves with CBC (default) or GLPK, which must be on your PATH (-s/--solver). Multiple objectives, strict inequalities, quadratic terms, and indicator or general constraints are errors; SOS constraints are dropped with a warning.

lp_parser solve problem.lp --solver glpk --format json --pretty

The terminal UI

TUI: lp_diff

A ratatui explorer and diff viewer for LP and MPS files. One file opens a single-model explorer; two files are diffed, in any mix of formats (lp_diff model.lp model.mps). It also solves with HiGHS, re-solves after an RHS edit, applies presolve-style rewrites and compares the result, and ranks the rows and columns most likely to be slowing a solve down.

Editor support

Language server: lp-lsp

A language server for .lp files. It understands the CPLEX LP format and the Gurobi extensions the parser supports, and keeps working while a file is half-written. It talks LSP over stdio and takes no arguments, so any editor that can run a language server can use it.

-- Neovim 0.11+: ~/.config/nvim/lsp/lp_lsp.lua
return {
  cmd = { 'lp-lsp' },
  filetypes = { 'lp' },
  root_markers = { '.git' },
}

-- init.lua
vim.filetype.add({ extension = { lp = 'lp' } })
vim.lsp.enable('lp_lsp')

A VS Code extension lives in lsp/editors/vscode (build it from source). Helix needs a languages.toml entry, and Zed a small dev extension to register the language; the README has both.

Python bindings

Python: parse_lp

Python bindings to the Rust library, with type stubs (Python 3.9+). They cover parsing, data access, modification, LP and MPS writing, analysis with configurable thresholds, CSV export and diff between two problems.

from parse_lp import LpParser

parser = LpParser("problem.lp")

print(parser.name, parser.sense, parser.num_variables)

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"])

The constructor parses the file straight away; the format follows the extension (.mps reads MPS). LpParser.from_file(path, format="mps") overrides it, LpParser.from_string(text) parses in-memory text, and to_mps_string / save_to_mps write MPS back out.

Getting it

Install

WhereCommand
Rust cratelp_parser_rs = { version = "5.0.1", features = ["serde", "diff"] }
CLIcargo install lp_parser_rs --features cli,csv,diff,serde,lp-solvers
TUIcargo install --path tui (from a clone)
Language servercargo install --path lsp (from a clone)
Pythonpip install parse_lp

Develop with cargo insta test --all-features and review snapshot changes with cargo insta review. Pull requests are welcome on GitHub.