scijit 0.1.0__py3-none-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- scijit/__init__.py +49 -0
- scijit/_lib/__init__.py +7 -0
- scijit/_lib/liblapackref.dll +0 -0
- scijit/_probe.py +369 -0
- scijit/integrate/__init__.py +101 -0
- scijit/integrate/_events.py +408 -0
- scijit/integrate/_ivp.py +928 -0
- scijit/integrate/_nquad.py +1781 -0
- scijit/integrate/_odeint_scipy.py +2491 -0
- scijit/integrate/_odepack.py +216 -0
- scijit/integrate/_quadpack.py +1877 -0
- scijit/integrate/_quadrature.py +2028 -0
- scijit/integrate/_solve_ivp.py +2297 -0
- scijit/integrate/liblsoda.dll +0 -0
- scijit/integrate/libquadpack.dll +0 -0
- scijit/interpolate/__init__.py +88 -0
- scijit/interpolate/_bspline.py +3312 -0
- scijit/interpolate/_cubic.py +2211 -0
- scijit/interpolate/_interp1d.py +1985 -0
- scijit/interpolate/_ndaxis.py +329 -0
- scijit/interpolate/_rgi.py +2558 -0
- scijit/interpolate/evaluators.py +1466 -0
- scijit/interpolate/fitters.py +2542 -0
- scijit/interpolate/interpolate.py +6141 -0
- scijit/interpolate/libfitpack.dll +0 -0
- scijit/optimize/__init__.py +119 -0
- scijit/optimize/_assignment.py +421 -0
- scijit/optimize/_callback.py +407 -0
- scijit/optimize/_global.py +2600 -0
- scijit/optimize/_lapack.py +128 -0
- scijit/optimize/_lbfgsb.py +1116 -0
- scijit/optimize/_lsq.py +2908 -0
- scijit/optimize/_minimize.py +2404 -0
- scijit/optimize/_minimize_py.py +3136 -0
- scijit/optimize/_minpack.py +4320 -0
- scijit/optimize/_prima.py +1895 -0
- scijit/optimize/_qmc.py +991 -0
- scijit/optimize/_scalar.py +5245 -0
- scijit/optimize/_slsqp.py +1355 -0
- scijit/optimize/liblbfgsb.dll +0 -0
- scijit/optimize/libminpack.dll +0 -0
- scijit/optimize/liboptlapack.dll +0 -0
- scijit/optimize/libprima.dll +0 -0
- scijit/optimize/libslsqp.dll +0 -0
- scijit-0.1.0.dist-info/METADATA +212 -0
- scijit-0.1.0.dist-info/RECORD +49 -0
- scijit-0.1.0.dist-info/WHEEL +5 -0
- scijit-0.1.0.dist-info/licenses/LICENSE +58 -0
- scijit-0.1.0.dist-info/top_level.txt +1 -0
scijit/__init__.py
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""scijit: scipy-equivalent numerical routines callable inside numba
|
|
2
|
+
@njit code, backed by the same Fortran libraries scipy itself wraps.
|
|
3
|
+
|
|
4
|
+
Subpackages
|
|
5
|
+
interpolate FITPACK (Dierckx) splines, all 31 public routines
|
|
6
|
+
plus scipy.interpolate-equivalent jitclasses;
|
|
7
|
+
results bit-for-bit identical to scipy.interpolate.
|
|
8
|
+
|
|
9
|
+
optimize MINPACK + L-BFGS-B + SLSQP + PRIMA, minimize
|
|
10
|
+
(unified, bound/constrained), root/fsolve/leastsq +
|
|
11
|
+
direct MINPACK drivers with the NumbaMinpack-
|
|
12
|
+
compatible cfunc API, and the Powell derivative-free
|
|
13
|
+
family (uobyqa/newuoa/bobyqa/lincoa/cobyla).
|
|
14
|
+
|
|
15
|
+
integrate QUADPACK + ODEPACK. quad over seven routes,
|
|
16
|
+
nquad/dblquad/tplquad over a nest of it, and
|
|
17
|
+
solve_ivp/odeint (LSODA/LSODAR).
|
|
18
|
+
|
|
19
|
+
scijit.optimize's least-squares path uses one vendored internal library,
|
|
20
|
+
scijit/_lib/liblapackref (full Reference-LAPACK + BLAS), so the
|
|
21
|
+
package needs no system BLAS/LAPACK, only gfortran at install time.
|
|
22
|
+
Each subpackage wraps one Fortran pack: sources in src/<pack>/ with a
|
|
23
|
+
bind(c) wrappers.f90, compiled at install into a shared library.
|
|
24
|
+
|
|
25
|
+
Originally inspired by Nicholas Wogan's NumbaMinpack (MIT), which
|
|
26
|
+
pioneered calling a compiled Fortran pack from inside @njit code:
|
|
27
|
+
https://github.com/Nicholaswogan/NumbaMinpack
|
|
28
|
+
FITPACK is by Paul Dierckx (Curve and Surface Fitting with Splines, 1993).
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
import os
|
|
32
|
+
import sys
|
|
33
|
+
|
|
34
|
+
# On Windows (Python 3.8+), register all package directories containing .dll files
|
|
35
|
+
# so Windows can find sibling shared libraries (like liblapackref.dll)
|
|
36
|
+
if sys.platform == 'win32':
|
|
37
|
+
_pkg_dir = os.path.dirname(os.path.abspath(__file__))
|
|
38
|
+
for _root, _dirs, _files in os.walk(_pkg_dir):
|
|
39
|
+
if any(_f.endswith('.dll') for _f in _files):
|
|
40
|
+
try:
|
|
41
|
+
os.add_dll_directory(_root)
|
|
42
|
+
except OSError:
|
|
43
|
+
pass
|
|
44
|
+
|
|
45
|
+
from . import interpolate
|
|
46
|
+
from . import optimize
|
|
47
|
+
from . import integrate
|
|
48
|
+
|
|
49
|
+
__version__ = '0.1.0'
|
scijit/_lib/__init__.py
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"""scijit._lib, internal shared Fortran libraries.
|
|
2
|
+
|
|
3
|
+
Holds liblapackref (the vendored full-precision Reference-LAPACK + BLAS,
|
|
4
|
+
BSD-3) that packs such as scijit.sparse.linalg (ARPACK) link against
|
|
5
|
+
at build time and resolve at import via a relocatable rpath. Not a
|
|
6
|
+
public API.
|
|
7
|
+
"""
|
|
Binary file
|
scijit/_probe.py
ADDED
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
"""Pre-flight validation of pointer-writing ``@cfunc`` callbacks.
|
|
2
|
+
|
|
3
|
+
Every Fortran pack in this package receives its user function as a
|
|
4
|
+
``@cfunc`` whose OUTPUT travels through a pointer. That has one failure
|
|
5
|
+
mode with no natural diagnostic: a callback that never writes leaves the
|
|
6
|
+
buffer holding whatever it held, and the Fortran reads that as a perfectly
|
|
7
|
+
converged problem. Measured before this module existed:
|
|
8
|
+
|
|
9
|
+
odeint, RHS writes no ydot -> returns the initial condition
|
|
10
|
+
minimize_uobyqa, objective writes no f -> returns x0 after 22 evaluations
|
|
11
|
+
lmdif, residual writes no fvec -> returns x0
|
|
12
|
+
|
|
13
|
+
all three with ``success = True``. A transposed argument order does exactly
|
|
14
|
+
this: writing ``@cfunc(minpack_jac_sig) def j(x, fvec, fjac, args, iflag)``
|
|
15
|
+
when the ABI is ``(x, fvec, fjac, iflag, args)`` makes ``iflag[0]`` read
|
|
16
|
+
``args[0]``, so the residual branch never runs.
|
|
17
|
+
|
|
18
|
+
scipy cannot hit this, because its callbacks RETURN a value whose shape it
|
|
19
|
+
validates (a ``None`` return raises ``RuntimeError``/``TypeError``). The
|
|
20
|
+
exposure is a consequence of the pointer ABI, so the check belongs here.
|
|
21
|
+
|
|
22
|
+
How it works
|
|
23
|
+
------------
|
|
24
|
+
Call the user's callback ONCE before handing it to the Fortran, with the
|
|
25
|
+
output buffer filled with a sentinel. If the buffer comes back untouched,
|
|
26
|
+
nothing was written. Two DIFFERENT sentinels are used in sequence, so a
|
|
27
|
+
callback that happens to write the first sentinel verbatim is still seen to
|
|
28
|
+
have written.
|
|
29
|
+
|
|
30
|
+
The distinction this preserves is the important one: a genuine all-zero
|
|
31
|
+
residual is NOT flagged, because writing ``0.0`` still overwrites the
|
|
32
|
+
sentinel. "The callback never ran" and "the problem is degenerate" become
|
|
33
|
+
separable, which is exactly what the Fortran cannot do.
|
|
34
|
+
|
|
35
|
+
Cost is one extra callback evaluation per solve (two in the rare case where
|
|
36
|
+
the first sentinel is written verbatim), against the hundreds or thousands a
|
|
37
|
+
solve performs.
|
|
38
|
+
|
|
39
|
+
Every entry point that takes one of these callbacks accepts ``validate``,
|
|
40
|
+
default ``True``. Pass ``False`` to skip the probe.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
import llvmlite.ir as ir
|
|
44
|
+
import numpy as np
|
|
45
|
+
from numba import njit, types
|
|
46
|
+
from numba.extending import intrinsic
|
|
47
|
+
|
|
48
|
+
# Two unrelated denormal-range values. Chosen to be things no real residual
|
|
49
|
+
# produces, and different from each other so that writing one verbatim still
|
|
50
|
+
# counts as a write.
|
|
51
|
+
SENTINEL_A = -1.234567890123e-301
|
|
52
|
+
SENTINEL_B = 9.876543210987e-302
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# ``args`` and ``x`` may arrive as a tuple or list, which the public API
|
|
56
|
+
# accepts; numba's ``np.asarray`` handles those but ``.astype`` does not
|
|
57
|
+
# (gotcha #0), so every probe converts with ``asarray`` first.
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _dptr():
|
|
61
|
+
return ir.DoubleType().as_pointer()
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@intrinsic
|
|
65
|
+
def _call_p3(typingctx, fn_addr, p0, p1, p2):
|
|
66
|
+
"""Call ``void(double*, double*, double*)`` by raw address.
|
|
67
|
+
|
|
68
|
+
Covers both ``minpack_sig`` and ``prima_sig``, which have the same C
|
|
69
|
+
signature.
|
|
70
|
+
"""
|
|
71
|
+
signature = types.void(types.intp, types.intp, types.intp, types.intp)
|
|
72
|
+
|
|
73
|
+
def codegen(context, builder, sg, args):
|
|
74
|
+
fnaddr, a0, a1, a2 = args
|
|
75
|
+
fnty = ir.FunctionType(ir.VoidType(), [_dptr()] * 3)
|
|
76
|
+
fptr = builder.inttoptr(fnaddr, fnty.as_pointer())
|
|
77
|
+
builder.call(fptr, [builder.inttoptr(a0, _dptr()),
|
|
78
|
+
builder.inttoptr(a1, _dptr()),
|
|
79
|
+
builder.inttoptr(a2, _dptr())])
|
|
80
|
+
return context.get_dummy_value()
|
|
81
|
+
|
|
82
|
+
return signature, codegen
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@intrinsic
|
|
86
|
+
def _call_p4(typingctx, fn_addr, p0, p1, p2, p3):
|
|
87
|
+
"""Call ``void(double*, double*, double*, double*)`` -- ``prima_con_sig``."""
|
|
88
|
+
signature = types.void(types.intp, types.intp, types.intp, types.intp,
|
|
89
|
+
types.intp)
|
|
90
|
+
|
|
91
|
+
def codegen(context, builder, sg, args):
|
|
92
|
+
fnaddr, a0, a1, a2, a3 = args
|
|
93
|
+
fnty = ir.FunctionType(ir.VoidType(), [_dptr()] * 4)
|
|
94
|
+
fptr = builder.inttoptr(fnaddr, fnty.as_pointer())
|
|
95
|
+
builder.call(fptr, [builder.inttoptr(a0, _dptr()),
|
|
96
|
+
builder.inttoptr(a1, _dptr()),
|
|
97
|
+
builder.inttoptr(a2, _dptr()),
|
|
98
|
+
builder.inttoptr(a3, _dptr())])
|
|
99
|
+
return context.get_dummy_value()
|
|
100
|
+
|
|
101
|
+
return signature, codegen
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@intrinsic
|
|
105
|
+
def _call_tp3(typingctx, fn_addr, t, p0, p1, p2):
|
|
106
|
+
"""Call ``void(double, double*, double*, double*)`` -- ``lsoda_sig``.
|
|
107
|
+
|
|
108
|
+
``t`` arrives by VALUE, which is why this cannot reuse ``_call_p3``.
|
|
109
|
+
"""
|
|
110
|
+
signature = types.void(types.intp, types.double, types.intp, types.intp,
|
|
111
|
+
types.intp)
|
|
112
|
+
|
|
113
|
+
def codegen(context, builder, sg, args):
|
|
114
|
+
fnaddr, tv, a0, a1, a2 = args
|
|
115
|
+
fnty = ir.FunctionType(ir.VoidType(),
|
|
116
|
+
[ir.DoubleType()] + [_dptr()] * 3)
|
|
117
|
+
fptr = builder.inttoptr(fnaddr, fnty.as_pointer())
|
|
118
|
+
builder.call(fptr, [tv,
|
|
119
|
+
builder.inttoptr(a0, _dptr()),
|
|
120
|
+
builder.inttoptr(a1, _dptr()),
|
|
121
|
+
builder.inttoptr(a2, _dptr())])
|
|
122
|
+
return context.get_dummy_value()
|
|
123
|
+
|
|
124
|
+
return signature, codegen
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@intrinsic
|
|
128
|
+
def _call_jac(typingctx, fn_addr, p0, p1, p2, pi, p4):
|
|
129
|
+
"""Call ``void(double*, double*, double*, int32*, double*)``.
|
|
130
|
+
|
|
131
|
+
``minpack_jac_sig``.
|
|
132
|
+
"""
|
|
133
|
+
signature = types.void(types.intp, types.intp, types.intp, types.intp,
|
|
134
|
+
types.intp, types.intp)
|
|
135
|
+
|
|
136
|
+
def codegen(context, builder, sg, args):
|
|
137
|
+
fnaddr, a0, a1, a2, ai, a4 = args
|
|
138
|
+
iptr = ir.IntType(32).as_pointer()
|
|
139
|
+
fnty = ir.FunctionType(ir.VoidType(),
|
|
140
|
+
[_dptr()] * 3 + [iptr, _dptr()])
|
|
141
|
+
fptr = builder.inttoptr(fnaddr, fnty.as_pointer())
|
|
142
|
+
builder.call(fptr, [builder.inttoptr(a0, _dptr()),
|
|
143
|
+
builder.inttoptr(a1, _dptr()),
|
|
144
|
+
builder.inttoptr(a2, _dptr()),
|
|
145
|
+
builder.inttoptr(ai, iptr),
|
|
146
|
+
builder.inttoptr(a4, _dptr())])
|
|
147
|
+
return context.get_dummy_value()
|
|
148
|
+
|
|
149
|
+
return signature, codegen
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
@njit
|
|
153
|
+
def _untouched(buf, sentinel):
|
|
154
|
+
for i in range(buf.size):
|
|
155
|
+
if buf[i] != sentinel:
|
|
156
|
+
return False
|
|
157
|
+
return True
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
@njit
|
|
161
|
+
def wrote_residual(fn_addr, x, nout, args):
|
|
162
|
+
"""True if a ``minpack_sig``/``prima_sig`` callback writes its output.
|
|
163
|
+
|
|
164
|
+
Parameters
|
|
165
|
+
----------
|
|
166
|
+
fn_addr : int
|
|
167
|
+
``.address`` of the ``@cfunc``.
|
|
168
|
+
x : 1-D float64 ndarray
|
|
169
|
+
Point to probe at, normally the initial guess.
|
|
170
|
+
nout : int
|
|
171
|
+
Length of the output buffer (``m`` residuals, or 1 for a PRIMA
|
|
172
|
+
objective).
|
|
173
|
+
args : 1-D float64 ndarray
|
|
174
|
+
The user's ``args``, passed through unchanged.
|
|
175
|
+
"""
|
|
176
|
+
xw = np.ascontiguousarray(np.asarray(x).astype(np.float64))
|
|
177
|
+
aw = np.ascontiguousarray(np.asarray(args).astype(np.float64))
|
|
178
|
+
b1 = np.full(nout, SENTINEL_A)
|
|
179
|
+
_call_p3(fn_addr, xw.ctypes.data, b1.ctypes.data, aw.ctypes.data)
|
|
180
|
+
if not _untouched(b1, SENTINEL_A):
|
|
181
|
+
return True
|
|
182
|
+
b2 = np.full(nout, SENTINEL_B)
|
|
183
|
+
_call_p3(fn_addr, xw.ctypes.data, b2.ctypes.data, aw.ctypes.data)
|
|
184
|
+
return not _untouched(b2, SENTINEL_B)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
@njit
|
|
188
|
+
def wrote_objcon(fn_addr, x, ncon, args):
|
|
189
|
+
"""True if a ``prima_con_sig`` callback writes ``f`` (COBYLA)."""
|
|
190
|
+
xw = np.ascontiguousarray(np.asarray(x).astype(np.float64))
|
|
191
|
+
aw = np.ascontiguousarray(np.asarray(args).astype(np.float64))
|
|
192
|
+
nc = ncon if ncon > 0 else 1
|
|
193
|
+
c1 = np.zeros(nc)
|
|
194
|
+
b1 = np.full(1, SENTINEL_A)
|
|
195
|
+
_call_p4(fn_addr, xw.ctypes.data, b1.ctypes.data, c1.ctypes.data,
|
|
196
|
+
aw.ctypes.data)
|
|
197
|
+
if not _untouched(b1, SENTINEL_A):
|
|
198
|
+
return True
|
|
199
|
+
b2 = np.full(1, SENTINEL_B)
|
|
200
|
+
_call_p4(fn_addr, xw.ctypes.data, b2.ctypes.data, c1.ctypes.data,
|
|
201
|
+
aw.ctypes.data)
|
|
202
|
+
return not _untouched(b2, SENTINEL_B)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
@njit
|
|
206
|
+
def wrote_ydot(fn_addr, t, y, args):
|
|
207
|
+
"""True if an ``lsoda_sig`` right-hand side writes ``ydot``."""
|
|
208
|
+
yw = np.ascontiguousarray(np.asarray(y).astype(np.float64))
|
|
209
|
+
aw = np.ascontiguousarray(np.asarray(args).astype(np.float64))
|
|
210
|
+
n = yw.size
|
|
211
|
+
d1 = np.full(n, SENTINEL_A)
|
|
212
|
+
_call_tp3(fn_addr, t, yw.ctypes.data, d1.ctypes.data, aw.ctypes.data)
|
|
213
|
+
if not _untouched(d1, SENTINEL_A):
|
|
214
|
+
return True
|
|
215
|
+
d2 = np.full(n, SENTINEL_B)
|
|
216
|
+
_call_tp3(fn_addr, t, yw.ctypes.data, d2.ctypes.data, aw.ctypes.data)
|
|
217
|
+
return not _untouched(d2, SENTINEL_B)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
@njit
|
|
221
|
+
def wrote_jac_residual(fn_addr, x, nout, njac, args):
|
|
222
|
+
"""True if a ``minpack_jac_sig`` callback writes ``fvec`` at iflag=1.
|
|
223
|
+
|
|
224
|
+
Probes the RESIDUAL branch specifically, which is the one a transposed
|
|
225
|
+
``iflag``/``args`` argument order silently skips.
|
|
226
|
+
"""
|
|
227
|
+
xw = np.ascontiguousarray(np.asarray(x).astype(np.float64))
|
|
228
|
+
aw = np.ascontiguousarray(np.asarray(args).astype(np.float64))
|
|
229
|
+
jbuf = np.zeros(njac)
|
|
230
|
+
iflag = np.ones(1, np.int32)
|
|
231
|
+
b1 = np.full(nout, SENTINEL_A)
|
|
232
|
+
_call_jac(fn_addr, xw.ctypes.data, b1.ctypes.data, jbuf.ctypes.data,
|
|
233
|
+
iflag.ctypes.data, aw.ctypes.data)
|
|
234
|
+
if not _untouched(b1, SENTINEL_A):
|
|
235
|
+
return True
|
|
236
|
+
iflag[0] = 1
|
|
237
|
+
b2 = np.full(nout, SENTINEL_B)
|
|
238
|
+
_call_jac(fn_addr, xw.ctypes.data, b2.ctypes.data, jbuf.ctypes.data,
|
|
239
|
+
iflag.ctypes.data, aw.ctypes.data)
|
|
240
|
+
return not _untouched(b2, SENTINEL_B)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
@intrinsic
|
|
244
|
+
def _call_aprod(typingctx, fn_addr, transa, m, n, p0, p1, p2):
|
|
245
|
+
"""Call ``void(int32, int32, int32, double*, double*, double*)``.
|
|
246
|
+
|
|
247
|
+
``propack_sig``. The first three arguments arrive BY VALUE, and the
|
|
248
|
+
operation code selects ``y = A @ x`` (0) or ``y = A.T @ x`` (1).
|
|
249
|
+
"""
|
|
250
|
+
signature = types.void(types.intp, types.int32, types.int32, types.int32,
|
|
251
|
+
types.intp, types.intp, types.intp)
|
|
252
|
+
|
|
253
|
+
def codegen(context, builder, sg, args):
|
|
254
|
+
fnaddr, ta, mv, nv, a0, a1, a2 = args
|
|
255
|
+
i32 = ir.IntType(32)
|
|
256
|
+
fnty = ir.FunctionType(ir.VoidType(), [i32] * 3 + [_dptr()] * 3)
|
|
257
|
+
fptr = builder.inttoptr(fnaddr, fnty.as_pointer())
|
|
258
|
+
builder.call(fptr, [ta, mv, nv,
|
|
259
|
+
builder.inttoptr(a0, _dptr()),
|
|
260
|
+
builder.inttoptr(a1, _dptr()),
|
|
261
|
+
builder.inttoptr(a2, _dptr())])
|
|
262
|
+
return context.get_dummy_value()
|
|
263
|
+
|
|
264
|
+
return signature, codegen
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
@njit
|
|
268
|
+
def wrote_aprod(fn_addr, m, n, args):
|
|
269
|
+
"""True if a ``propack_sig`` operator writes ``y`` for ``y = A @ x``.
|
|
270
|
+
|
|
271
|
+
Probes with ``transa = 0``, so ``x`` has length ``n`` and ``y`` length
|
|
272
|
+
``m``. ``x`` is all ones rather than zeros: a correct operator applied
|
|
273
|
+
to a zero vector legitimately returns zeros, which would be
|
|
274
|
+
indistinguishable from not writing at all.
|
|
275
|
+
"""
|
|
276
|
+
aw = np.ascontiguousarray(np.asarray(args).astype(np.float64))
|
|
277
|
+
xv = np.ones(n)
|
|
278
|
+
y1 = np.full(m, SENTINEL_A)
|
|
279
|
+
_call_aprod(fn_addr, np.int32(0), np.int32(m), np.int32(n),
|
|
280
|
+
xv.ctypes.data, y1.ctypes.data, aw.ctypes.data)
|
|
281
|
+
if not _untouched(y1, SENTINEL_A):
|
|
282
|
+
return True
|
|
283
|
+
y2 = np.full(m, SENTINEL_B)
|
|
284
|
+
_call_aprod(fn_addr, np.int32(0), np.int32(m), np.int32(n),
|
|
285
|
+
xv.ctypes.data, y2.ctypes.data, aw.ctypes.data)
|
|
286
|
+
return not _untouched(y2, SENTINEL_B)
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
@njit
|
|
290
|
+
def _eval_resid(fn_addr, x, nout, args):
|
|
291
|
+
xw = np.ascontiguousarray(np.asarray(x).astype(np.float64))
|
|
292
|
+
aw = np.ascontiguousarray(np.asarray(args).astype(np.float64))
|
|
293
|
+
f = np.zeros(nout)
|
|
294
|
+
_call_p3(fn_addr, xw.ctypes.data, f.ctypes.data, aw.ctypes.data)
|
|
295
|
+
return f
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
@njit
|
|
299
|
+
def residual_status(fn_addr, xstar, fstar, nout, args):
|
|
300
|
+
"""Classify the returned solution. 0 ok, 1 degenerate, 2 inconsistent.
|
|
301
|
+
|
|
302
|
+
Three stages, cheapest first:
|
|
303
|
+
|
|
304
|
+
1. ``fstar`` is the residual the solver already computed at ``xstar``.
|
|
305
|
+
Any nonzero entry settles it, with no extra callback evaluation.
|
|
306
|
+
That is every ordinary solve, so the rest is rarely reached.
|
|
307
|
+
2. ``fstar`` all zero: evaluate ``f(xstar)`` directly. If the fresh
|
|
308
|
+
value is NOT zero it disagrees with what the solver reported, which
|
|
309
|
+
is a convergence failure rather than a degenerate residual, and is
|
|
310
|
+
reported separately (status 2). A stale ``fvec`` from an earlier
|
|
311
|
+
iterate or a callback with state both land here.
|
|
312
|
+
3. Fresh value also zero: perturb one component at a time. All zero
|
|
313
|
+
again means the residual carries no information (status 1).
|
|
314
|
+
"""
|
|
315
|
+
for i in range(nout):
|
|
316
|
+
if fstar[i] != 0.0:
|
|
317
|
+
return 0
|
|
318
|
+
|
|
319
|
+
f0 = _eval_resid(fn_addr, xstar, nout, args)
|
|
320
|
+
for i in range(nout):
|
|
321
|
+
if f0[i] != 0.0:
|
|
322
|
+
return 2
|
|
323
|
+
|
|
324
|
+
xs = np.ascontiguousarray(np.asarray(xstar).astype(np.float64))
|
|
325
|
+
for j in range(xs.size):
|
|
326
|
+
xp = xs.copy()
|
|
327
|
+
xp[j] = xs[j] + 1e-3 * (1.0 + np.abs(xs[j]))
|
|
328
|
+
fj = _eval_resid(fn_addr, np.ascontiguousarray(xp), nout, args)
|
|
329
|
+
for i in range(nout):
|
|
330
|
+
if fj[i] != 0.0:
|
|
331
|
+
return 0 # varies: a real residual, not degenerate
|
|
332
|
+
return 1
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
@njit
|
|
336
|
+
def _eval_jac_resid(fn_addr, x, nout, njac, args):
|
|
337
|
+
"""Residual from a ``minpack_jac_sig`` callback, at ``iflag = 1``."""
|
|
338
|
+
xw = np.ascontiguousarray(np.asarray(x).astype(np.float64))
|
|
339
|
+
aw = np.ascontiguousarray(np.asarray(args).astype(np.float64))
|
|
340
|
+
f = np.zeros(nout)
|
|
341
|
+
jbuf = np.zeros(njac)
|
|
342
|
+
iflag = np.ones(1, np.int32)
|
|
343
|
+
_call_jac(fn_addr, xw.ctypes.data, f.ctypes.data, jbuf.ctypes.data,
|
|
344
|
+
iflag.ctypes.data, aw.ctypes.data)
|
|
345
|
+
return f
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
@njit
|
|
349
|
+
def jac_residual_status(fn_addr, xstar, fstar, nout, njac, args):
|
|
350
|
+
"""``residual_status`` for the analytic-Jacobian callbacks."""
|
|
351
|
+
for i in range(nout):
|
|
352
|
+
if fstar[i] != 0.0:
|
|
353
|
+
return 0
|
|
354
|
+
|
|
355
|
+
f0 = _eval_jac_resid(fn_addr, xstar, nout, njac, args)
|
|
356
|
+
for i in range(nout):
|
|
357
|
+
if f0[i] != 0.0:
|
|
358
|
+
return 2
|
|
359
|
+
|
|
360
|
+
xs = np.ascontiguousarray(np.asarray(xstar).astype(np.float64))
|
|
361
|
+
for j in range(xs.size):
|
|
362
|
+
xp = xs.copy()
|
|
363
|
+
xp[j] = xs[j] + 1e-3 * (1.0 + np.abs(xs[j]))
|
|
364
|
+
fj = _eval_jac_resid(fn_addr, np.ascontiguousarray(xp), nout, njac,
|
|
365
|
+
args)
|
|
366
|
+
for i in range(nout):
|
|
367
|
+
if fj[i] != 0.0:
|
|
368
|
+
return 0 # varies: a real residual, not degenerate
|
|
369
|
+
return 1
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""scijit.integrate, adaptive integration and ODE solving in @njit.
|
|
2
|
+
|
|
3
|
+
Adaptive quadrature:
|
|
4
|
+
quad one name over seven QUADPACK routes: finite or infinite
|
|
5
|
+
limits, break points, and the cos/sin/alg/cauchy weights.
|
|
6
|
+
Takes a plain @njit f(x, args).
|
|
7
|
+
nquad integration over n variables; ranges[0] is innermost
|
|
8
|
+
dblquad double integral; each inner limit is a constant or a callback
|
|
9
|
+
tplquad triple integral; same
|
|
10
|
+
|
|
11
|
+
Sampled-data rules (integrate samples, take no callback):
|
|
12
|
+
trapezoid simpson cumulative_simpson cumulative_trapezoid romb
|
|
13
|
+
newton_cotes weights and error coefficient of a rule
|
|
14
|
+
fixed_quad fixed-order Gauss-Legendre; takes a vectorized @njit fn
|
|
15
|
+
|
|
16
|
+
Initial value problems:
|
|
17
|
+
solve_ivp scipy's signature. method='RK45'/'RK23'/'DOP853' reaches
|
|
18
|
+
the pure port, 'LSODA' reaches Fortran. res.y is
|
|
19
|
+
(n_states, n_times), scipy's orientation, and the result
|
|
20
|
+
is a namedtuple, so res.y works and res['y'] does not.
|
|
21
|
+
dense_output=True adds a callable res.sol(t), on every
|
|
22
|
+
method. events=g reports the roots of g(t, y) and, with
|
|
23
|
+
terminal, stops there.
|
|
24
|
+
odeint LSODA over t_eval, auto stiff/nonstiff switching
|
|
25
|
+
|
|
26
|
+
Diagnostics:
|
|
27
|
+
IntegrationWarning what quad warns with when QUADPACK reports a soft
|
|
28
|
+
failure and full_output is off, as scipy does
|
|
29
|
+
|
|
30
|
+
Callbacks are plain @njit functions, everywhere. An integrand is written
|
|
31
|
+
`f(x, args)`, a right-hand side `f(t, y)` or scipy's `f(y, t)`. Parameters
|
|
32
|
+
reach a right-hand side either through a closure or, where the routine
|
|
33
|
+
accepts `args`, through a third parameter, `f(t, y, args)`. Everything is
|
|
34
|
+
passed as an ordinary argument:
|
|
35
|
+
|
|
36
|
+
quad(f, 0.0, np.inf, args) odeint(f, y0, t)
|
|
37
|
+
dblquad(f, a, b, gfun, hfun) solve_ivp(f, (t0, t1), y0, 'LSODA')
|
|
38
|
+
nquad(f, ((0.0, 1.0), (0.0, 1.0)))
|
|
39
|
+
|
|
40
|
+
Where a routine reaches Fortran the @cfunc is built internally, when the
|
|
41
|
+
calling function compiles, so no caller needs to hold a `.address`. Every
|
|
42
|
+
public routine, `solve_ivp(method='LSODA')`, `odeint` and the quad family
|
|
43
|
+
included, takes a plain @njit function and rejects a `.address` or a raw
|
|
44
|
+
integer pointer in the callback slot, from Python and from inside @njit.
|
|
45
|
+
|
|
46
|
+
`nquad`, `dblquad` and `tplquad` take a plain function as the integrand and
|
|
47
|
+
reject an `.address` there too. The coordinates of a multi-dimensional
|
|
48
|
+
integrand cross as separate arguments, which the internal integrand callback
|
|
49
|
+
has no slot for.
|
|
50
|
+
|
|
51
|
+
Every routine in this subpackage is prange-safe. The Fortran packs reach
|
|
52
|
+
their callback through a module variable, and that slot is `!$omp
|
|
53
|
+
threadprivate`, so each thread reads its own copy.
|
|
54
|
+
|
|
55
|
+
Nesting needs nothing from the caller either. Each wrapper saves the slot on
|
|
56
|
+
entry and restores it on exit, so an integration inside another integration
|
|
57
|
+
is correct to any depth. That is what `nquad` is built on: an n-deep nest of
|
|
58
|
+
`quad`, its depth following the length of `ranges`. `dblquad` and `tplquad`
|
|
59
|
+
are `nquad` at depth two and three, which is how scipy implements them too,
|
|
60
|
+
so every QUADPACK route reaches them: infinite limits, break points, the
|
|
61
|
+
weights and `full_output`, per axis.
|
|
62
|
+
|
|
63
|
+
The names above are the whole public surface. Several array-level spellings
|
|
64
|
+
and result types live on in their modules and are reachable there, but a
|
|
65
|
+
caller reaching for one is reaching past the front end that covers it.
|
|
66
|
+
"""
|
|
67
|
+
from ._quadpack import quad, IntegrationWarning
|
|
68
|
+
from ._odeint_scipy import odeint, ODEintWarning, ODEpackError
|
|
69
|
+
from ._quadrature import (simpson, cumulative_simpson, romb, newton_cotes,
|
|
70
|
+
fixed_quad, trapezoid, cumulative_trapezoid)
|
|
71
|
+
from ._nquad import nquad, dblquad, tplquad
|
|
72
|
+
from ._solve_ivp import solve_ivp, OdeSolution
|
|
73
|
+
|
|
74
|
+
# Deliberately NOT exported, and each reachable by its module path.
|
|
75
|
+
#
|
|
76
|
+
# OdeResult, OdeResultDense the two result SHAPES solve_ivp returns,
|
|
77
|
+
# picked by `dense_output`. A caller reads
|
|
78
|
+
# fields off the instance and never names
|
|
79
|
+
# the type; scipy exports neither.
|
|
80
|
+
# LsodaSolution what `res.sol` is on 'LSODA'. A caller
|
|
81
|
+
# invokes `res.sol(t)` and never constructs
|
|
82
|
+
# one. `OdeSolution`, its RK counterpart, IS
|
|
83
|
+
# exported, because scipy publishes that name.
|
|
84
|
+
# rk_dense_eval, the array-level twins of `res.sol`.
|
|
85
|
+
# lsoda_dense_eval
|
|
86
|
+
# METHOD_RK45 .. METHOD_LSODA int codes the private engines route on.
|
|
87
|
+
#
|
|
88
|
+
# `roots_legendre` belongs to `scijit.special`, which mirrors scipy's
|
|
89
|
+
# layout; `_quadrature` keeps it because `fixed_quad` needs it.
|
|
90
|
+
|
|
91
|
+
__all__ = [
|
|
92
|
+
# adaptive quadrature
|
|
93
|
+
'quad', 'dblquad', 'tplquad', 'nquad',
|
|
94
|
+
# initial value problems (scipy's signature)
|
|
95
|
+
'odeint', 'solve_ivp', 'OdeSolution',
|
|
96
|
+
# fixed-sample / fixed-order quadrature
|
|
97
|
+
'trapezoid', 'simpson', 'cumulative_simpson', 'cumulative_trapezoid',
|
|
98
|
+
'romb', 'newton_cotes', 'fixed_quad',
|
|
99
|
+
# warning and error classes a caller may need to name
|
|
100
|
+
'IntegrationWarning', 'ODEintWarning', 'ODEpackError',
|
|
101
|
+
]
|