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: a Git tag triggers
.github/workflows/wheels.yml, which builds the wheels + sdist and uploads them as artifacts. Publishing to PyPI runs only when a GitHub Release is published for that tag, using trusted publishing (OIDC, no API token stored).Prerequisites (one-time): register a PyPI trusted publisher for this repository and create a
pypienvironment in the GitHub repo settings.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.0
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.0.tar.gz | 453.5 kB | Details |
Built distributions (wheels)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| primalsolver-0.1.0-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl | Python 3 | none | Linux glibc 2.28+ x86-64, Linux glibc 2.17+ x86-64 | Details |
| primalsolver-0.1.0-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl | Python 3 | none | Linux glibc 2.17+ ARM64, Linux glibc 2.28+ ARM64 | Details |
| primalsolver-0.1.0-py3-none-macosx_11_0_universal2.whl | Python 3 | none | macOS 11.0+ universal2 (ARM64, x86-64) | Details |
Total release size: 3.3 MB
Release files / primalsolver-0.1.0.tar.gz
| Download URL | primalsolver-0.1.0.tar.gz |
|---|---|
| Size | 453.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
2d3c1df91220884b383cebb1e6c015c4dec4cff94fb1d5b8d84bb6e14222d8df
|
|
BLAKE2b-256 checksum How to use checksums |
af1a855c4546a933f74fb6b189118189d9ee6848416b7d04f04bbcc5f272ffa0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.14
|
Release files / primalsolver-0.1.0-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl
| Download URL | primalsolver-0.1.0-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl |
|---|---|
| Size | 1.2 MB |
| Tags | Linux glibc 2.17+ x86-64 Linux glibc 2.28+ x86-64 Python 3 |
|
SHA-256 checksum How to use checksums |
5e98a77620beec074b36119d1290618d24ad539b1567bc97201478e6bfa22f48
|
|
BLAKE2b-256 checksum How to use checksums |
9fef075025920f809520e2272eec284c70cc17822652c27f31046a10f69f1e67
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.14
|
Release files / primalsolver-0.1.0-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl
| Download URL | primalsolver-0.1.0-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl |
|---|---|
| Size | 1.2 MB |
| Tags | Linux glibc 2.17+ ARM64 Linux glibc 2.28+ ARM64 Python 3 |
|
SHA-256 checksum How to use checksums |
6d5b3ba9c2f6d0a84cb69c0e2e1226f9729a91d413d43fe8c770443c7caeecee
|
|
BLAKE2b-256 checksum How to use checksums |
5ae157b9f899d2fefd80fe255e2cc4892fc6164c7cfbe783b5061507da6b7223
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.14
|
Release files / primalsolver-0.1.0-py3-none-macosx_11_0_universal2.whl
| Download URL | primalsolver-0.1.0-py3-none-macosx_11_0_universal2.whl |
|---|---|
| Size | 392.5 kB |
| Tags | Python 3 macOS 11.0+ universal2 (ARM64, x86-64) |
|
SHA-256 checksum How to use checksums |
43950d2e2638c91e1f1f5af25e4225d594e4183b1fd8b675622ea87f9ab7b554
|
|
BLAKE2b-256 checksum How to use checksums |
b2490a5762069d74da9a78183beaaa1b5efd0e0fe59d35548a23f428903376a8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.14
|