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.
Files changed (49) hide show
  1. scijit/__init__.py +49 -0
  2. scijit/_lib/__init__.py +7 -0
  3. scijit/_lib/liblapackref.dll +0 -0
  4. scijit/_probe.py +369 -0
  5. scijit/integrate/__init__.py +101 -0
  6. scijit/integrate/_events.py +408 -0
  7. scijit/integrate/_ivp.py +928 -0
  8. scijit/integrate/_nquad.py +1781 -0
  9. scijit/integrate/_odeint_scipy.py +2491 -0
  10. scijit/integrate/_odepack.py +216 -0
  11. scijit/integrate/_quadpack.py +1877 -0
  12. scijit/integrate/_quadrature.py +2028 -0
  13. scijit/integrate/_solve_ivp.py +2297 -0
  14. scijit/integrate/liblsoda.dll +0 -0
  15. scijit/integrate/libquadpack.dll +0 -0
  16. scijit/interpolate/__init__.py +88 -0
  17. scijit/interpolate/_bspline.py +3312 -0
  18. scijit/interpolate/_cubic.py +2211 -0
  19. scijit/interpolate/_interp1d.py +1985 -0
  20. scijit/interpolate/_ndaxis.py +329 -0
  21. scijit/interpolate/_rgi.py +2558 -0
  22. scijit/interpolate/evaluators.py +1466 -0
  23. scijit/interpolate/fitters.py +2542 -0
  24. scijit/interpolate/interpolate.py +6141 -0
  25. scijit/interpolate/libfitpack.dll +0 -0
  26. scijit/optimize/__init__.py +119 -0
  27. scijit/optimize/_assignment.py +421 -0
  28. scijit/optimize/_callback.py +407 -0
  29. scijit/optimize/_global.py +2600 -0
  30. scijit/optimize/_lapack.py +128 -0
  31. scijit/optimize/_lbfgsb.py +1116 -0
  32. scijit/optimize/_lsq.py +2908 -0
  33. scijit/optimize/_minimize.py +2404 -0
  34. scijit/optimize/_minimize_py.py +3136 -0
  35. scijit/optimize/_minpack.py +4320 -0
  36. scijit/optimize/_prima.py +1895 -0
  37. scijit/optimize/_qmc.py +991 -0
  38. scijit/optimize/_scalar.py +5245 -0
  39. scijit/optimize/_slsqp.py +1355 -0
  40. scijit/optimize/liblbfgsb.dll +0 -0
  41. scijit/optimize/libminpack.dll +0 -0
  42. scijit/optimize/liboptlapack.dll +0 -0
  43. scijit/optimize/libprima.dll +0 -0
  44. scijit/optimize/libslsqp.dll +0 -0
  45. scijit-0.1.0.dist-info/METADATA +212 -0
  46. scijit-0.1.0.dist-info/RECORD +49 -0
  47. scijit-0.1.0.dist-info/WHEEL +5 -0
  48. scijit-0.1.0.dist-info/licenses/LICENSE +58 -0
  49. 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'
@@ -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
+ ]