simplinho 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,18 @@
1
+ # Build artifacts
2
+ *.o
3
+ *.obj
4
+ *.so
5
+ *.a
6
+ *.lib
7
+
8
+ # Build folders
9
+ build/
10
+ dist/
11
+ bin/
12
+ obj/
13
+ build-local/
14
+ build-verify/
15
+
16
+ # IDE files
17
+ .vscode/
18
+ .idea/
@@ -0,0 +1,77 @@
1
+ cmake_minimum_required(VERSION 3.16)
2
+
3
+ project(simplinho VERSION 1.0 LANGUAGES CXX)
4
+
5
+ set(CMAKE_CXX_STANDARD 20)
6
+ set(CMAKE_CXX_STANDARD_REQUIRED ON)
7
+ set(CMAKE_POSITION_INDEPENDENT_CODE ON)
8
+
9
+ include(FetchContent)
10
+
11
+ set(PYBIND11_FINDPYTHON ON CACHE BOOL "Use CMake's FindPython for pybind11" FORCE)
12
+ set(PYBIND11_PYTHON_VERSION 3.13 CACHE STRING
13
+ "Python version to use when building pybind11 modules" FORCE)
14
+ find_package(Python 3.13 EXACT REQUIRED COMPONENTS Interpreter Development)
15
+
16
+ set(SIMPLEX_LOCAL_DEPS_ROOT "")
17
+ foreach(SIMPLEX_DEPS_CANDIDATE
18
+ "${CMAKE_CURRENT_LIST_DIR}/build-local/_deps"
19
+ "${CMAKE_CURRENT_LIST_DIR}/build/_deps"
20
+ "${CMAKE_CURRENT_LIST_DIR}/../build/_deps"
21
+ "${CMAKE_CURRENT_LIST_DIR}/../build-local/_deps")
22
+ if(EXISTS "${SIMPLEX_DEPS_CANDIDATE}")
23
+ set(SIMPLEX_LOCAL_DEPS_ROOT "${SIMPLEX_DEPS_CANDIDATE}")
24
+ break()
25
+ endif()
26
+ endforeach()
27
+
28
+ set(SIMPLEX_LOCAL_EIGEN_DIR
29
+ "${SIMPLEX_LOCAL_DEPS_ROOT}/eigen-src")
30
+ set(SIMPLEX_LOCAL_PYBIND11_DIR
31
+ "${SIMPLEX_LOCAL_DEPS_ROOT}/pybind11-src")
32
+
33
+ if(NOT TARGET Eigen3::Eigen)
34
+ if(EXISTS "${SIMPLEX_LOCAL_EIGEN_DIR}/Eigen/Core")
35
+ add_library(simplex_eigen INTERFACE)
36
+ target_include_directories(simplex_eigen INTERFACE
37
+ "${SIMPLEX_LOCAL_EIGEN_DIR}")
38
+ add_library(Eigen3::Eigen ALIAS simplex_eigen)
39
+ else()
40
+ FetchContent_Declare(
41
+ Eigen
42
+ GIT_REPOSITORY https://gitlab.com/libeigen/eigen.git
43
+ GIT_TAG 3.4.1
44
+ )
45
+ FetchContent_MakeAvailable(Eigen)
46
+ add_library(simplex_eigen INTERFACE)
47
+ target_include_directories(simplex_eigen INTERFACE
48
+ "${eigen_SOURCE_DIR}")
49
+ add_library(Eigen3::Eigen ALIAS simplex_eigen)
50
+ endif()
51
+ endif()
52
+
53
+ if(NOT TARGET pybind11::module)
54
+ if(EXISTS "${SIMPLEX_LOCAL_PYBIND11_DIR}/CMakeLists.txt")
55
+ add_subdirectory(
56
+ "${SIMPLEX_LOCAL_PYBIND11_DIR}"
57
+ "${CMAKE_CURRENT_BINARY_DIR}/pybind11-build"
58
+ )
59
+ else()
60
+ FetchContent_Declare(
61
+ pybind11
62
+ GIT_REPOSITORY https://github.com/pybind/pybind11.git
63
+ GIT_TAG v2.12.0
64
+ )
65
+ FetchContent_MakeAvailable(pybind11)
66
+ endif()
67
+ endif()
68
+
69
+ add_library(simplex_core INTERFACE)
70
+ target_include_directories(simplex_core INTERFACE
71
+ "${CMAKE_CURRENT_SOURCE_DIR}/include")
72
+ target_link_libraries(simplex_core INTERFACE Eigen3::Eigen)
73
+
74
+ pybind11_add_module(simplinho bindings/simplex_bindings.cpp)
75
+ target_link_libraries(simplinho PRIVATE simplex_core pybind11::module)
76
+
77
+ install(TARGETS simplinho DESTINATION .)
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Laio O. Seman. All rights reserved.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,302 @@
1
+ Metadata-Version: 2.2
2
+ Name: simplinho
3
+ Version: 0.1.0
4
+ Summary: Simple revised simplex LP solver with Python bindings
5
+ Keywords: linear programming,simplex,optimization,operations research
6
+ Author-Email: "Laio O. Seman" <laio@ufsc.br>
7
+ License: MIT License
8
+
9
+ Copyright (c) 2026 Laio O. Seman. All rights reserved.
10
+
11
+ Permission is hereby granted, free of charge, to any person obtaining a copy
12
+ of this software and associated documentation files (the "Software"), to deal
13
+ in the Software without restriction, including without limitation the rights
14
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15
+ copies of the Software, and to permit persons to whom the Software is
16
+ furnished to do so, subject to the following conditions:
17
+
18
+ The above copyright notice and this permission notice shall be included in all
19
+ copies or substantial portions of the Software.
20
+
21
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27
+ SOFTWARE.
28
+
29
+ Classifier: Development Status :: 4 - Beta
30
+ Classifier: Intended Audience :: Science/Research
31
+ Classifier: License :: OSI Approved :: MIT License
32
+ Classifier: Programming Language :: Python :: 3
33
+ Classifier: Programming Language :: Python :: 3.10
34
+ Classifier: Programming Language :: Python :: 3.11
35
+ Classifier: Programming Language :: Python :: 3.12
36
+ Classifier: Programming Language :: Python :: 3.13
37
+ Classifier: Programming Language :: C++
38
+ Classifier: Topic :: Scientific/Engineering :: Mathematics
39
+ Project-URL: Repository, https://github.com/lseman/simplinho
40
+ Requires-Python: >=3.10
41
+ Description-Content-Type: text/markdown
42
+
43
+ <p align="center">
44
+ <img src="assets/simplinho-logo.svg" alt="simplinho logo" width="720">
45
+ </p>
46
+
47
+ # simplinho
48
+
49
+ `simplinho` is a standalone revised simplex LP solver with a Python extension module and a small modeling API.
50
+
51
+ 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`.
52
+
53
+ ## Highlights
54
+
55
+ - Primal revised simplex and dual revised simplex in one solver
56
+ - Automatic mode selection with `Auto`, `Primal`, and `Dual` modes
57
+ - Direct Phase II attempts with Phase I fallback when a feasible basis is not available
58
+ - Explicit handling of lower and upper bounds, including automatic reformulation of nonstandard variable bounds
59
+ - Dual bound flipping with Beale-style bound-flip ratio logic
60
+ - Presolve passes for row reduction, scaling, singleton elimination, bound tightening, dual fixing, and early infeasible/unbounded detection
61
+ - Markowitz LU factorization with rook pivoting and iterative refinement
62
+ - Forrest-Tomlin basis updates, with eta-stack updates as an alternative
63
+ - Multi-attempt crash basis construction with Markowitz-threshold triangularization,
64
+ rotating `hybrid`/`sprint`/`crash_ii`/`crash_iii` heuristics, and
65
+ presolved warm-start repair
66
+ - Adaptive pricing, Devex pricing, and most-negative pricing in both primal
67
+ and dual simplex
68
+ - Degeneracy management, anti-cycling support, and Harris-style ratio tests
69
+ - Rich solve outputs: basis data, reduced costs, dual values, shadow prices, internal tableau data, traces, and Farkas certificates
70
+ - A higher-level Python modeling layer with algebraic expressions, named variables, named constraints, and post-solve dual access
71
+
72
+ ## What The Project Exposes
73
+
74
+ There are two main ways to use the solver:
75
+
76
+ 1. `simplinho.RevisedSimplex`
77
+ Use the low-level matrix API directly:
78
+ `solve(A, b, c, l, u)` for `min c^T x` subject to `Ax = b` and `l <= x <= u`.
79
+
80
+ 2. `simplinho.Model`
81
+ Use the modeling API with variables, expressions, constraints, and `minimize(...)` / `maximize(...)`.
82
+
83
+ The Python module also exposes:
84
+
85
+ - `RevisedSimplexOptions`
86
+ - `SimplexMode`
87
+ - `LPStatus`
88
+ - `LPBasis`
89
+ - `LPBasisStatus`
90
+ - `status_to_string(...)`
91
+
92
+ ## Solver Features
93
+
94
+ ### Algorithms
95
+
96
+ - Revised simplex implementation with both primal and dual pivoting
97
+ - Automatic fallback behavior when a direct solve needs Phase I work
98
+ - Support for bounded variables, free variables, shifted variables, and internally added slacks
99
+ - Bound-flip logic for dual iterations when enabled through `dual_allow_bound_flip`
100
+
101
+ ### Numerics
102
+
103
+ - Dense Markowitz LU factorization
104
+ - Rook pivoting for more robust pivot selection
105
+ - Iterative refinement in both forward and transpose solves
106
+ - Configurable pivot tolerances, refactor frequency, and compression thresholds
107
+ - Forrest-Tomlin updates by default, or eta-stack updates via `basis_update = "eta"`
108
+
109
+ ### Pricing And Stability
110
+
111
+ - `pricing_rule = "adaptive"` for steepest-edge on smaller dual bases and
112
+ Devex on larger ones, with periodic weight resets
113
+ - `pricing_rule = "devex"` for Devex pricing
114
+ - `pricing_rule = "most_negative"` for a simple reduced-cost rule in primal and
115
+ most-infeasible-row pricing in dual
116
+ - `crash_attempts`, `crash_markowitz_tol`, `crash_strategy`, and
117
+ `repair_mapped_basis` tune the initial basis search and post-presolve basis
118
+ repair
119
+ - Degeneracy tracking and anti-cycling support
120
+ - Harris-style primal and dual ratio tests
121
+ - Optional Bland rule toggle
122
+
123
+ ### Presolve And Diagnostics
124
+
125
+ - Presolve with row/column simplifications, scaling, singleton eliminations, and bound tightening
126
+ - Early `infeasible` and `unbounded` detection in presolve when possible
127
+ - Verbose tracing with optional basis and presolve details
128
+ - Final internal tableau, reduced costs, dual values, and shadow prices on the solved internal model
129
+ - Farkas certificate output when infeasibility is proven through the dual path
130
+
131
+ ## Build
132
+
133
+ The CMake project currently builds a Python extension named `simplinho`.
134
+
135
+ ### Requirements
136
+
137
+ - CMake 3.16+
138
+ - A C++20 compiler
139
+ - Python 3.13 development headers
140
+
141
+ If local copies of Eigen or `pybind11` are not present in a nearby `_deps` directory, CMake will fetch them with `FetchContent`.
142
+
143
+ ### Build Commands
144
+
145
+ ```bash
146
+ cmake -S . -B build-local
147
+ cmake --build build-local -j
148
+ ```
149
+
150
+ That produces a shared object like `build-local/simplinho.cpython-313-...so`.
151
+
152
+ ## Low-Level Example
153
+
154
+ ```python
155
+ import sys
156
+ from pathlib import Path
157
+
158
+ import numpy as np
159
+
160
+ sys.path.insert(0, str(Path("build-local").resolve()))
161
+
162
+ import simplinho as simplex
163
+
164
+ A = np.array([
165
+ [1.0, 1.0],
166
+ ])
167
+ b = np.array([4.0])
168
+ c = np.array([1.0, 2.0])
169
+ l = np.array([0.0, 0.0])
170
+ u = np.array([np.inf, np.inf])
171
+
172
+ options = simplex.RevisedSimplexOptions()
173
+ options.mode = simplex.SimplexMode.Auto
174
+ options.pricing_rule = "adaptive"
175
+
176
+ solver = simplex.RevisedSimplex(options)
177
+ solution = solver.solve(A, b, c, l, u)
178
+
179
+ print(simplex.status_to_string(solution.status))
180
+ print("objective:", solution.obj)
181
+ print("x:", solution.x)
182
+ print("iterations:", solution.iters)
183
+ print("stats:", solution.stats.as_dict())
184
+ print("log:", solution.log)
185
+ print("basis:", solution.basis_state)
186
+ print("dual values:", solution.dual_values_internal)
187
+ print("reduced costs:", solution.reduced_costs_internal)
188
+ ```
189
+
190
+ You can also reuse that basis as a warm start after bound changes:
191
+
192
+ ```python
193
+ basis = solution.basis_state
194
+
195
+ u2 = np.array([1.5, np.inf])
196
+ warm = solver.solve(A, b, c, l, u2, basis=basis)
197
+ print(warm.stats.basis_start)
198
+ ```
199
+
200
+ If you keep the same `RevisedSimplex` instance in `Dual` mode, it will also
201
+ automatically reuse the last valid basis on later `solve(...)` calls with the
202
+ same row/column dimensions. That makes repeated bound-fixing re-solves behave
203
+ more like a branch-and-bound node LP loop:
204
+
205
+ ```python
206
+ options.mode = simplex.SimplexMode.Dual
207
+ solver = simplex.RevisedSimplex(options)
208
+
209
+ root = solver.solve(A, b, c, l, u)
210
+ u_node = np.array([1.5, np.inf])
211
+ node = solver.solve(A, b, c, l, u_node)
212
+ print(node.stats.basis_start)
213
+ ```
214
+
215
+ ## Modeling API Example
216
+
217
+ ```python
218
+ import sys
219
+ from pathlib import Path
220
+
221
+ sys.path.insert(0, str(Path("build-local").resolve()))
222
+
223
+ import simplinho as simplex
224
+
225
+ model = simplex.Model()
226
+
227
+ x = model.addVar("x", lb=0.0)
228
+ y = model.addVar("y", lb=0.0)
229
+
230
+ c1 = model.addConstr(x <= 3, name="cap_x")
231
+ c2 = model.addConstr(y <= 2, name="cap_y")
232
+ c3 = model.addConstr(x + 2 * y <= 6, name="mix_cap")
233
+
234
+ model.maximize(x + y)
235
+ solution = model.solve()
236
+
237
+ print("status :", simplex.status_to_string(solution.status))
238
+ print("objective:", solution.obj)
239
+ print("x :", solution.value(x))
240
+ print("y :", solution.value("y"))
241
+ print("all vars :", solution.values)
242
+ print("stats :", solution.stats.as_dict())
243
+ print("basis :", solution.basis)
244
+ print("log :", solution.log)
245
+ print("dual c1 :", c1.pi)
246
+ print("dual c2 :", c2.pi)
247
+ print("dual c3 :", c3.pi)
248
+ ```
249
+
250
+ The modeling layer also supports live edits after construction:
251
+
252
+ ```python
253
+ x.obj = 3.0
254
+ c3.rhs = 5.0
255
+ c3.set_coeff(y, 2.0)
256
+
257
+ solution = model.reoptimize()
258
+
259
+ model.deleteConstr(c1)
260
+ model.deleteVar(x)
261
+ solution = model.reoptimize()
262
+ ```
263
+
264
+ When the model structure stays the same, `reoptimize()` will automatically try
265
+ to reuse the last valid basis after edits like bound changes, RHS updates,
266
+ coefficient changes, or objective changes. You can also pass an explicit saved
267
+ basis for fast dual re-solves:
268
+
269
+ ```python
270
+ basis = solution.basis
271
+ x.ub = 1.5
272
+ model.options.mode = simplex.SimplexMode.Dual
273
+ solution = model.reoptimize(basis)
274
+ ```
275
+
276
+ ## Useful Outputs
277
+
278
+ The low-level `LPSolution` object includes more than just the primal vector:
279
+
280
+ - `status`, `obj`, `x`, `iters`
281
+ - `stats` with typed solve telemetry and `as_dict()`
282
+ - `basis_state` / `basis` for reusable warm starts
283
+ - `log_lines` and `log` for verbose solver traces
284
+ - `basis`, `basis_internal`, `nonbasis_internal`
285
+ - `tableau`, `tableau_rhs`, `has_internal_tableau`
286
+ - `reduced_costs_internal`
287
+ - `dual_values_internal`
288
+ - `shadow_prices_internal`
289
+ - `trace`
290
+ - `info`
291
+ - `farkas_y`, `farkas_y_internal`, `farkas_has_cert`
292
+ - `primal_ray`, `primal_ray_internal`, `primal_ray_has_cert`
293
+
294
+ The modeling layer wraps that in `ModelSolution`, while still exposing the raw solve result as `solution.raw`.
295
+
296
+ ## Repository Layout
297
+
298
+ - `include/simplex/`: header-only solver core, presolve, LU, pricing, and degeneracy helpers
299
+ - `bindings/`: `pybind11` bindings and modeling API
300
+ - `src/`: reserved for future non-header sources
301
+ - `simplex_modeling_api_demo.ipynb`: notebook showing the modeling API in action
302
+ - `CMakeLists.txt`: standalone build for the Python module
@@ -0,0 +1,260 @@
1
+ <p align="center">
2
+ <img src="assets/simplinho-logo.svg" alt="simplinho logo" width="720">
3
+ </p>
4
+
5
+ # simplinho
6
+
7
+ `simplinho` is a standalone revised simplex LP solver with a Python extension module and a small modeling API.
8
+
9
+ 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`.
10
+
11
+ ## Highlights
12
+
13
+ - Primal revised simplex and dual revised simplex in one solver
14
+ - Automatic mode selection with `Auto`, `Primal`, and `Dual` modes
15
+ - Direct Phase II attempts with Phase I fallback when a feasible basis is not available
16
+ - Explicit handling of lower and upper bounds, including automatic reformulation of nonstandard variable bounds
17
+ - Dual bound flipping with Beale-style bound-flip ratio logic
18
+ - Presolve passes for row reduction, scaling, singleton elimination, bound tightening, dual fixing, and early infeasible/unbounded detection
19
+ - Markowitz LU factorization with rook pivoting and iterative refinement
20
+ - Forrest-Tomlin basis updates, with eta-stack updates as an alternative
21
+ - Multi-attempt crash basis construction with Markowitz-threshold triangularization,
22
+ rotating `hybrid`/`sprint`/`crash_ii`/`crash_iii` heuristics, and
23
+ presolved warm-start repair
24
+ - Adaptive pricing, Devex pricing, and most-negative pricing in both primal
25
+ and dual simplex
26
+ - Degeneracy management, anti-cycling support, and Harris-style ratio tests
27
+ - Rich solve outputs: basis data, reduced costs, dual values, shadow prices, internal tableau data, traces, and Farkas certificates
28
+ - A higher-level Python modeling layer with algebraic expressions, named variables, named constraints, and post-solve dual access
29
+
30
+ ## What The Project Exposes
31
+
32
+ There are two main ways to use the solver:
33
+
34
+ 1. `simplinho.RevisedSimplex`
35
+ Use the low-level matrix API directly:
36
+ `solve(A, b, c, l, u)` for `min c^T x` subject to `Ax = b` and `l <= x <= u`.
37
+
38
+ 2. `simplinho.Model`
39
+ Use the modeling API with variables, expressions, constraints, and `minimize(...)` / `maximize(...)`.
40
+
41
+ The Python module also exposes:
42
+
43
+ - `RevisedSimplexOptions`
44
+ - `SimplexMode`
45
+ - `LPStatus`
46
+ - `LPBasis`
47
+ - `LPBasisStatus`
48
+ - `status_to_string(...)`
49
+
50
+ ## Solver Features
51
+
52
+ ### Algorithms
53
+
54
+ - Revised simplex implementation with both primal and dual pivoting
55
+ - Automatic fallback behavior when a direct solve needs Phase I work
56
+ - Support for bounded variables, free variables, shifted variables, and internally added slacks
57
+ - Bound-flip logic for dual iterations when enabled through `dual_allow_bound_flip`
58
+
59
+ ### Numerics
60
+
61
+ - Dense Markowitz LU factorization
62
+ - Rook pivoting for more robust pivot selection
63
+ - Iterative refinement in both forward and transpose solves
64
+ - Configurable pivot tolerances, refactor frequency, and compression thresholds
65
+ - Forrest-Tomlin updates by default, or eta-stack updates via `basis_update = "eta"`
66
+
67
+ ### Pricing And Stability
68
+
69
+ - `pricing_rule = "adaptive"` for steepest-edge on smaller dual bases and
70
+ Devex on larger ones, with periodic weight resets
71
+ - `pricing_rule = "devex"` for Devex pricing
72
+ - `pricing_rule = "most_negative"` for a simple reduced-cost rule in primal and
73
+ most-infeasible-row pricing in dual
74
+ - `crash_attempts`, `crash_markowitz_tol`, `crash_strategy`, and
75
+ `repair_mapped_basis` tune the initial basis search and post-presolve basis
76
+ repair
77
+ - Degeneracy tracking and anti-cycling support
78
+ - Harris-style primal and dual ratio tests
79
+ - Optional Bland rule toggle
80
+
81
+ ### Presolve And Diagnostics
82
+
83
+ - Presolve with row/column simplifications, scaling, singleton eliminations, and bound tightening
84
+ - Early `infeasible` and `unbounded` detection in presolve when possible
85
+ - Verbose tracing with optional basis and presolve details
86
+ - Final internal tableau, reduced costs, dual values, and shadow prices on the solved internal model
87
+ - Farkas certificate output when infeasibility is proven through the dual path
88
+
89
+ ## Build
90
+
91
+ The CMake project currently builds a Python extension named `simplinho`.
92
+
93
+ ### Requirements
94
+
95
+ - CMake 3.16+
96
+ - A C++20 compiler
97
+ - Python 3.13 development headers
98
+
99
+ If local copies of Eigen or `pybind11` are not present in a nearby `_deps` directory, CMake will fetch them with `FetchContent`.
100
+
101
+ ### Build Commands
102
+
103
+ ```bash
104
+ cmake -S . -B build-local
105
+ cmake --build build-local -j
106
+ ```
107
+
108
+ That produces a shared object like `build-local/simplinho.cpython-313-...so`.
109
+
110
+ ## Low-Level Example
111
+
112
+ ```python
113
+ import sys
114
+ from pathlib import Path
115
+
116
+ import numpy as np
117
+
118
+ sys.path.insert(0, str(Path("build-local").resolve()))
119
+
120
+ import simplinho as simplex
121
+
122
+ A = np.array([
123
+ [1.0, 1.0],
124
+ ])
125
+ b = np.array([4.0])
126
+ c = np.array([1.0, 2.0])
127
+ l = np.array([0.0, 0.0])
128
+ u = np.array([np.inf, np.inf])
129
+
130
+ options = simplex.RevisedSimplexOptions()
131
+ options.mode = simplex.SimplexMode.Auto
132
+ options.pricing_rule = "adaptive"
133
+
134
+ solver = simplex.RevisedSimplex(options)
135
+ solution = solver.solve(A, b, c, l, u)
136
+
137
+ print(simplex.status_to_string(solution.status))
138
+ print("objective:", solution.obj)
139
+ print("x:", solution.x)
140
+ print("iterations:", solution.iters)
141
+ print("stats:", solution.stats.as_dict())
142
+ print("log:", solution.log)
143
+ print("basis:", solution.basis_state)
144
+ print("dual values:", solution.dual_values_internal)
145
+ print("reduced costs:", solution.reduced_costs_internal)
146
+ ```
147
+
148
+ You can also reuse that basis as a warm start after bound changes:
149
+
150
+ ```python
151
+ basis = solution.basis_state
152
+
153
+ u2 = np.array([1.5, np.inf])
154
+ warm = solver.solve(A, b, c, l, u2, basis=basis)
155
+ print(warm.stats.basis_start)
156
+ ```
157
+
158
+ If you keep the same `RevisedSimplex` instance in `Dual` mode, it will also
159
+ automatically reuse the last valid basis on later `solve(...)` calls with the
160
+ same row/column dimensions. That makes repeated bound-fixing re-solves behave
161
+ more like a branch-and-bound node LP loop:
162
+
163
+ ```python
164
+ options.mode = simplex.SimplexMode.Dual
165
+ solver = simplex.RevisedSimplex(options)
166
+
167
+ root = solver.solve(A, b, c, l, u)
168
+ u_node = np.array([1.5, np.inf])
169
+ node = solver.solve(A, b, c, l, u_node)
170
+ print(node.stats.basis_start)
171
+ ```
172
+
173
+ ## Modeling API Example
174
+
175
+ ```python
176
+ import sys
177
+ from pathlib import Path
178
+
179
+ sys.path.insert(0, str(Path("build-local").resolve()))
180
+
181
+ import simplinho as simplex
182
+
183
+ model = simplex.Model()
184
+
185
+ x = model.addVar("x", lb=0.0)
186
+ y = model.addVar("y", lb=0.0)
187
+
188
+ c1 = model.addConstr(x <= 3, name="cap_x")
189
+ c2 = model.addConstr(y <= 2, name="cap_y")
190
+ c3 = model.addConstr(x + 2 * y <= 6, name="mix_cap")
191
+
192
+ model.maximize(x + y)
193
+ solution = model.solve()
194
+
195
+ print("status :", simplex.status_to_string(solution.status))
196
+ print("objective:", solution.obj)
197
+ print("x :", solution.value(x))
198
+ print("y :", solution.value("y"))
199
+ print("all vars :", solution.values)
200
+ print("stats :", solution.stats.as_dict())
201
+ print("basis :", solution.basis)
202
+ print("log :", solution.log)
203
+ print("dual c1 :", c1.pi)
204
+ print("dual c2 :", c2.pi)
205
+ print("dual c3 :", c3.pi)
206
+ ```
207
+
208
+ The modeling layer also supports live edits after construction:
209
+
210
+ ```python
211
+ x.obj = 3.0
212
+ c3.rhs = 5.0
213
+ c3.set_coeff(y, 2.0)
214
+
215
+ solution = model.reoptimize()
216
+
217
+ model.deleteConstr(c1)
218
+ model.deleteVar(x)
219
+ solution = model.reoptimize()
220
+ ```
221
+
222
+ When the model structure stays the same, `reoptimize()` will automatically try
223
+ to reuse the last valid basis after edits like bound changes, RHS updates,
224
+ coefficient changes, or objective changes. You can also pass an explicit saved
225
+ basis for fast dual re-solves:
226
+
227
+ ```python
228
+ basis = solution.basis
229
+ x.ub = 1.5
230
+ model.options.mode = simplex.SimplexMode.Dual
231
+ solution = model.reoptimize(basis)
232
+ ```
233
+
234
+ ## Useful Outputs
235
+
236
+ The low-level `LPSolution` object includes more than just the primal vector:
237
+
238
+ - `status`, `obj`, `x`, `iters`
239
+ - `stats` with typed solve telemetry and `as_dict()`
240
+ - `basis_state` / `basis` for reusable warm starts
241
+ - `log_lines` and `log` for verbose solver traces
242
+ - `basis`, `basis_internal`, `nonbasis_internal`
243
+ - `tableau`, `tableau_rhs`, `has_internal_tableau`
244
+ - `reduced_costs_internal`
245
+ - `dual_values_internal`
246
+ - `shadow_prices_internal`
247
+ - `trace`
248
+ - `info`
249
+ - `farkas_y`, `farkas_y_internal`, `farkas_has_cert`
250
+ - `primal_ray`, `primal_ray_internal`, `primal_ray_has_cert`
251
+
252
+ The modeling layer wraps that in `ModelSolution`, while still exposing the raw solve result as `solution.raw`.
253
+
254
+ ## Repository Layout
255
+
256
+ - `include/simplex/`: header-only solver core, presolve, LU, pricing, and degeneracy helpers
257
+ - `bindings/`: `pybind11` bindings and modeling API
258
+ - `src/`: reserved for future non-header sources
259
+ - `simplex_modeling_api_demo.ipynb`: notebook showing the modeling API in action
260
+ - `CMakeLists.txt`: standalone build for the Python module