Metadata-Version: 2.4
Name: einheit
Version: 0.4.0
Requires-Dist: pulp>=2.7 ; extra == 'pulp'
Requires-Dist: pyomo>=6.8 ; extra == 'pyomo'
Provides-Extra: pulp
Provides-Extra: pyomo
Summary: Einheit — a racing LP/MIP solver. Python bindings over the Rust core (PyO3).
Requires-Python: >=3.11
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM

# einheit (Python)

Python bindings for [Einheit](https://github.com/einheit-solver/einheit), a racing LP/MIP
solver. A thin [PyO3](https://pyo3.rs) wrapper over the Rust core — the solver runs
native; Python just drives it.

```python
import einheit

lp = einheit.parse_mps(open("afiro.mps").read())
sol = einheit.solve(lp)
if sol.is_optimal:
    print(sol.objective)   # -464.7531428571
    print(sol.x)           # primal solution, length lp.num_cols
```

## API

### Building a model

Three ways to get an `Lp`:

```python
import einheit, math

# 1. MPS text
lp = einheit.parse_mps(open("model.mps").read())

# 2. CPLEX / Gurobi LP-format text
lp = einheit.parse_lp(open("model.lp").read())

# 3. Programmatic builder — no file needed
b = einheit.LpBuilder("demo")
b.sense("maximize")                              # default is "minimize"
x = b.add_var("x", 3.0, 0.0, math.inf)          # returns VarRef
y = b.add_var("y", 2.0, 0.0, math.inf)
b.add_constraint("c1", [(x, 1.0), (y, 1.0)], -math.inf, 4.0)
b.add_constraint("c2", [(x, 1.0), (y, 3.0)], -math.inf, 6.0)
lp = b.build()                                   # raises ValueError on bad bounds / refs
```

### Reference

- `parse_mps(text: str) -> Lp` — parse an MPS model (raises `ValueError` on a bad model).
- `parse_lp(text: str) -> Lp` — parse a CPLEX / Gurobi LP-format model (raises `ValueError`
  on a bad model). Produces the same `Lp` as `parse_mps` — the two readers are interchangeable.
- `LpBuilder(name: str = "") -> LpBuilder` — programmatic model builder.
  - `.sense(sense: str)` — `"minimize"` (default) or `"maximize"`.
  - `.objective_offset(offset: float)` — constant term added to the objective.
  - `.add_var(name, obj, lower, upper) -> VarRef` — add a variable; returns an opaque handle.
  - `.add_constraint(name, coeffs, lower, upper)` — add a ranged row; `coeffs` is a list of
    `(VarRef, float)` pairs.
  - `.build() -> Lp` — validate and pack; raises `ValueError` on bad bounds or foreign refs.
- `VarRef` — opaque column handle; `.index` gives the column number.
- `Lp` — `num_rows`, `num_cols`, `nnz`, `name`, `col_names`, `row_names` (all read-only).
- `solve(lp, *, max_iters=None, driver=None, presolve=None, edge_weight=None) -> Solution`
  — solve to optimum / infeasible / unbounded. Releases the GIL while the simplex runs.
  All keyword args default to `None` (Rust `Options::default()`); pass explicit values to
  override: `driver` (`"primal"` / `"dual"`), `presolve` (`True` / `False`), `edge_weight`
  (`"dantzig"` / `"steepest"`). Raises `ValueError` on unknown strings.
- `Solution` — `status` (`"optimal"` | `"infeasible"` | `"unbounded"` | `"iteration_limit"`),
  `is_optimal`, `objective`, `x`, `iterations`.

## Install from a GitHub Release

Pre-built wheels (Linux x86_64, macOS arm64, Windows x86_64) attach to every
[GitHub Release](https://github.com/einheit-solver/einheit/releases). No Rust toolchain required —
the compiled core is bundled inside the wheel.

```sh
# Replace <tag> with the release tag, e.g. v0.1.0.
pip install einheit \
  --find-links https://github.com/einheit-solver/einheit/releases/expanded_assets/<tag>
```

## Build from source

Built with [maturin](https://www.maturin.rs) (needs a Rust toolchain):

```sh
cd crates/einheit-py
uv run maturin develop      # build + install into the uv env (editable)
uv run pytest               # run the tests
```

Or from the repo root:

```sh
just python-build   # editable install into the uv dev env
just python-test    # build + run pytest
just python-wheel   # build a release wheel into crates/einheit-py/target/wheels/
```

