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.1
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.1.tar.gz | 453.1 kB | Details |
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| primalsolver-0.1.1-py3-none-win_amd64.whl | Python 3 | none | Windows x86-64 | Details |
| primalsolver-0.1.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl | Python 3 | none | Linux glibc 2.17+ x86-64 | Details |
| primalsolver-0.1.1-py3-none-macosx_11_0_arm64.whl | Python 3 | none | macOS 11.0+ ARM64 | Details |
| primalsolver-0.1.1-py3-none-macosx_10_13_x86_64.whl | Python 3 | none | macOS 10.13+ x86-64 | Details |
Total release size: 2.9 MB
Release files / primalsolver-0.1.1.tar.gz
| Download URL | primalsolver-0.1.1.tar.gz |
|---|---|
| Size | 453.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
6fbf920efae4cbf99c9001c97eebba9dc08a54df15dba2d85bf19457a95e798d
|
|
BLAKE2b-256 checksum How to use checksums |
d8524a0f6461c81505afc2f8b7703927f56dafdda720ba1d5be72f47c6dc19ab
|
| 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.1-py3-none-win_amd64.whl
| Download URL | primalsolver-0.1.1-py3-none-win_amd64.whl |
|---|---|
| Size | 458.4 kB |
| Tags | Python 3 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
9eda224204802de8d21230661f47019b3c3f8e56503f55c95860df882f242a8d
|
|
BLAKE2b-256 checksum How to use checksums |
e1ba96293eef9b96ec4934135d94cdbcab89d2c7990c403e700538deb4537004
|
| 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.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | primalsolver-0.1.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
|---|---|
| Size | 398.4 kB |
| Tags | Linux glibc 2.17+ x86-64 Python 3 |
|
SHA-256 checksum How to use checksums |
f27a7bef2ae35f7dadd2e44fea6e7187177a30c113b2a8d2d0b79306f89a4119
|
|
BLAKE2b-256 checksum How to use checksums |
fb8551cb70715c3615410d21e6c613fd9cffcc7f532f586dc57403ca7020ba95
|
| 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.1-py3-none-macosx_11_0_arm64.whl
| Download URL | primalsolver-0.1.1-py3-none-macosx_11_0_arm64.whl |
|---|---|
| Size | 817.8 kB |
| Tags | Python 3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
e39df1f12f00ef4f2a409e9351243838a2c83cca91cf9d8e8afa864a25e04548
|
|
BLAKE2b-256 checksum How to use checksums |
15e64161dce4bcd4e0489c629d0759d36b7fc3435a8af4f9eb7e759a904b9927
|
| 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.1-py3-none-macosx_10_13_x86_64.whl
| Download URL | primalsolver-0.1.1-py3-none-macosx_10_13_x86_64.whl |
|---|---|
| Size | 817.6 kB |
| Tags | Python 3 macOS 10.13+ x86-64 |
|
SHA-256 checksum How to use checksums |
29263d15f3878197c56f253c8b995362198f1960656094935ca93a22e9cef9f4
|
|
BLAKE2b-256 checksum How to use checksums |
be048f0d72a77446278925df6d421c40210b85d16bbee9fe77c58fc35a312aee
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|