Install

snn_opt is on PyPI. Wheels ship for Linux (x86_64, aarch64), macOS (Apple Silicon), and Windows across CPython 3.9–3.13, with a compiled C++ kernel pre-built. No compiler needed on your side.

pip install snn-opt                  # core
pip install "snn-opt[examples]"      # also matplotlib, for the figures

The PyPI distribution name is snn-opt (hyphenated, lowercase, per PEP 503); the Python import name is snn_opt. So you pip install snn-opt and then import snn_opt.

Only NumPy and SciPy are required at runtime.

Developer install

For a checkout that lets you edit the source:

git clone https://github.com/ahkhan03/SNN_opt.git
cd SNN_opt
pip install -e .                     # editable
pip install -e ".[examples]"         # + matplotlib for the figures

For a specific commit (reproducibility for papers):

pip install "git+https://github.com/ahkhan03/SNN_opt.git@<commit-sha>"

Your first QP

We’ll solve the smallest QP that does anything interesting:

minxR2 12x2s.t.x1+2x21.\min_{x \in \mathbb{R}^2}\ \tfrac{1}{2}\|x\|^2 \quad\text{s.t.}\quad x_1 + 2x_2 \le 1.

Without the constraint, the optimum is the origin and the trajectory is uneventful. The constraint cuts off a half-plane, and the solver has to figure out that the optimum sits on the line.

import numpy as np
from snn_opt import solve_qp

# Hessian, linear cost, constraint matrix, constraint offset.
A  = np.eye(2)
b  = np.zeros(2)
C  = np.array([[1.0, 2.0]])
d  = np.array([-1.0])
x0 = np.array([1.0, 1.0])      # starts feasible

result = solve_qp(A, b, C, d, x0, max_iterations=1000)

print(result.summary())
print("x* =", result.final_x)
print("f* =", result.final_objective)

Run it. You should see an answer very close to x=(0,0)x^\star = (0, 0) with f* ≈ 0, plus a one-line-per-stat summary including the iteration count and the number of projection events.

Compiled C++ backend

The PyPI wheels include a compiled kernel (snn_opt._kernel, built via pybind11) that accelerates the inner adaptive-projection loop by roughly an order of magnitude. Opt in with the backend keyword:

result = solve_qp(A, b, C, d, x0, backend='c')        # compiled kernel
result = solve_qp(A, b, C, d, x0, backend='python')   # reference (default)

Both backends are kept in lockstep by a parity test suite that asserts they agree on every iteration. The C kernel supports dense problems with projection_method='adaptive'; sparse and non-adaptive paths transparently use Python. The same kernel source is HLS-compatible and is the basis for the planned FPGA deployment track.

What the result tells you

SolverResult is a dataclass with everything the solver knows about the run, not just the final answer:

result.final_x                # the solution
result.final_objective        # f(x*)
result.converged              # bool
result.convergence_reason     # which criterion fired
result.iterations_used        # how many Euler steps
result.n_projections          # how many constraint corrections
result.t, result.X            # full trajectory: time, state
result.objective_values       # f(x_t) per iteration
result.constraint_violations  # max violation per iteration
result.spike_times            # iterations at which a spike fired
result.spike_constraints      # which constraint(s) fired each time
result.spike_norms            # how big each spike was

The last four fields are the raw material for the spike raster you’ll see throughout this site — they’re how you learn what your solver is doing.

Next

  • An SVM is a QP — turn a real machine-learning problem into something solve_qp can take.
  • Reading the spike raster — interpret the spike-time outputs above as a diagnostic plot.
  • See examples/ in the repo for nine more worked QPs — 3-D polytopes, infeasible recovery, equality constraints, warm starting, and a full SVM.