Compatibility¶
What scijit is measured against, where it agrees with scipy exactly, where it differs, and which routines are safe to call concurrently.
Last measured 2026-07-27 on scipy 1.18.0, numba 0.66.0, numpy 2.4.6.
Requirements¶
Runtime dependencies, installed automatically with scijit:
package |
requirement |
why |
|---|---|---|
Python |
|
|
|
|
0.66 adds the multi-dimensional fancy indexing the package uses |
|
numba’s bound |
scijit sets no bound of its own; the version is whatever the installed numba supports |
|
|
makes the jitclasses callable ( |
scipy is not a runtime dependency and is never imported by scijit. It is a
test extra: pip install scijit[test] pulls scipy>=1.18, against which the
test suites assert parity.
Agreement with scipy¶
scijit wraps the same Fortran scipy has historically wrapped, so most routines agree exactly. Where scipy has since moved a routine to a C translation or a pure-Python port, scijit keeps the Fortran; the two then agree to a few ulp, or a little more for the few where scipy runs a different algorithm entirely. Argument names, defaults, return fields, status codes and error behaviour follow scipy exactly.
Each routine’s docstring names any difference from scipy under Notes; silence
means the two were compared and agreed. Per-routine accuracy figures are in the
docstrings.
Degenerate inputs¶
On a few degenerate inputs where scipy is silent and returns a wrong or undefined
result, scijit raises instead: a wrong-length SLSQP gradient, a callback that
writes nothing, and nnls with zero equations (which reads uninitialised memory
in scipy). Each is noted in the routine’s docstring.
Distinct warning classes¶
scijit.integrate.IntegrationWarning and scijit.optimize.OptimizeWarning are
distinct class objects from the scipy warnings of the same name, sharing only the
name and the UserWarning base. A filter naming scipy’s class does not catch this
one, and the reverse. Filter on this package’s class or on UserWarning. This
keeps scipy out of the runtime dependencies. The same holds for
ODEintWarning/ODEpackError against scipy’s private ODEPACK error type.
Thread safety¶
Every routine in the three subpackages is safe to call from parallel code: inside
a numba.prange loop, a thread pool, or separate processes. “Parallel” here means
a @njit(parallel=True) function whose loop runs over prange instead of range.
One pattern keeps it safe:
Build in serial, evaluate in parallel. Construct each spline, interpolator or solver result before the parallel loop; inside the loop, only evaluate it. Evaluation is read-only, so many threads can share one object safely.
from numba import njit, prange
@njit(parallel=True)
def eval_many(spl, xs, out): # spl built in serial by the caller
for i in prange(xs.shape[0]):
out[i] = spl(xs[i]) # read-only, shared safely across threads
multiprocessing is always safe: each process loads its own copy of the
library. Measured: 32 concurrent threads reproduce the single-threaded result
exactly.
Two argument choices turn off the parallelism:
A Python
callback, ordispoutput. Passing a plain Python function as acallbacktofmin_*,basinhoppingordifferential_evolution, or settingdisp=1, forces those calls to run one at a time, even inside aprangeloop. For the parallel path, pass the callback as an@njitfunction and leavedisp=0.A shared random generator. In
basinhoppinganddifferential_evolution, leaverngat its default or pass an integer seed, so each call gets its own random state. Do not share onenumpy.random.Generatorobject across threads.
Compile-time constraints inside @njit¶
An argument that selects the SHAPE or PRESENCE of the result must be readable when
the call compiles: a literal, an omitted default, or a module-level constant. A
runtime value raises TypingError. From Python any value works.
full_output(bisect,brentq,brenth,ridder,toms748,newton,fixed_point,fsolve,leastsq,fminbound,brent,golden),brute’sfull_outputandworkersall select the return shape.A
methodstring that selects the iteration or the return type (root,minimize,minimize_scalar,fixed_point), and whetherroot’s orfsolve’sjac/fprimeisNone, must be a compile-time constant.romb’sshowandnquad’s per-axis weightoptsare read at compile time.A
bracket,bounds,brack,ranges,argsoroptssequence passed into compiled code must be a tuple or array, not a Python list.
Passing a settings dict from inside @njit (minimize’s options, nquad’s
opts):
A dict whose values are all numbers can hold only numbers. A tuple or array value in it fails to compile.
An empty
{}has no type. PassNonefor the empty case.nquad’sweightandpointssettings work as a singleoptsdict applied to every axis, but not as a per-axis tuple of dicts. Callnquadfrom Python for the per-axis form.
Result containers¶
Read a result the way you would in scipy. res.x, res.fun, res.success,
res['x'], res.get(...), and in / keys / values / items all work, the
same from Python and inside @njit.
Two differences from scipy:
The result is a namedtuple, not a
dictsubclass.isinstance(res, scipy.optimize.OptimizeResult)is False, and integer indexing or unpacking reads the field values (scipy raises, or yields field names).A field the solver did not compute is absent, not
None. Reading it raisesAttributeErrorfrom Python,TypingErrorinside@njit. The routine’sNoteslists any field that is always absent.
Constructor defaults are Python-only. A jitclass constructor’s defaults apply
from Python and are lost inside @njit, where every argument must be written out.
This affects OdeSolution and its LSODA/complex twins, and every
scijit.interpolate class constructed directly. The public @njit factory names
(CubicSpline, make_interp_spline, …) keep their defaults in both worlds; the
private jitclass they wrap does not.
Per-routine divergences¶
Where a routine differs from scipy, its docstring Notes says how; the
difference also shows on the routine’s page in the
API reference. Silence there means it was compared
to scipy and agreed.
Coverage of scipy arguments¶
A routine absent from this table matches SciPy on every argument it accepts.
Listed is each argument or mode a routine refuses where SciPy accepts it. An
argument that behaves differently but does not raise is not here; it is in the
routine’s Notes. An argument scijit adds beyond scipy is not here either.
Exception class: from Python the refusal is a ValueError unless noted; a
refusal resolved when the call compiles is a TypingError inside @njit.
BSpline and make_interp_spline refuse complex coefficients with a
TypingError in both.
interpolate¶
Routine |
Raises on |
|---|---|
|
|
|
|
|
a complex |
|
a complex |
optimize¶
Routine |
Raises on |
Routes to |
|---|---|---|
|
|
the nonlinear |
|
|
|
|
|
|
|
|
the dense |
|
|
|
|
|
|
|
|
integrate¶
Routine |
Raises on |
|---|---|
|
|
Caching¶
Nothing here caches compiled code to disk. The first call to a routine in a process pays numba’s one-time compile cost; later calls in the same process are fast.
Build each spline, interpolator and solver result once, then reuse it inside compiled code.
Platforms¶
Wheels are built and tested for Linux, Windows and Apple Silicon (arm64) macOS. Intel x86_64 macOS is not supported: numba moved it to Tier 2 in 0.63 and stopped shipping binaries for it, so an x86_64 macOS wheel would have no numba to install beside it.