lp_parser_rs 3.5.0

A Rust parser for the LP file format.
Documentation
# Rust LP File Parser, Writer, and Diff Tool

[![Cargo Test](https://github.com/dandxy89/congenial-enigma/actions/workflows/cargo_test.yml/badge.svg)](https://github.com/dandxy89/congenial-enigma/actions/workflows/cargo_test.yml)
[![Crates.io](https://img.shields.io/crates/v/lp_parser_rs.svg)](https://crates.io/crates/lp_parser_rs)
[![Documentation](https://docs.rs/lp_parser_rs/badge.svg)](https://docs.rs/lp_parser_rs/)
[![PyPI version](https://badge.fury.io/py/parse-lp.svg)](https://badge.fury.io/py/parse-lp)
[![PyPI Downloads](https://static.pepy.tech/personalized-badge/parse-lp?period=total&units=NONE&left_color=BLACK&right_color=GREEN&left_text=downloads)](https://pepy.tech/projects/parse-lp)

A robust Rust library and CLI for parsing, analysing, modifying, and writing Linear Programming (LP) files. Built on [LALRPOP](https://github.com/lalrpop/lalrpop); grammar lives in [`lp.lalrpop`](/rust/src/lp.lalrpop).

Supported specifications: [IBM CPLEX v22.1.1](https://www.ibm.com/docs/en/icos/22.1.1?topic=cplex-lp-file-format-algebraic-representation), [FICO Xpress](https://www.fico.com/fico-xpress-optimization/docs/dms2020-03/solver/optimizer/HTML/chapter10_sec_section102.html), [Gurobi](https://www.gurobi.com/documentation/current/refman/lp_format.html), Mosek.

## Features

- **Parsing & writing** — round-trip LP files (parse → modify → write → parse) with configurable formatting
- **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 comparison between two LP files
- **Serialisation** (`serde` feature) — JSON / YAML support
- **External solvers** (`lp-solvers` feature) — CBC, Gurobi, CPLEX, GLPK via the [lp-solvers]https://crates.io/crates/lp-solvers crate

## Library Usage

Add to `Cargo.toml`:

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

Parse and inspect:

```rust
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:

```rust
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

```bash
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                 |

```bash
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                 |

```bash
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 |

```bash
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
```

<details>
<summary>Example output</summary>

```yaml
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: []
```
</details>

### `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: `                                                    | a-b | ≤ rel_tol · max( | a | , | b | )` |
| `--rename <PATTERN> <REPL>` || Regex rewrite applied to names in both files before matching; repeatable |

```bash
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
```

### `convert` — translate to another format

| Option                  | Default | Description                                 |
| ----------------------- | ------- | ------------------------------------------- |
| `<FILE>`                || Path to the LP file                         |
| `-o, --output <PATH>`   | stdout  | Output file or directory (required for CSV) |
| `-f, --format <FMT>`    | `lp`    | `lp`, `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                          |

```bash
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
```

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

| Option                | Default | Description                      |
| --------------------- | ------- | -------------------------------- |
| `<FILE>`              || Path to the LP file              |
| `-s, --solver <NAME>` | `cbc`   | `cbc`, `gurobi`, `cplex`, `glpk` |
| `-o, --output <PATH>` | stdout  | Write solution to file           |
| `-f, --format <FMT>`  | `text`  | `text`, `json`, `yaml`           |
| `--pretty`            | off     | Pretty-print structured output   |

```bash
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).

## Interactive TUI Diff Viewer (`lp_diff`)

A terminal UI for comparing LP/MPS files with coefficient-level side-by-side diffs, fuzzy search, filtering, and integrated [HiGHS](https://highs.dev) solving. Built with [ratatui](https://ratatui.rs).

```bash
cargo install --path tui
lp_diff base.lp modified.lp            # interactive
lp_diff base.lp modified.lp --summary  # non-interactive 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: `|a-b| ≤ rel_tol · max(|a|,|b|)` |
| `--rename <PATTERN> <REPL>` || Regex rewrite applied to names in both files before matching; repeatable |

```bash
# 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, four sections (Summary / Variables / Constraints / Objectives), filtering (`a`/`+`/`-`/`m`), telescope-style search (fuzzy, `r:` regex, `s:` substring), HiGHS solve-and-compare (`S`), vim-style navigation and jumplist, clipboard yank (`y`/`Y`), CSV export (`w`), `?` for full help.

See [`tui/README.md`](tui/README.md) for the complete reference.

## Development

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

Test data sources: [Jplex](https://github.com/asbestian/jplex/blob/main/instances/afiro.lp), [LPWriter.jl](https://github.com/odow/LPWriter.jl/blob/master/test/model2.lp), [Lp-Parser](https://github.com/aphi/Lp-Parser).

## Contributing

Contributions are welcome — please open a Pull Request.