primalsolver
Python bindings for PrimalSolver — a dependency-free convex optimization solver in C99 (LP / QP / SOCP / SDP / exp-power / MIP).
The package ships a pre-built shared library (libprimal.dylib / .so / .dll)
inside the wheel and calls it through ctypes: no C toolchain is needed to
install, and the whole public API is exposed.
Upstream C library: https://github.com/c-vision/Primal. This package treats its sources as read-only input and never modifies them.
from primalsolver import Model, BK, SOLSTA
with Model(maxcon=2, maxvar=2) as m:
m.obj([0, 1], [-3.0, -2.0]) # min -3 x0 - 2 x1
m.a_ij(0, 0, 1.0); m.a_ij(0, 1, 1.0) # x0 + x1 <= 4
m.a_ij(1, 0, 1.0); m.a_ij(1, 1, 3.0) # x0 + 3x1 <= 6
m.con_bounds(0, BK.UP, up=4.0)
m.con_bounds(1, BK.UP, up=6.0)
m.var_bounds(0, BK.RA, 0.0, 3.0) # 0 <= x0 <= 3
m.var_bounds(1, BK.RA, 0.0, 3.0)
r = m.solve()
assert r.solsta == SOLSTA.OPTIMAL
print(r.x, r.objective) # [3.0, 1.0] -11.0
Requirements
- Python >= 3.8, no runtime dependencies other than the standard library.
- A platform wheel is provided per OS/arch (macOS, Linux, Windows). On a platform without a wheel you can build from source (see below).
Install
pip install primalsolver
Verify:
python -c "import primalsolver as ps; print(ps.version())"
Quickstart
Build and solve a model in code (above), or solve a model file — MPS, CPLEX LP, OPF and CBF are auto-detected by content/extension:
from primalsolver import Model, SOLSTA
with Model() as m:
m.read("model.mps")
r = m.solve()
print(r.solsta, r.x, r.objective)
The high-level Model covers the common path. Everything else is reachable
through the generated low-level binding.
Full API (all 547 functions)
tools/gen_bindings.py parses the C header primal.h and emits
src/primalsolver/_bindings.py with a ctypes signature for every PRIMAL_*
function. _native.py applies them and re-exports them, so the entire surface is
available:
import primalsolver as ps
ps.PRIMAL_getdualray(...) # every PRIMAL_* is available at package level
ps.FUNCTIONS # name -> ctypes function object (547)
ps.MISSING # declared in the header but absent in the lib (empty = OK)
ps._native.optimize # short aliases (PRIMAL_ removed) used by Model
Handles (PRIMALenv_t / PRIMALtask_t) are opaque C pointers: the functions
that create/destroy them take a pointer to the handle, so pass
ctypes.byref(...). Callback arguments are exposed as c_void_p; the two
variadic functions (PRIMAL_echotask, PRIMAL_echoenv) are exposed without
argtypes (ctypes cannot type C varargs) — pass explicit ctypes objects for the
extra arguments.
Regenerate the binding after any change to primal.h:
python tools/gen_bindings.py
What is shipped
- Wheel: only the
primalsolverpackage —__init__.py,_native.py, the generated_bindings.py, the compiled library in_lib/, plusREADME.mdandLICENSE. - Python examples live in
../python_examples/(tracked in the C repository), not in this package. No test files are shipped.
Building from source
The C sources are read-only input; this project never modifies them. setup.py
compiles the shared library into src/primalsolver/_lib/ before packaging.
Source directory resolution, in order:
$PRIMAL_C_SRC— an explicit C checkout;csrc/— a vendored snapshot (python sync_csrc.py), used by the sdist/CI;..— the parent directory, i.e. the live C repository (default).
csrc/ is generated by sync_csrc.py and is gitignored: the C sources have
a single home in the C repository. CI fetches them from
github.com/c-vision/Primal and regenerates csrc/ before building.
Development install:
python -m venv .venv && . .venv/bin/activate
pip install -U pip setuptools wheel
pip install -e .
python -c "import primalsolver as ps; print(ps.version())"
Build a wheel locally (needs only setuptools + wheel, no build package):
python -m pip wheel . --no-build-isolation --no-deps -w dist
The wheel bundles a native library, so it is platform-specific:
py3-none-<platform>(e.g.py3-none-macosx_11_0_arm64), nevernone-any.
Building the distribution wheels (all platforms)
Config lives in [tool.cibuildwheel] (pyproject.toml); cibuildwheel
compiles inside each target environment (required for manylinux) and runs
auditwheel / delocate. In CI the C sources are fetched from
github.com/c-vision/Primal and vendored with sync_csrc.py before building
(see .github/workflows/wheels.yml).
pip install cibuildwheel
python -m cibuildwheel --output-dir dist # current platform
python -m cibuildwheel --platform <os> --output-dir dist
Notes:
- macOS: set
MACOSX_DEPLOYMENT_TARGET(e.g.11.0) for older systems and codesign the dylib (codesign -s -);cibuildwheelhandlesdelocate. - Windows: built with mingw-w64 (
before-all = "choco install -y mingw") and-staticso the DLL is standalone. - Linux:
-pthreadis linked (manylinux glibc < 2.34 keeps pthread inlibpthread).
Release procedure
-
Generate the vendored snapshot (makes the sdist self-contained; it is gitignored, not committed):
python sync_csrc.py -
Regenerate the binding if
primal.hchanged:python tools/gen_bindings.py -
Bump the version — the single source of truth is
__version__insrc/primalsolver/__init__.py(pyproject.tomlreads it viadynamic = ["version"]). -
Check locally:
pip install -e . python -c "import primalsolver as ps; print(ps.version())" python -m pip wheel . --no-build-isolation --no-deps -w dist
-
Commit and tag (in the Python repository, never the C one):
git add -A && git commit -m "release vX.Y.Z" git tag vX.Y.Z git push origin main --tags
-
Publish: pushing a tag
vX.Y.Ztriggers.github/workflows/wheels.yml, which builds the wheels + sdist and uploads them as artifacts. Publishing to PyPI runs when a GitHub Release is published for that tag, using the repository secretPYPI_API_TOKEN(token auth).Prerequisites (one-time): add
PYPI_API_TOKENin the repo (Settings → Secrets and variables → Actions) with the PyPI API token.Manual/fallback publish (e.g. TestPyPI):
pip install build twine python -m build twine check dist/* twine upload --repository testpypi dist/* # then: twine upload dist/*
Repository layout
pyproject.toml # metadata + cibuildwheel config
setup.py # compiles the shared lib; forces the platform wheel tag
MANIFEST.in # sdist contents (vendored csrc, no prebuilt binaries)
sync_csrc.py # vendored C-source snapshot -> csrc/
tools/gen_bindings.py # primal.h -> src/primalsolver/_bindings.py (547 fns)
csrc/ # C-source snapshot (generated by sync_csrc.py; gitignored)
src/primalsolver/ # the installed package (__init__, _native, _bindings, _lib)
.github/workflows/wheels.yml
License
Apache-2.0, same as PrimalSolver. See LICENSE.
Metadata
Release files for primalsolver 0.1.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| primalsolver-0.1.2.tar.gz | 454.8 kB | Details |
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| primalsolver-0.1.2-py3-none-win_amd64.whl | Python 3 | none | Windows x86-64 | Details |
| primalsolver-0.1.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl | Python 3 | none | Linux glibc 2.17+ x86-64 | Details |
| primalsolver-0.1.2-py3-none-macosx_11_0_arm64.whl | Python 3 | none | macOS 11.0+ ARM64 | Details |
| primalsolver-0.1.2-py3-none-macosx_10_13_x86_64.whl | Python 3 | none | macOS 10.13+ x86-64 | Details |
Total release size: 3.0 MB
Release files / primalsolver-0.1.2.tar.gz
| Download URL | primalsolver-0.1.2.tar.gz |
|---|---|
| Size | 454.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
dd019a00c4b68e9ef9dba9164334099c3f6fc21afe2f7b26578b7b2fef64ef2f
|
|
BLAKE2b-256 checksum How to use checksums |
4af83a5d343a37ec1e5a4070d56be187bf29fd146e99e461054fb4d0a7f650fd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / primalsolver-0.1.2-py3-none-win_amd64.whl
| Download URL | primalsolver-0.1.2-py3-none-win_amd64.whl |
|---|---|
| Size | 460.4 kB |
| Tags | Python 3 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
64ce341f09e90761d4a3c763a8af8a4a4f084f7724072b1be6f2ec8d2d9b8baf
|
|
BLAKE2b-256 checksum How to use checksums |
c728161d0df597f9fd8524430aaf7dd127bc3eb6e5443dc829629dfdc66fb5a7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / primalsolver-0.1.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | primalsolver-0.1.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
|---|---|
| Size | 400.1 kB |
| Tags | Linux glibc 2.17+ x86-64 Python 3 |
|
SHA-256 checksum How to use checksums |
74298c8dc4c2a45cb743402b46d587e70a44bf6e31f97a291b5330242ccd736c
|
|
BLAKE2b-256 checksum How to use checksums |
c038f5a0a3c32243395b174b7d3aff44beacfa3a8022ed6bd72f653224ccdfb5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / primalsolver-0.1.2-py3-none-macosx_11_0_arm64.whl
| Download URL | primalsolver-0.1.2-py3-none-macosx_11_0_arm64.whl |
|---|---|
| Size | 821.3 kB |
| Tags | Python 3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
52060e14483aea20c7d902e45171fca45fc29525c0f2a3b8324bd4c53123d49a
|
|
BLAKE2b-256 checksum How to use checksums |
8cab42a64838761389642b11f8e3156a43f377c8f0984ac2c6d3fb5d890cf8d6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / primalsolver-0.1.2-py3-none-macosx_10_13_x86_64.whl
| Download URL | primalsolver-0.1.2-py3-none-macosx_10_13_x86_64.whl |
|---|---|
| Size | 821.1 kB |
| Tags | Python 3 macOS 10.13+ x86-64 |
|
SHA-256 checksum How to use checksums |
f107fd54be9244e2c223c6589683b025ca6c4a9c55f950284d87865455d4aefb
|
|
BLAKE2b-256 checksum How to use checksums |
b30bd3c8c97beced67d3e522232996fe96d89a701b886ab5f354f42a24fc14d7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|