Simple revised simplex LP solver with Python bindings
Project description
simplinho
simplinho is a standalone revised simplex LP solver with a Python extension module and a small modeling API.
The core solver is header-only C++ in include/simplex/, the Python bindings live in bindings/, and the top-level build produces a simplinho module via pybind11.
Highlights
- Primal revised simplex and dual revised simplex in one solver
- Automatic mode selection with
Auto,Primal, andDualmodes - Direct Phase II attempts with Phase I fallback when a feasible basis is not available
- Explicit handling of lower and upper bounds, including automatic reformulation of nonstandard variable bounds
- Dual bound flipping with Beale-style bound-flip ratio logic
- Presolve passes for row reduction, scaling, singleton elimination, bound tightening, dual fixing, and early infeasible/unbounded detection
- Markowitz LU factorization with rook pivoting and iterative refinement
- Forrest-Tomlin basis updates, with eta-stack updates as an alternative
- Multi-attempt crash basis construction with Markowitz-threshold triangularization,
rotating
hybrid/sprint/crash_ii/crash_iiiheuristics, and presolved warm-start repair - Adaptive pricing, Devex pricing, and most-negative pricing in both primal and dual simplex
- Degeneracy management, anti-cycling support, and Harris-style ratio tests
- Rich solve outputs: basis data, reduced costs, dual values, shadow prices, internal tableau data, traces, and Farkas certificates
- A higher-level Python modeling layer with algebraic expressions, named variables, named constraints, and post-solve dual access
What The Project Exposes
There are two main ways to use the solver:
-
simplinho.RevisedSimplexUse the low-level matrix API directly:solve(A, b, c, l, u)formin c^T xsubject toAx = bandl <= x <= u. -
simplinho.ModelUse the modeling API with variables, expressions, constraints, andminimize(...)/maximize(...).
The Python module also exposes:
RevisedSimplexOptionsSimplexModeLPStatusLPBasisLPBasisStatusstatus_to_string(...)
Solver Features
Algorithms
- Revised simplex implementation with both primal and dual pivoting
- Automatic fallback behavior when a direct solve needs Phase I work
- Support for bounded variables, free variables, shifted variables, and internally added slacks
- Bound-flip logic for dual iterations when enabled through
dual_allow_bound_flip
Numerics
- Dense Markowitz LU factorization
- Rook pivoting for more robust pivot selection
- Iterative refinement in both forward and transpose solves
- Configurable pivot tolerances, refactor frequency, and compression thresholds
- Forrest-Tomlin updates by default, or eta-stack updates via
basis_update = "eta"
Pricing And Stability
pricing_rule = "adaptive"for steepest-edge on smaller dual bases and Devex on larger ones, with periodic weight resetspricing_rule = "devex"for Devex pricingpricing_rule = "most_negative"for a simple reduced-cost rule in primal and most-infeasible-row pricing in dualcrash_attempts,crash_markowitz_tol,crash_strategy, andrepair_mapped_basistune the initial basis search and post-presolve basis repair- Degeneracy tracking and anti-cycling support
- Harris-style primal and dual ratio tests
- Optional Bland rule toggle
Presolve And Diagnostics
- Presolve with row/column simplifications, scaling, singleton eliminations, and bound tightening
- Early
infeasibleandunboundeddetection in presolve when possible - Verbose tracing with optional basis and presolve details
- Final internal tableau, reduced costs, dual values, and shadow prices on the solved internal model
- Farkas certificate output when infeasibility is proven through the dual path
Build
The CMake project currently builds a Python extension named simplinho.
Requirements
- CMake 3.16+
- A C++20 compiler
- Python 3.13 development headers
If local copies of Eigen or pybind11 are not present in a nearby _deps directory, CMake will fetch them with FetchContent.
Build Commands
cmake -S . -B build-local
cmake --build build-local -j
That produces a shared object like build-local/simplinho.cpython-313-...so.
Low-Level Example
import sys
from pathlib import Path
import numpy as np
sys.path.insert(0, str(Path("build-local").resolve()))
import simplinho as simplex
A = np.array([
[1.0, 1.0],
])
b = np.array([4.0])
c = np.array([1.0, 2.0])
l = np.array([0.0, 0.0])
u = np.array([np.inf, np.inf])
options = simplex.RevisedSimplexOptions()
options.mode = simplex.SimplexMode.Auto
options.pricing_rule = "adaptive"
solver = simplex.RevisedSimplex(options)
solution = solver.solve(A, b, c, l, u)
print(simplex.status_to_string(solution.status))
print("objective:", solution.obj)
print("x:", solution.x)
print("iterations:", solution.iters)
print("stats:", solution.stats.as_dict())
print("log:", solution.log)
print("basis:", solution.basis_state)
print("dual values:", solution.dual_values_internal)
print("reduced costs:", solution.reduced_costs_internal)
You can also reuse that basis as a warm start after bound changes:
basis = solution.basis_state
u2 = np.array([1.5, np.inf])
warm = solver.solve(A, b, c, l, u2, basis=basis)
print(warm.stats.basis_start)
If you keep the same RevisedSimplex instance in Dual mode, it will also
automatically reuse the last valid basis on later solve(...) calls with the
same row/column dimensions. That makes repeated bound-fixing re-solves behave
more like a branch-and-bound node LP loop:
options.mode = simplex.SimplexMode.Dual
solver = simplex.RevisedSimplex(options)
root = solver.solve(A, b, c, l, u)
u_node = np.array([1.5, np.inf])
node = solver.solve(A, b, c, l, u_node)
print(node.stats.basis_start)
Modeling API Example
import sys
from pathlib import Path
sys.path.insert(0, str(Path("build-local").resolve()))
import simplinho as simplex
model = simplex.Model()
x = model.addVar("x", lb=0.0)
y = model.addVar("y", lb=0.0)
c1 = model.addConstr(x <= 3, name="cap_x")
c2 = model.addConstr(y <= 2, name="cap_y")
c3 = model.addConstr(x + 2 * y <= 6, name="mix_cap")
model.maximize(x + y)
solution = model.solve()
print("status :", simplex.status_to_string(solution.status))
print("objective:", solution.obj)
print("x :", solution.value(x))
print("y :", solution.value("y"))
print("all vars :", solution.values)
print("stats :", solution.stats.as_dict())
print("basis :", solution.basis)
print("log :", solution.log)
print("dual c1 :", c1.pi)
print("dual c2 :", c2.pi)
print("dual c3 :", c3.pi)
The modeling layer also supports live edits after construction:
x.obj = 3.0
c3.rhs = 5.0
c3.set_coeff(y, 2.0)
solution = model.reoptimize()
model.deleteConstr(c1)
model.deleteVar(x)
solution = model.reoptimize()
When the model structure stays the same, reoptimize() will automatically try
to reuse the last valid basis after edits like bound changes, RHS updates,
coefficient changes, or objective changes. You can also pass an explicit saved
basis for fast dual re-solves:
basis = solution.basis
x.ub = 1.5
model.options.mode = simplex.SimplexMode.Dual
solution = model.reoptimize(basis)
Useful Outputs
The low-level LPSolution object includes more than just the primal vector:
status,obj,x,itersstatswith typed solve telemetry andas_dict()basis_state/basisfor reusable warm startslog_linesandlogfor verbose solver tracesbasis,basis_internal,nonbasis_internaltableau,tableau_rhs,has_internal_tableaureduced_costs_internaldual_values_internalshadow_prices_internaltraceinfofarkas_y,farkas_y_internal,farkas_has_certprimal_ray,primal_ray_internal,primal_ray_has_cert
The modeling layer wraps that in ModelSolution, while still exposing the raw solve result as solution.raw.
Repository Layout
include/simplex/: header-only solver core, presolve, LU, pricing, and degeneracy helpersbindings/:pybind11bindings and modeling APIsrc/: reserved for future non-header sourcessimplex_modeling_api_demo.ipynb: notebook showing the modeling API in actionCMakeLists.txt: standalone build for the Python module
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file simplinho-0.1.0.tar.gz.
File metadata
- Download URL: simplinho-0.1.0.tar.gz
- Upload date:
- Size: 92.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d2980b079aa315d90d4c600c653c7229e5c77f703d5f2bf6622d370b01a100fe
|
|
| MD5 |
4153c34e9c72bf91aa4d84a37cc9c292
|
|
| BLAKE2b-256 |
ac783f7e66b66ac34a0189da1ea40764bbaed11120343d6ffed232e88b8ba8df
|
File details
Details for the file simplinho-0.1.0-cp313-cp313-manylinux_2_39_x86_64.whl.
File metadata
- Download URL: simplinho-0.1.0-cp313-cp313-manylinux_2_39_x86_64.whl
- Upload date:
- Size: 693.4 kB
- Tags: CPython 3.13, manylinux: glibc 2.39+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9c8ab4b13e79d6e4a356971cf639469c2f44b5ec8f3d93327dd1cd54021b520e
|
|
| MD5 |
22089f24e31519c1a4068cd09bf61738
|
|
| BLAKE2b-256 |
5f1f4eb5363a35e5c602f633ee897e4f8080dc637183b85de80eb9315babe7e8
|