Fixed database typo and removed unnecessary class identifier.
This commit is contained in:
parent
00ad49a143
commit
45fb349a7d
5098 changed files with 952558 additions and 85 deletions
103
venv/Lib/site-packages/scipy/integrate/__init__.py
Normal file
103
venv/Lib/site-packages/scipy/integrate/__init__.py
Normal file
|
@ -0,0 +1,103 @@
|
|||
"""
|
||||
=============================================
|
||||
Integration and ODEs (:mod:`scipy.integrate`)
|
||||
=============================================
|
||||
|
||||
.. currentmodule:: scipy.integrate
|
||||
|
||||
Integrating functions, given function object
|
||||
============================================
|
||||
|
||||
.. autosummary::
|
||||
:toctree: generated/
|
||||
|
||||
quad -- General purpose integration
|
||||
quad_vec -- General purpose integration of vector-valued functions
|
||||
dblquad -- General purpose double integration
|
||||
tplquad -- General purpose triple integration
|
||||
nquad -- General purpose N-D integration
|
||||
fixed_quad -- Integrate func(x) using Gaussian quadrature of order n
|
||||
quadrature -- Integrate with given tolerance using Gaussian quadrature
|
||||
romberg -- Integrate func using Romberg integration
|
||||
quad_explain -- Print information for use of quad
|
||||
newton_cotes -- Weights and error coefficient for Newton-Cotes integration
|
||||
IntegrationWarning -- Warning on issues during integration
|
||||
AccuracyWarning -- Warning on issues during quadrature integration
|
||||
|
||||
Integrating functions, given fixed samples
|
||||
==========================================
|
||||
|
||||
.. autosummary::
|
||||
:toctree: generated/
|
||||
|
||||
trapz -- Use trapezoidal rule to compute integral.
|
||||
cumtrapz -- Use trapezoidal rule to cumulatively compute integral.
|
||||
simps -- Use Simpson's rule to compute integral from samples.
|
||||
romb -- Use Romberg Integration to compute integral from
|
||||
-- (2**k + 1) evenly-spaced samples.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:mod:`scipy.special` for orthogonal polynomials (special) for Gaussian
|
||||
quadrature roots and weights for other weighting factors and regions.
|
||||
|
||||
Solving initial value problems for ODE systems
|
||||
==============================================
|
||||
|
||||
The solvers are implemented as individual classes, which can be used directly
|
||||
(low-level usage) or through a convenience function.
|
||||
|
||||
.. autosummary::
|
||||
:toctree: generated/
|
||||
|
||||
solve_ivp -- Convenient function for ODE integration.
|
||||
RK23 -- Explicit Runge-Kutta solver of order 3(2).
|
||||
RK45 -- Explicit Runge-Kutta solver of order 5(4).
|
||||
DOP853 -- Explicit Runge-Kutta solver of order 8.
|
||||
Radau -- Implicit Runge-Kutta solver of order 5.
|
||||
BDF -- Implicit multi-step variable order (1 to 5) solver.
|
||||
LSODA -- LSODA solver from ODEPACK Fortran package.
|
||||
OdeSolver -- Base class for ODE solvers.
|
||||
DenseOutput -- Local interpolant for computing a dense output.
|
||||
OdeSolution -- Class which represents a continuous ODE solution.
|
||||
|
||||
|
||||
Old API
|
||||
-------
|
||||
|
||||
These are the routines developed earlier for SciPy. They wrap older solvers
|
||||
implemented in Fortran (mostly ODEPACK). While the interface to them is not
|
||||
particularly convenient and certain features are missing compared to the new
|
||||
API, the solvers themselves are of good quality and work fast as compiled
|
||||
Fortran code. In some cases, it might be worth using this old API.
|
||||
|
||||
.. autosummary::
|
||||
:toctree: generated/
|
||||
|
||||
odeint -- General integration of ordinary differential equations.
|
||||
ode -- Integrate ODE using VODE and ZVODE routines.
|
||||
complex_ode -- Convert a complex-valued ODE to real-valued and integrate.
|
||||
|
||||
|
||||
Solving boundary value problems for ODE systems
|
||||
===============================================
|
||||
|
||||
.. autosummary::
|
||||
:toctree: generated/
|
||||
|
||||
solve_bvp -- Solve a boundary value problem for a system of ODEs.
|
||||
"""
|
||||
from ._quadrature import *
|
||||
from .odepack import *
|
||||
from .quadpack import *
|
||||
from ._ode import *
|
||||
from ._bvp import solve_bvp
|
||||
from ._ivp import (solve_ivp, OdeSolution, DenseOutput,
|
||||
OdeSolver, RK23, RK45, DOP853, Radau, BDF, LSODA)
|
||||
from ._quad_vec import quad_vec
|
||||
|
||||
__all__ = [s for s in dir() if not s.startswith('_')]
|
||||
|
||||
from scipy._lib._testutils import PytestTester
|
||||
test = PytestTester(__name__)
|
||||
del PytestTester
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
1159
venv/Lib/site-packages/scipy/integrate/_bvp.py
Normal file
1159
venv/Lib/site-packages/scipy/integrate/_bvp.py
Normal file
File diff suppressed because it is too large
Load diff
BIN
venv/Lib/site-packages/scipy/integrate/_dop.cp36-win32.pyd
Normal file
BIN
venv/Lib/site-packages/scipy/integrate/_dop.cp36-win32.pyd
Normal file
Binary file not shown.
8
venv/Lib/site-packages/scipy/integrate/_ivp/__init__.py
Normal file
8
venv/Lib/site-packages/scipy/integrate/_ivp/__init__.py
Normal file
|
@ -0,0 +1,8 @@
|
|||
"""Suite of ODE solvers implemented in Python."""
|
||||
from .ivp import solve_ivp
|
||||
from .rk import RK23, RK45, DOP853
|
||||
from .radau import Radau
|
||||
from .bdf import BDF
|
||||
from .lsoda import LSODA
|
||||
from .common import OdeSolution
|
||||
from .base import DenseOutput, OdeSolver
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
274
venv/Lib/site-packages/scipy/integrate/_ivp/base.py
Normal file
274
venv/Lib/site-packages/scipy/integrate/_ivp/base.py
Normal file
|
@ -0,0 +1,274 @@
|
|||
import numpy as np
|
||||
|
||||
|
||||
def check_arguments(fun, y0, support_complex):
|
||||
"""Helper function for checking arguments common to all solvers."""
|
||||
y0 = np.asarray(y0)
|
||||
if np.issubdtype(y0.dtype, np.complexfloating):
|
||||
if not support_complex:
|
||||
raise ValueError("`y0` is complex, but the chosen solver does "
|
||||
"not support integration in a complex domain.")
|
||||
dtype = complex
|
||||
else:
|
||||
dtype = float
|
||||
y0 = y0.astype(dtype, copy=False)
|
||||
|
||||
if y0.ndim != 1:
|
||||
raise ValueError("`y0` must be 1-dimensional.")
|
||||
|
||||
def fun_wrapped(t, y):
|
||||
return np.asarray(fun(t, y), dtype=dtype)
|
||||
|
||||
return fun_wrapped, y0
|
||||
|
||||
|
||||
class OdeSolver(object):
|
||||
"""Base class for ODE solvers.
|
||||
|
||||
In order to implement a new solver you need to follow the guidelines:
|
||||
|
||||
1. A constructor must accept parameters presented in the base class
|
||||
(listed below) along with any other parameters specific to a solver.
|
||||
2. A constructor must accept arbitrary extraneous arguments
|
||||
``**extraneous``, but warn that these arguments are irrelevant
|
||||
using `common.warn_extraneous` function. Do not pass these
|
||||
arguments to the base class.
|
||||
3. A solver must implement a private method `_step_impl(self)` which
|
||||
propagates a solver one step further. It must return tuple
|
||||
``(success, message)``, where ``success`` is a boolean indicating
|
||||
whether a step was successful, and ``message`` is a string
|
||||
containing description of a failure if a step failed or None
|
||||
otherwise.
|
||||
4. A solver must implement a private method `_dense_output_impl(self)`,
|
||||
which returns a `DenseOutput` object covering the last successful
|
||||
step.
|
||||
5. A solver must have attributes listed below in Attributes section.
|
||||
Note that ``t_old`` and ``step_size`` are updated automatically.
|
||||
6. Use `fun(self, t, y)` method for the system rhs evaluation, this
|
||||
way the number of function evaluations (`nfev`) will be tracked
|
||||
automatically.
|
||||
7. For convenience, a base class provides `fun_single(self, t, y)` and
|
||||
`fun_vectorized(self, t, y)` for evaluating the rhs in
|
||||
non-vectorized and vectorized fashions respectively (regardless of
|
||||
how `fun` from the constructor is implemented). These calls don't
|
||||
increment `nfev`.
|
||||
8. If a solver uses a Jacobian matrix and LU decompositions, it should
|
||||
track the number of Jacobian evaluations (`njev`) and the number of
|
||||
LU decompositions (`nlu`).
|
||||
9. By convention, the function evaluations used to compute a finite
|
||||
difference approximation of the Jacobian should not be counted in
|
||||
`nfev`, thus use `fun_single(self, t, y)` or
|
||||
`fun_vectorized(self, t, y)` when computing a finite difference
|
||||
approximation of the Jacobian.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
fun : callable
|
||||
Right-hand side of the system. The calling signature is ``fun(t, y)``.
|
||||
Here ``t`` is a scalar and there are two options for ndarray ``y``.
|
||||
It can either have shape (n,), then ``fun`` must return array_like with
|
||||
shape (n,). Or, alternatively, it can have shape (n, n_points), then
|
||||
``fun`` must return array_like with shape (n, n_points) (each column
|
||||
corresponds to a single column in ``y``). The choice between the two
|
||||
options is determined by `vectorized` argument (see below).
|
||||
t0 : float
|
||||
Initial time.
|
||||
y0 : array_like, shape (n,)
|
||||
Initial state.
|
||||
t_bound : float
|
||||
Boundary time --- the integration won't continue beyond it. It also
|
||||
determines the direction of the integration.
|
||||
vectorized : bool
|
||||
Whether `fun` is implemented in a vectorized fashion.
|
||||
support_complex : bool, optional
|
||||
Whether integration in a complex domain should be supported.
|
||||
Generally determined by a derived solver class capabilities.
|
||||
Default is False.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
n : int
|
||||
Number of equations.
|
||||
status : string
|
||||
Current status of the solver: 'running', 'finished' or 'failed'.
|
||||
t_bound : float
|
||||
Boundary time.
|
||||
direction : float
|
||||
Integration direction: +1 or -1.
|
||||
t : float
|
||||
Current time.
|
||||
y : ndarray
|
||||
Current state.
|
||||
t_old : float
|
||||
Previous time. None if no steps were made yet.
|
||||
step_size : float
|
||||
Size of the last successful step. None if no steps were made yet.
|
||||
nfev : int
|
||||
Number of the system's rhs evaluations.
|
||||
njev : int
|
||||
Number of the Jacobian evaluations.
|
||||
nlu : int
|
||||
Number of LU decompositions.
|
||||
"""
|
||||
TOO_SMALL_STEP = "Required step size is less than spacing between numbers."
|
||||
|
||||
def __init__(self, fun, t0, y0, t_bound, vectorized,
|
||||
support_complex=False):
|
||||
self.t_old = None
|
||||
self.t = t0
|
||||
self._fun, self.y = check_arguments(fun, y0, support_complex)
|
||||
self.t_bound = t_bound
|
||||
self.vectorized = vectorized
|
||||
|
||||
if vectorized:
|
||||
def fun_single(t, y):
|
||||
return self._fun(t, y[:, None]).ravel()
|
||||
fun_vectorized = self._fun
|
||||
else:
|
||||
fun_single = self._fun
|
||||
|
||||
def fun_vectorized(t, y):
|
||||
f = np.empty_like(y)
|
||||
for i, yi in enumerate(y.T):
|
||||
f[:, i] = self._fun(t, yi)
|
||||
return f
|
||||
|
||||
def fun(t, y):
|
||||
self.nfev += 1
|
||||
return self.fun_single(t, y)
|
||||
|
||||
self.fun = fun
|
||||
self.fun_single = fun_single
|
||||
self.fun_vectorized = fun_vectorized
|
||||
|
||||
self.direction = np.sign(t_bound - t0) if t_bound != t0 else 1
|
||||
self.n = self.y.size
|
||||
self.status = 'running'
|
||||
|
||||
self.nfev = 0
|
||||
self.njev = 0
|
||||
self.nlu = 0
|
||||
|
||||
@property
|
||||
def step_size(self):
|
||||
if self.t_old is None:
|
||||
return None
|
||||
else:
|
||||
return np.abs(self.t - self.t_old)
|
||||
|
||||
def step(self):
|
||||
"""Perform one integration step.
|
||||
|
||||
Returns
|
||||
-------
|
||||
message : string or None
|
||||
Report from the solver. Typically a reason for a failure if
|
||||
`self.status` is 'failed' after the step was taken or None
|
||||
otherwise.
|
||||
"""
|
||||
if self.status != 'running':
|
||||
raise RuntimeError("Attempt to step on a failed or finished "
|
||||
"solver.")
|
||||
|
||||
if self.n == 0 or self.t == self.t_bound:
|
||||
# Handle corner cases of empty solver or no integration.
|
||||
self.t_old = self.t
|
||||
self.t = self.t_bound
|
||||
message = None
|
||||
self.status = 'finished'
|
||||
else:
|
||||
t = self.t
|
||||
success, message = self._step_impl()
|
||||
|
||||
if not success:
|
||||
self.status = 'failed'
|
||||
else:
|
||||
self.t_old = t
|
||||
if self.direction * (self.t - self.t_bound) >= 0:
|
||||
self.status = 'finished'
|
||||
|
||||
return message
|
||||
|
||||
def dense_output(self):
|
||||
"""Compute a local interpolant over the last successful step.
|
||||
|
||||
Returns
|
||||
-------
|
||||
sol : `DenseOutput`
|
||||
Local interpolant over the last successful step.
|
||||
"""
|
||||
if self.t_old is None:
|
||||
raise RuntimeError("Dense output is available after a successful "
|
||||
"step was made.")
|
||||
|
||||
if self.n == 0 or self.t == self.t_old:
|
||||
# Handle corner cases of empty solver and no integration.
|
||||
return ConstantDenseOutput(self.t_old, self.t, self.y)
|
||||
else:
|
||||
return self._dense_output_impl()
|
||||
|
||||
def _step_impl(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def _dense_output_impl(self):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class DenseOutput(object):
|
||||
"""Base class for local interpolant over step made by an ODE solver.
|
||||
|
||||
It interpolates between `t_min` and `t_max` (see Attributes below).
|
||||
Evaluation outside this interval is not forbidden, but the accuracy is not
|
||||
guaranteed.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
t_min, t_max : float
|
||||
Time range of the interpolation.
|
||||
"""
|
||||
def __init__(self, t_old, t):
|
||||
self.t_old = t_old
|
||||
self.t = t
|
||||
self.t_min = min(t, t_old)
|
||||
self.t_max = max(t, t_old)
|
||||
|
||||
def __call__(self, t):
|
||||
"""Evaluate the interpolant.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
t : float or array_like with shape (n_points,)
|
||||
Points to evaluate the solution at.
|
||||
|
||||
Returns
|
||||
-------
|
||||
y : ndarray, shape (n,) or (n, n_points)
|
||||
Computed values. Shape depends on whether `t` was a scalar or a
|
||||
1-D array.
|
||||
"""
|
||||
t = np.asarray(t)
|
||||
if t.ndim > 1:
|
||||
raise ValueError("`t` must be a float or a 1-D array.")
|
||||
return self._call_impl(t)
|
||||
|
||||
def _call_impl(self, t):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class ConstantDenseOutput(DenseOutput):
|
||||
"""Constant value interpolator.
|
||||
|
||||
This class used for degenerate integration cases: equal integration limits
|
||||
or a system with 0 equations.
|
||||
"""
|
||||
def __init__(self, t_old, t, value):
|
||||
super(ConstantDenseOutput, self).__init__(t_old, t)
|
||||
self.value = value
|
||||
|
||||
def _call_impl(self, t):
|
||||
if t.ndim == 0:
|
||||
return self.value
|
||||
else:
|
||||
ret = np.empty((self.value.shape[0], t.shape[0]))
|
||||
ret[:] = self.value[:, None]
|
||||
return ret
|
466
venv/Lib/site-packages/scipy/integrate/_ivp/bdf.py
Normal file
466
venv/Lib/site-packages/scipy/integrate/_ivp/bdf.py
Normal file
|
@ -0,0 +1,466 @@
|
|||
import numpy as np
|
||||
from scipy.linalg import lu_factor, lu_solve
|
||||
from scipy.sparse import issparse, csc_matrix, eye
|
||||
from scipy.sparse.linalg import splu
|
||||
from scipy.optimize._numdiff import group_columns
|
||||
from .common import (validate_max_step, validate_tol, select_initial_step,
|
||||
norm, EPS, num_jac, validate_first_step,
|
||||
warn_extraneous)
|
||||
from .base import OdeSolver, DenseOutput
|
||||
|
||||
|
||||
MAX_ORDER = 5
|
||||
NEWTON_MAXITER = 4
|
||||
MIN_FACTOR = 0.2
|
||||
MAX_FACTOR = 10
|
||||
|
||||
|
||||
def compute_R(order, factor):
|
||||
"""Compute the matrix for changing the differences array."""
|
||||
I = np.arange(1, order + 1)[:, None]
|
||||
J = np.arange(1, order + 1)
|
||||
M = np.zeros((order + 1, order + 1))
|
||||
M[1:, 1:] = (I - 1 - factor * J) / I
|
||||
M[0] = 1
|
||||
return np.cumprod(M, axis=0)
|
||||
|
||||
|
||||
def change_D(D, order, factor):
|
||||
"""Change differences array in-place when step size is changed."""
|
||||
R = compute_R(order, factor)
|
||||
U = compute_R(order, 1)
|
||||
RU = R.dot(U)
|
||||
D[:order + 1] = np.dot(RU.T, D[:order + 1])
|
||||
|
||||
|
||||
def solve_bdf_system(fun, t_new, y_predict, c, psi, LU, solve_lu, scale, tol):
|
||||
"""Solve the algebraic system resulting from BDF method."""
|
||||
d = 0
|
||||
y = y_predict.copy()
|
||||
dy_norm_old = None
|
||||
converged = False
|
||||
for k in range(NEWTON_MAXITER):
|
||||
f = fun(t_new, y)
|
||||
if not np.all(np.isfinite(f)):
|
||||
break
|
||||
|
||||
dy = solve_lu(LU, c * f - psi - d)
|
||||
dy_norm = norm(dy / scale)
|
||||
|
||||
if dy_norm_old is None:
|
||||
rate = None
|
||||
else:
|
||||
rate = dy_norm / dy_norm_old
|
||||
|
||||
if (rate is not None and (rate >= 1 or
|
||||
rate ** (NEWTON_MAXITER - k) / (1 - rate) * dy_norm > tol)):
|
||||
break
|
||||
|
||||
y += dy
|
||||
d += dy
|
||||
|
||||
if (dy_norm == 0 or
|
||||
rate is not None and rate / (1 - rate) * dy_norm < tol):
|
||||
converged = True
|
||||
break
|
||||
|
||||
dy_norm_old = dy_norm
|
||||
|
||||
return converged, k + 1, y, d
|
||||
|
||||
|
||||
class BDF(OdeSolver):
|
||||
"""Implicit method based on backward-differentiation formulas.
|
||||
|
||||
This is a variable order method with the order varying automatically from
|
||||
1 to 5. The general framework of the BDF algorithm is described in [1]_.
|
||||
This class implements a quasi-constant step size as explained in [2]_.
|
||||
The error estimation strategy for the constant-step BDF is derived in [3]_.
|
||||
An accuracy enhancement using modified formulas (NDF) [2]_ is also implemented.
|
||||
|
||||
Can be applied in the complex domain.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
fun : callable
|
||||
Right-hand side of the system. The calling signature is ``fun(t, y)``.
|
||||
Here ``t`` is a scalar, and there are two options for the ndarray ``y``:
|
||||
It can either have shape (n,); then ``fun`` must return array_like with
|
||||
shape (n,). Alternatively it can have shape (n, k); then ``fun``
|
||||
must return an array_like with shape (n, k), i.e. each column
|
||||
corresponds to a single column in ``y``. The choice between the two
|
||||
options is determined by `vectorized` argument (see below). The
|
||||
vectorized implementation allows a faster approximation of the Jacobian
|
||||
by finite differences (required for this solver).
|
||||
t0 : float
|
||||
Initial time.
|
||||
y0 : array_like, shape (n,)
|
||||
Initial state.
|
||||
t_bound : float
|
||||
Boundary time - the integration won't continue beyond it. It also
|
||||
determines the direction of the integration.
|
||||
first_step : float or None, optional
|
||||
Initial step size. Default is ``None`` which means that the algorithm
|
||||
should choose.
|
||||
max_step : float, optional
|
||||
Maximum allowed step size. Default is np.inf, i.e., the step size is not
|
||||
bounded and determined solely by the solver.
|
||||
rtol, atol : float and array_like, optional
|
||||
Relative and absolute tolerances. The solver keeps the local error
|
||||
estimates less than ``atol + rtol * abs(y)``. Here `rtol` controls a
|
||||
relative accuracy (number of correct digits). But if a component of `y`
|
||||
is approximately below `atol`, the error only needs to fall within
|
||||
the same `atol` threshold, and the number of correct digits is not
|
||||
guaranteed. If components of y have different scales, it might be
|
||||
beneficial to set different `atol` values for different components by
|
||||
passing array_like with shape (n,) for `atol`. Default values are
|
||||
1e-3 for `rtol` and 1e-6 for `atol`.
|
||||
jac : {None, array_like, sparse_matrix, callable}, optional
|
||||
Jacobian matrix of the right-hand side of the system with respect to y,
|
||||
required by this method. The Jacobian matrix has shape (n, n) and its
|
||||
element (i, j) is equal to ``d f_i / d y_j``.
|
||||
There are three ways to define the Jacobian:
|
||||
|
||||
* If array_like or sparse_matrix, the Jacobian is assumed to
|
||||
be constant.
|
||||
* If callable, the Jacobian is assumed to depend on both
|
||||
t and y; it will be called as ``jac(t, y)`` as necessary.
|
||||
For the 'Radau' and 'BDF' methods, the return value might be a
|
||||
sparse matrix.
|
||||
* If None (default), the Jacobian will be approximated by
|
||||
finite differences.
|
||||
|
||||
It is generally recommended to provide the Jacobian rather than
|
||||
relying on a finite-difference approximation.
|
||||
jac_sparsity : {None, array_like, sparse matrix}, optional
|
||||
Defines a sparsity structure of the Jacobian matrix for a
|
||||
finite-difference approximation. Its shape must be (n, n). This argument
|
||||
is ignored if `jac` is not `None`. If the Jacobian has only few non-zero
|
||||
elements in *each* row, providing the sparsity structure will greatly
|
||||
speed up the computations [4]_. A zero entry means that a corresponding
|
||||
element in the Jacobian is always zero. If None (default), the Jacobian
|
||||
is assumed to be dense.
|
||||
vectorized : bool, optional
|
||||
Whether `fun` is implemented in a vectorized fashion. Default is False.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
n : int
|
||||
Number of equations.
|
||||
status : string
|
||||
Current status of the solver: 'running', 'finished' or 'failed'.
|
||||
t_bound : float
|
||||
Boundary time.
|
||||
direction : float
|
||||
Integration direction: +1 or -1.
|
||||
t : float
|
||||
Current time.
|
||||
y : ndarray
|
||||
Current state.
|
||||
t_old : float
|
||||
Previous time. None if no steps were made yet.
|
||||
step_size : float
|
||||
Size of the last successful step. None if no steps were made yet.
|
||||
nfev : int
|
||||
Number of evaluations of the right-hand side.
|
||||
njev : int
|
||||
Number of evaluations of the Jacobian.
|
||||
nlu : int
|
||||
Number of LU decompositions.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] G. D. Byrne, A. C. Hindmarsh, "A Polyalgorithm for the Numerical
|
||||
Solution of Ordinary Differential Equations", ACM Transactions on
|
||||
Mathematical Software, Vol. 1, No. 1, pp. 71-96, March 1975.
|
||||
.. [2] L. F. Shampine, M. W. Reichelt, "THE MATLAB ODE SUITE", SIAM J. SCI.
|
||||
COMPUTE., Vol. 18, No. 1, pp. 1-22, January 1997.
|
||||
.. [3] E. Hairer, G. Wanner, "Solving Ordinary Differential Equations I:
|
||||
Nonstiff Problems", Sec. III.2.
|
||||
.. [4] A. Curtis, M. J. D. Powell, and J. Reid, "On the estimation of
|
||||
sparse Jacobian matrices", Journal of the Institute of Mathematics
|
||||
and its Applications, 13, pp. 117-120, 1974.
|
||||
"""
|
||||
def __init__(self, fun, t0, y0, t_bound, max_step=np.inf,
|
||||
rtol=1e-3, atol=1e-6, jac=None, jac_sparsity=None,
|
||||
vectorized=False, first_step=None, **extraneous):
|
||||
warn_extraneous(extraneous)
|
||||
super(BDF, self).__init__(fun, t0, y0, t_bound, vectorized,
|
||||
support_complex=True)
|
||||
self.max_step = validate_max_step(max_step)
|
||||
self.rtol, self.atol = validate_tol(rtol, atol, self.n)
|
||||
f = self.fun(self.t, self.y)
|
||||
if first_step is None:
|
||||
self.h_abs = select_initial_step(self.fun, self.t, self.y, f,
|
||||
self.direction, 1,
|
||||
self.rtol, self.atol)
|
||||
else:
|
||||
self.h_abs = validate_first_step(first_step, t0, t_bound)
|
||||
self.h_abs_old = None
|
||||
self.error_norm_old = None
|
||||
|
||||
self.newton_tol = max(10 * EPS / rtol, min(0.03, rtol ** 0.5))
|
||||
|
||||
self.jac_factor = None
|
||||
self.jac, self.J = self._validate_jac(jac, jac_sparsity)
|
||||
if issparse(self.J):
|
||||
def lu(A):
|
||||
self.nlu += 1
|
||||
return splu(A)
|
||||
|
||||
def solve_lu(LU, b):
|
||||
return LU.solve(b)
|
||||
|
||||
I = eye(self.n, format='csc', dtype=self.y.dtype)
|
||||
else:
|
||||
def lu(A):
|
||||
self.nlu += 1
|
||||
return lu_factor(A, overwrite_a=True)
|
||||
|
||||
def solve_lu(LU, b):
|
||||
return lu_solve(LU, b, overwrite_b=True)
|
||||
|
||||
I = np.identity(self.n, dtype=self.y.dtype)
|
||||
|
||||
self.lu = lu
|
||||
self.solve_lu = solve_lu
|
||||
self.I = I
|
||||
|
||||
kappa = np.array([0, -0.1850, -1/9, -0.0823, -0.0415, 0])
|
||||
self.gamma = np.hstack((0, np.cumsum(1 / np.arange(1, MAX_ORDER + 1))))
|
||||
self.alpha = (1 - kappa) * self.gamma
|
||||
self.error_const = kappa * self.gamma + 1 / np.arange(1, MAX_ORDER + 2)
|
||||
|
||||
D = np.empty((MAX_ORDER + 3, self.n), dtype=self.y.dtype)
|
||||
D[0] = self.y
|
||||
D[1] = f * self.h_abs * self.direction
|
||||
self.D = D
|
||||
|
||||
self.order = 1
|
||||
self.n_equal_steps = 0
|
||||
self.LU = None
|
||||
|
||||
def _validate_jac(self, jac, sparsity):
|
||||
t0 = self.t
|
||||
y0 = self.y
|
||||
|
||||
if jac is None:
|
||||
if sparsity is not None:
|
||||
if issparse(sparsity):
|
||||
sparsity = csc_matrix(sparsity)
|
||||
groups = group_columns(sparsity)
|
||||
sparsity = (sparsity, groups)
|
||||
|
||||
def jac_wrapped(t, y):
|
||||
self.njev += 1
|
||||
f = self.fun_single(t, y)
|
||||
J, self.jac_factor = num_jac(self.fun_vectorized, t, y, f,
|
||||
self.atol, self.jac_factor,
|
||||
sparsity)
|
||||
return J
|
||||
J = jac_wrapped(t0, y0)
|
||||
elif callable(jac):
|
||||
J = jac(t0, y0)
|
||||
self.njev += 1
|
||||
if issparse(J):
|
||||
J = csc_matrix(J, dtype=y0.dtype)
|
||||
|
||||
def jac_wrapped(t, y):
|
||||
self.njev += 1
|
||||
return csc_matrix(jac(t, y), dtype=y0.dtype)
|
||||
else:
|
||||
J = np.asarray(J, dtype=y0.dtype)
|
||||
|
||||
def jac_wrapped(t, y):
|
||||
self.njev += 1
|
||||
return np.asarray(jac(t, y), dtype=y0.dtype)
|
||||
|
||||
if J.shape != (self.n, self.n):
|
||||
raise ValueError("`jac` is expected to have shape {}, but "
|
||||
"actually has {}."
|
||||
.format((self.n, self.n), J.shape))
|
||||
else:
|
||||
if issparse(jac):
|
||||
J = csc_matrix(jac, dtype=y0.dtype)
|
||||
else:
|
||||
J = np.asarray(jac, dtype=y0.dtype)
|
||||
|
||||
if J.shape != (self.n, self.n):
|
||||
raise ValueError("`jac` is expected to have shape {}, but "
|
||||
"actually has {}."
|
||||
.format((self.n, self.n), J.shape))
|
||||
jac_wrapped = None
|
||||
|
||||
return jac_wrapped, J
|
||||
|
||||
def _step_impl(self):
|
||||
t = self.t
|
||||
D = self.D
|
||||
|
||||
max_step = self.max_step
|
||||
min_step = 10 * np.abs(np.nextafter(t, self.direction * np.inf) - t)
|
||||
if self.h_abs > max_step:
|
||||
h_abs = max_step
|
||||
change_D(D, self.order, max_step / self.h_abs)
|
||||
self.n_equal_steps = 0
|
||||
elif self.h_abs < min_step:
|
||||
h_abs = min_step
|
||||
change_D(D, self.order, min_step / self.h_abs)
|
||||
self.n_equal_steps = 0
|
||||
else:
|
||||
h_abs = self.h_abs
|
||||
|
||||
atol = self.atol
|
||||
rtol = self.rtol
|
||||
order = self.order
|
||||
|
||||
alpha = self.alpha
|
||||
gamma = self.gamma
|
||||
error_const = self.error_const
|
||||
|
||||
J = self.J
|
||||
LU = self.LU
|
||||
current_jac = self.jac is None
|
||||
|
||||
step_accepted = False
|
||||
while not step_accepted:
|
||||
if h_abs < min_step:
|
||||
return False, self.TOO_SMALL_STEP
|
||||
|
||||
h = h_abs * self.direction
|
||||
t_new = t + h
|
||||
|
||||
if self.direction * (t_new - self.t_bound) > 0:
|
||||
t_new = self.t_bound
|
||||
change_D(D, order, np.abs(t_new - t) / h_abs)
|
||||
self.n_equal_steps = 0
|
||||
LU = None
|
||||
|
||||
h = t_new - t
|
||||
h_abs = np.abs(h)
|
||||
|
||||
y_predict = np.sum(D[:order + 1], axis=0)
|
||||
|
||||
scale = atol + rtol * np.abs(y_predict)
|
||||
psi = np.dot(D[1: order + 1].T, gamma[1: order + 1]) / alpha[order]
|
||||
|
||||
converged = False
|
||||
c = h / alpha[order]
|
||||
while not converged:
|
||||
if LU is None:
|
||||
LU = self.lu(self.I - c * J)
|
||||
|
||||
converged, n_iter, y_new, d = solve_bdf_system(
|
||||
self.fun, t_new, y_predict, c, psi, LU, self.solve_lu,
|
||||
scale, self.newton_tol)
|
||||
|
||||
if not converged:
|
||||
if current_jac:
|
||||
break
|
||||
J = self.jac(t_new, y_predict)
|
||||
LU = None
|
||||
current_jac = True
|
||||
|
||||
if not converged:
|
||||
factor = 0.5
|
||||
h_abs *= factor
|
||||
change_D(D, order, factor)
|
||||
self.n_equal_steps = 0
|
||||
LU = None
|
||||
continue
|
||||
|
||||
safety = 0.9 * (2 * NEWTON_MAXITER + 1) / (2 * NEWTON_MAXITER
|
||||
+ n_iter)
|
||||
|
||||
scale = atol + rtol * np.abs(y_new)
|
||||
error = error_const[order] * d
|
||||
error_norm = norm(error / scale)
|
||||
|
||||
if error_norm > 1:
|
||||
factor = max(MIN_FACTOR,
|
||||
safety * error_norm ** (-1 / (order + 1)))
|
||||
h_abs *= factor
|
||||
change_D(D, order, factor)
|
||||
self.n_equal_steps = 0
|
||||
# As we didn't have problems with convergence, we don't
|
||||
# reset LU here.
|
||||
else:
|
||||
step_accepted = True
|
||||
|
||||
self.n_equal_steps += 1
|
||||
|
||||
self.t = t_new
|
||||
self.y = y_new
|
||||
|
||||
self.h_abs = h_abs
|
||||
self.J = J
|
||||
self.LU = LU
|
||||
|
||||
# Update differences. The principal relation here is
|
||||
# D^{j + 1} y_n = D^{j} y_n - D^{j} y_{n - 1}. Keep in mind that D
|
||||
# contained difference for previous interpolating polynomial and
|
||||
# d = D^{k + 1} y_n. Thus this elegant code follows.
|
||||
D[order + 2] = d - D[order + 1]
|
||||
D[order + 1] = d
|
||||
for i in reversed(range(order + 1)):
|
||||
D[i] += D[i + 1]
|
||||
|
||||
if self.n_equal_steps < order + 1:
|
||||
return True, None
|
||||
|
||||
if order > 1:
|
||||
error_m = error_const[order - 1] * D[order]
|
||||
error_m_norm = norm(error_m / scale)
|
||||
else:
|
||||
error_m_norm = np.inf
|
||||
|
||||
if order < MAX_ORDER:
|
||||
error_p = error_const[order + 1] * D[order + 2]
|
||||
error_p_norm = norm(error_p / scale)
|
||||
else:
|
||||
error_p_norm = np.inf
|
||||
|
||||
error_norms = np.array([error_m_norm, error_norm, error_p_norm])
|
||||
with np.errstate(divide='ignore'):
|
||||
factors = error_norms ** (-1 / np.arange(order, order + 3))
|
||||
|
||||
delta_order = np.argmax(factors) - 1
|
||||
order += delta_order
|
||||
self.order = order
|
||||
|
||||
factor = min(MAX_FACTOR, safety * np.max(factors))
|
||||
self.h_abs *= factor
|
||||
change_D(D, order, factor)
|
||||
self.n_equal_steps = 0
|
||||
self.LU = None
|
||||
|
||||
return True, None
|
||||
|
||||
def _dense_output_impl(self):
|
||||
return BdfDenseOutput(self.t_old, self.t, self.h_abs * self.direction,
|
||||
self.order, self.D[:self.order + 1].copy())
|
||||
|
||||
|
||||
class BdfDenseOutput(DenseOutput):
|
||||
def __init__(self, t_old, t, h, order, D):
|
||||
super(BdfDenseOutput, self).__init__(t_old, t)
|
||||
self.order = order
|
||||
self.t_shift = self.t - h * np.arange(self.order)
|
||||
self.denom = h * (1 + np.arange(self.order))
|
||||
self.D = D
|
||||
|
||||
def _call_impl(self, t):
|
||||
if t.ndim == 0:
|
||||
x = (t - self.t_shift) / self.denom
|
||||
p = np.cumprod(x)
|
||||
else:
|
||||
x = (t - self.t_shift[:, None]) / self.denom[:, None]
|
||||
p = np.cumprod(x, axis=0)
|
||||
|
||||
y = np.dot(self.D[1:].T, p)
|
||||
if y.ndim == 1:
|
||||
y += self.D[0]
|
||||
else:
|
||||
y += self.D[0, :, None]
|
||||
|
||||
return y
|
431
venv/Lib/site-packages/scipy/integrate/_ivp/common.py
Normal file
431
venv/Lib/site-packages/scipy/integrate/_ivp/common.py
Normal file
|
@ -0,0 +1,431 @@
|
|||
from itertools import groupby
|
||||
from warnings import warn
|
||||
import numpy as np
|
||||
from scipy.sparse import find, coo_matrix
|
||||
|
||||
|
||||
EPS = np.finfo(float).eps
|
||||
|
||||
|
||||
def validate_first_step(first_step, t0, t_bound):
|
||||
"""Assert that first_step is valid and return it."""
|
||||
if first_step <= 0:
|
||||
raise ValueError("`first_step` must be positive.")
|
||||
if first_step > np.abs(t_bound - t0):
|
||||
raise ValueError("`first_step` exceeds bounds.")
|
||||
return first_step
|
||||
|
||||
|
||||
def validate_max_step(max_step):
|
||||
"""Assert that max_Step is valid and return it."""
|
||||
if max_step <= 0:
|
||||
raise ValueError("`max_step` must be positive.")
|
||||
return max_step
|
||||
|
||||
|
||||
def warn_extraneous(extraneous):
|
||||
"""Display a warning for extraneous keyword arguments.
|
||||
|
||||
The initializer of each solver class is expected to collect keyword
|
||||
arguments that it doesn't understand and warn about them. This function
|
||||
prints a warning for each key in the supplied dictionary.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
extraneous : dict
|
||||
Extraneous keyword arguments
|
||||
"""
|
||||
if extraneous:
|
||||
warn("The following arguments have no effect for a chosen solver: {}."
|
||||
.format(", ".join("`{}`".format(x) for x in extraneous)))
|
||||
|
||||
|
||||
def validate_tol(rtol, atol, n):
|
||||
"""Validate tolerance values."""
|
||||
if rtol < 100 * EPS:
|
||||
warn("`rtol` is too low, setting to {}".format(100 * EPS))
|
||||
rtol = 100 * EPS
|
||||
|
||||
atol = np.asarray(atol)
|
||||
if atol.ndim > 0 and atol.shape != (n,):
|
||||
raise ValueError("`atol` has wrong shape.")
|
||||
|
||||
if np.any(atol < 0):
|
||||
raise ValueError("`atol` must be positive.")
|
||||
|
||||
return rtol, atol
|
||||
|
||||
|
||||
def norm(x):
|
||||
"""Compute RMS norm."""
|
||||
return np.linalg.norm(x) / x.size ** 0.5
|
||||
|
||||
|
||||
def select_initial_step(fun, t0, y0, f0, direction, order, rtol, atol):
|
||||
"""Empirically select a good initial step.
|
||||
|
||||
The algorithm is described in [1]_.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
fun : callable
|
||||
Right-hand side of the system.
|
||||
t0 : float
|
||||
Initial value of the independent variable.
|
||||
y0 : ndarray, shape (n,)
|
||||
Initial value of the dependent variable.
|
||||
f0 : ndarray, shape (n,)
|
||||
Initial value of the derivative, i.e., ``fun(t0, y0)``.
|
||||
direction : float
|
||||
Integration direction.
|
||||
order : float
|
||||
Error estimator order. It means that the error controlled by the
|
||||
algorithm is proportional to ``step_size ** (order + 1)`.
|
||||
rtol : float
|
||||
Desired relative tolerance.
|
||||
atol : float
|
||||
Desired absolute tolerance.
|
||||
|
||||
Returns
|
||||
-------
|
||||
h_abs : float
|
||||
Absolute value of the suggested initial step.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] E. Hairer, S. P. Norsett G. Wanner, "Solving Ordinary Differential
|
||||
Equations I: Nonstiff Problems", Sec. II.4.
|
||||
"""
|
||||
if y0.size == 0:
|
||||
return np.inf
|
||||
|
||||
scale = atol + np.abs(y0) * rtol
|
||||
d0 = norm(y0 / scale)
|
||||
d1 = norm(f0 / scale)
|
||||
if d0 < 1e-5 or d1 < 1e-5:
|
||||
h0 = 1e-6
|
||||
else:
|
||||
h0 = 0.01 * d0 / d1
|
||||
|
||||
y1 = y0 + h0 * direction * f0
|
||||
f1 = fun(t0 + h0 * direction, y1)
|
||||
d2 = norm((f1 - f0) / scale) / h0
|
||||
|
||||
if d1 <= 1e-15 and d2 <= 1e-15:
|
||||
h1 = max(1e-6, h0 * 1e-3)
|
||||
else:
|
||||
h1 = (0.01 / max(d1, d2)) ** (1 / (order + 1))
|
||||
|
||||
return min(100 * h0, h1)
|
||||
|
||||
|
||||
class OdeSolution(object):
|
||||
"""Continuous ODE solution.
|
||||
|
||||
It is organized as a collection of `DenseOutput` objects which represent
|
||||
local interpolants. It provides an algorithm to select a right interpolant
|
||||
for each given point.
|
||||
|
||||
The interpolants cover the range between `t_min` and `t_max` (see
|
||||
Attributes below). Evaluation outside this interval is not forbidden, but
|
||||
the accuracy is not guaranteed.
|
||||
|
||||
When evaluating at a breakpoint (one of the values in `ts`) a segment with
|
||||
the lower index is selected.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
ts : array_like, shape (n_segments + 1,)
|
||||
Time instants between which local interpolants are defined. Must
|
||||
be strictly increasing or decreasing (zero segment with two points is
|
||||
also allowed).
|
||||
interpolants : list of DenseOutput with n_segments elements
|
||||
Local interpolants. An i-th interpolant is assumed to be defined
|
||||
between ``ts[i]`` and ``ts[i + 1]``.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
t_min, t_max : float
|
||||
Time range of the interpolation.
|
||||
"""
|
||||
def __init__(self, ts, interpolants):
|
||||
ts = np.asarray(ts)
|
||||
d = np.diff(ts)
|
||||
# The first case covers integration on zero segment.
|
||||
if not ((ts.size == 2 and ts[0] == ts[-1])
|
||||
or np.all(d > 0) or np.all(d < 0)):
|
||||
raise ValueError("`ts` must be strictly increasing or decreasing.")
|
||||
|
||||
self.n_segments = len(interpolants)
|
||||
if ts.shape != (self.n_segments + 1,):
|
||||
raise ValueError("Numbers of time stamps and interpolants "
|
||||
"don't match.")
|
||||
|
||||
self.ts = ts
|
||||
self.interpolants = interpolants
|
||||
if ts[-1] >= ts[0]:
|
||||
self.t_min = ts[0]
|
||||
self.t_max = ts[-1]
|
||||
self.ascending = True
|
||||
self.ts_sorted = ts
|
||||
else:
|
||||
self.t_min = ts[-1]
|
||||
self.t_max = ts[0]
|
||||
self.ascending = False
|
||||
self.ts_sorted = ts[::-1]
|
||||
|
||||
def _call_single(self, t):
|
||||
# Here we preserve a certain symmetry that when t is in self.ts,
|
||||
# then we prioritize a segment with a lower index.
|
||||
if self.ascending:
|
||||
ind = np.searchsorted(self.ts_sorted, t, side='left')
|
||||
else:
|
||||
ind = np.searchsorted(self.ts_sorted, t, side='right')
|
||||
|
||||
segment = min(max(ind - 1, 0), self.n_segments - 1)
|
||||
if not self.ascending:
|
||||
segment = self.n_segments - 1 - segment
|
||||
|
||||
return self.interpolants[segment](t)
|
||||
|
||||
def __call__(self, t):
|
||||
"""Evaluate the solution.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
t : float or array_like with shape (n_points,)
|
||||
Points to evaluate at.
|
||||
|
||||
Returns
|
||||
-------
|
||||
y : ndarray, shape (n_states,) or (n_states, n_points)
|
||||
Computed values. Shape depends on whether `t` is a scalar or a
|
||||
1-D array.
|
||||
"""
|
||||
t = np.asarray(t)
|
||||
|
||||
if t.ndim == 0:
|
||||
return self._call_single(t)
|
||||
|
||||
order = np.argsort(t)
|
||||
reverse = np.empty_like(order)
|
||||
reverse[order] = np.arange(order.shape[0])
|
||||
t_sorted = t[order]
|
||||
|
||||
# See comment in self._call_single.
|
||||
if self.ascending:
|
||||
segments = np.searchsorted(self.ts_sorted, t_sorted, side='left')
|
||||
else:
|
||||
segments = np.searchsorted(self.ts_sorted, t_sorted, side='right')
|
||||
segments -= 1
|
||||
segments[segments < 0] = 0
|
||||
segments[segments > self.n_segments - 1] = self.n_segments - 1
|
||||
if not self.ascending:
|
||||
segments = self.n_segments - 1 - segments
|
||||
|
||||
ys = []
|
||||
group_start = 0
|
||||
for segment, group in groupby(segments):
|
||||
group_end = group_start + len(list(group))
|
||||
y = self.interpolants[segment](t_sorted[group_start:group_end])
|
||||
ys.append(y)
|
||||
group_start = group_end
|
||||
|
||||
ys = np.hstack(ys)
|
||||
ys = ys[:, reverse]
|
||||
|
||||
return ys
|
||||
|
||||
|
||||
NUM_JAC_DIFF_REJECT = EPS ** 0.875
|
||||
NUM_JAC_DIFF_SMALL = EPS ** 0.75
|
||||
NUM_JAC_DIFF_BIG = EPS ** 0.25
|
||||
NUM_JAC_MIN_FACTOR = 1e3 * EPS
|
||||
NUM_JAC_FACTOR_INCREASE = 10
|
||||
NUM_JAC_FACTOR_DECREASE = 0.1
|
||||
|
||||
|
||||
def num_jac(fun, t, y, f, threshold, factor, sparsity=None):
|
||||
"""Finite differences Jacobian approximation tailored for ODE solvers.
|
||||
|
||||
This function computes finite difference approximation to the Jacobian
|
||||
matrix of `fun` with respect to `y` using forward differences.
|
||||
The Jacobian matrix has shape (n, n) and its element (i, j) is equal to
|
||||
``d f_i / d y_j``.
|
||||
|
||||
A special feature of this function is the ability to correct the step
|
||||
size from iteration to iteration. The main idea is to keep the finite
|
||||
difference significantly separated from its round-off error which
|
||||
approximately equals ``EPS * np.abs(f)``. It reduces a possibility of a
|
||||
huge error and assures that the estimated derivative are reasonably close
|
||||
to the true values (i.e., the finite difference approximation is at least
|
||||
qualitatively reflects the structure of the true Jacobian).
|
||||
|
||||
Parameters
|
||||
----------
|
||||
fun : callable
|
||||
Right-hand side of the system implemented in a vectorized fashion.
|
||||
t : float
|
||||
Current time.
|
||||
y : ndarray, shape (n,)
|
||||
Current state.
|
||||
f : ndarray, shape (n,)
|
||||
Value of the right hand side at (t, y).
|
||||
threshold : float
|
||||
Threshold for `y` value used for computing the step size as
|
||||
``factor * np.maximum(np.abs(y), threshold)``. Typically, the value of
|
||||
absolute tolerance (atol) for a solver should be passed as `threshold`.
|
||||
factor : ndarray with shape (n,) or None
|
||||
Factor to use for computing the step size. Pass None for the very
|
||||
evaluation, then use the value returned from this function.
|
||||
sparsity : tuple (structure, groups) or None
|
||||
Sparsity structure of the Jacobian, `structure` must be csc_matrix.
|
||||
|
||||
Returns
|
||||
-------
|
||||
J : ndarray or csc_matrix, shape (n, n)
|
||||
Jacobian matrix.
|
||||
factor : ndarray, shape (n,)
|
||||
Suggested `factor` for the next evaluation.
|
||||
"""
|
||||
y = np.asarray(y)
|
||||
n = y.shape[0]
|
||||
if n == 0:
|
||||
return np.empty((0, 0)), factor
|
||||
|
||||
if factor is None:
|
||||
factor = np.full(n, EPS ** 0.5)
|
||||
else:
|
||||
factor = factor.copy()
|
||||
|
||||
# Direct the step as ODE dictates, hoping that such a step won't lead to
|
||||
# a problematic region. For complex ODEs it makes sense to use the real
|
||||
# part of f as we use steps along real axis.
|
||||
f_sign = 2 * (np.real(f) >= 0).astype(float) - 1
|
||||
y_scale = f_sign * np.maximum(threshold, np.abs(y))
|
||||
h = (y + factor * y_scale) - y
|
||||
|
||||
# Make sure that the step is not 0 to start with. Not likely it will be
|
||||
# executed often.
|
||||
for i in np.nonzero(h == 0)[0]:
|
||||
while h[i] == 0:
|
||||
factor[i] *= 10
|
||||
h[i] = (y[i] + factor[i] * y_scale[i]) - y[i]
|
||||
|
||||
if sparsity is None:
|
||||
return _dense_num_jac(fun, t, y, f, h, factor, y_scale)
|
||||
else:
|
||||
structure, groups = sparsity
|
||||
return _sparse_num_jac(fun, t, y, f, h, factor, y_scale,
|
||||
structure, groups)
|
||||
|
||||
|
||||
def _dense_num_jac(fun, t, y, f, h, factor, y_scale):
|
||||
n = y.shape[0]
|
||||
h_vecs = np.diag(h)
|
||||
f_new = fun(t, y[:, None] + h_vecs)
|
||||
diff = f_new - f[:, None]
|
||||
max_ind = np.argmax(np.abs(diff), axis=0)
|
||||
r = np.arange(n)
|
||||
max_diff = np.abs(diff[max_ind, r])
|
||||
scale = np.maximum(np.abs(f[max_ind]), np.abs(f_new[max_ind, r]))
|
||||
|
||||
diff_too_small = max_diff < NUM_JAC_DIFF_REJECT * scale
|
||||
if np.any(diff_too_small):
|
||||
ind, = np.nonzero(diff_too_small)
|
||||
new_factor = NUM_JAC_FACTOR_INCREASE * factor[ind]
|
||||
h_new = (y[ind] + new_factor * y_scale[ind]) - y[ind]
|
||||
h_vecs[ind, ind] = h_new
|
||||
f_new = fun(t, y[:, None] + h_vecs[:, ind])
|
||||
diff_new = f_new - f[:, None]
|
||||
max_ind = np.argmax(np.abs(diff_new), axis=0)
|
||||
r = np.arange(ind.shape[0])
|
||||
max_diff_new = np.abs(diff_new[max_ind, r])
|
||||
scale_new = np.maximum(np.abs(f[max_ind]), np.abs(f_new[max_ind, r]))
|
||||
|
||||
update = max_diff[ind] * scale_new < max_diff_new * scale[ind]
|
||||
if np.any(update):
|
||||
update, = np.nonzero(update)
|
||||
update_ind = ind[update]
|
||||
factor[update_ind] = new_factor[update]
|
||||
h[update_ind] = h_new[update]
|
||||
diff[:, update_ind] = diff_new[:, update]
|
||||
scale[update_ind] = scale_new[update]
|
||||
max_diff[update_ind] = max_diff_new[update]
|
||||
|
||||
diff /= h
|
||||
|
||||
factor[max_diff < NUM_JAC_DIFF_SMALL * scale] *= NUM_JAC_FACTOR_INCREASE
|
||||
factor[max_diff > NUM_JAC_DIFF_BIG * scale] *= NUM_JAC_FACTOR_DECREASE
|
||||
factor = np.maximum(factor, NUM_JAC_MIN_FACTOR)
|
||||
|
||||
return diff, factor
|
||||
|
||||
|
||||
def _sparse_num_jac(fun, t, y, f, h, factor, y_scale, structure, groups):
|
||||
n = y.shape[0]
|
||||
n_groups = np.max(groups) + 1
|
||||
h_vecs = np.empty((n_groups, n))
|
||||
for group in range(n_groups):
|
||||
e = np.equal(group, groups)
|
||||
h_vecs[group] = h * e
|
||||
h_vecs = h_vecs.T
|
||||
|
||||
f_new = fun(t, y[:, None] + h_vecs)
|
||||
df = f_new - f[:, None]
|
||||
|
||||
i, j, _ = find(structure)
|
||||
diff = coo_matrix((df[i, groups[j]], (i, j)), shape=(n, n)).tocsc()
|
||||
max_ind = np.array(abs(diff).argmax(axis=0)).ravel()
|
||||
r = np.arange(n)
|
||||
max_diff = np.asarray(np.abs(diff[max_ind, r])).ravel()
|
||||
scale = np.maximum(np.abs(f[max_ind]),
|
||||
np.abs(f_new[max_ind, groups[r]]))
|
||||
|
||||
diff_too_small = max_diff < NUM_JAC_DIFF_REJECT * scale
|
||||
if np.any(diff_too_small):
|
||||
ind, = np.nonzero(diff_too_small)
|
||||
new_factor = NUM_JAC_FACTOR_INCREASE * factor[ind]
|
||||
h_new = (y[ind] + new_factor * y_scale[ind]) - y[ind]
|
||||
h_new_all = np.zeros(n)
|
||||
h_new_all[ind] = h_new
|
||||
|
||||
groups_unique = np.unique(groups[ind])
|
||||
groups_map = np.empty(n_groups, dtype=int)
|
||||
h_vecs = np.empty((groups_unique.shape[0], n))
|
||||
for k, group in enumerate(groups_unique):
|
||||
e = np.equal(group, groups)
|
||||
h_vecs[k] = h_new_all * e
|
||||
groups_map[group] = k
|
||||
h_vecs = h_vecs.T
|
||||
|
||||
f_new = fun(t, y[:, None] + h_vecs)
|
||||
df = f_new - f[:, None]
|
||||
i, j, _ = find(structure[:, ind])
|
||||
diff_new = coo_matrix((df[i, groups_map[groups[ind[j]]]],
|
||||
(i, j)), shape=(n, ind.shape[0])).tocsc()
|
||||
|
||||
max_ind_new = np.array(abs(diff_new).argmax(axis=0)).ravel()
|
||||
r = np.arange(ind.shape[0])
|
||||
max_diff_new = np.asarray(np.abs(diff_new[max_ind_new, r])).ravel()
|
||||
scale_new = np.maximum(
|
||||
np.abs(f[max_ind_new]),
|
||||
np.abs(f_new[max_ind_new, groups_map[groups[ind]]]))
|
||||
|
||||
update = max_diff[ind] * scale_new < max_diff_new * scale[ind]
|
||||
if np.any(update):
|
||||
update, = np.nonzero(update)
|
||||
update_ind = ind[update]
|
||||
factor[update_ind] = new_factor[update]
|
||||
h[update_ind] = h_new[update]
|
||||
diff[:, update_ind] = diff_new[:, update]
|
||||
scale[update_ind] = scale_new[update]
|
||||
max_diff[update_ind] = max_diff_new[update]
|
||||
|
||||
diff.data /= np.repeat(h, np.diff(diff.indptr))
|
||||
|
||||
factor[max_diff < NUM_JAC_DIFF_SMALL * scale] *= NUM_JAC_FACTOR_INCREASE
|
||||
factor[max_diff > NUM_JAC_DIFF_BIG * scale] *= NUM_JAC_FACTOR_DECREASE
|
||||
factor = np.maximum(factor, NUM_JAC_MIN_FACTOR)
|
||||
|
||||
return diff, factor
|
|
@ -0,0 +1,193 @@
|
|||
import numpy as np
|
||||
|
||||
N_STAGES = 12
|
||||
N_STAGES_EXTENDED = 16
|
||||
INTERPOLATOR_POWER = 7
|
||||
|
||||
C = np.array([0.0,
|
||||
0.526001519587677318785587544488e-01,
|
||||
0.789002279381515978178381316732e-01,
|
||||
0.118350341907227396726757197510,
|
||||
0.281649658092772603273242802490,
|
||||
0.333333333333333333333333333333,
|
||||
0.25,
|
||||
0.307692307692307692307692307692,
|
||||
0.651282051282051282051282051282,
|
||||
0.6,
|
||||
0.857142857142857142857142857142,
|
||||
1.0,
|
||||
1.0,
|
||||
0.1,
|
||||
0.2,
|
||||
0.777777777777777777777777777778])
|
||||
|
||||
A = np.zeros((N_STAGES_EXTENDED, N_STAGES_EXTENDED))
|
||||
A[1, 0] = 5.26001519587677318785587544488e-2
|
||||
|
||||
A[2, 0] = 1.97250569845378994544595329183e-2
|
||||
A[2, 1] = 5.91751709536136983633785987549e-2
|
||||
|
||||
A[3, 0] = 2.95875854768068491816892993775e-2
|
||||
A[3, 2] = 8.87627564304205475450678981324e-2
|
||||
|
||||
A[4, 0] = 2.41365134159266685502369798665e-1
|
||||
A[4, 2] = -8.84549479328286085344864962717e-1
|
||||
A[4, 3] = 9.24834003261792003115737966543e-1
|
||||
|
||||
A[5, 0] = 3.7037037037037037037037037037e-2
|
||||
A[5, 3] = 1.70828608729473871279604482173e-1
|
||||
A[5, 4] = 1.25467687566822425016691814123e-1
|
||||
|
||||
A[6, 0] = 3.7109375e-2
|
||||
A[6, 3] = 1.70252211019544039314978060272e-1
|
||||
A[6, 4] = 6.02165389804559606850219397283e-2
|
||||
A[6, 5] = -1.7578125e-2
|
||||
|
||||
A[7, 0] = 3.70920001185047927108779319836e-2
|
||||
A[7, 3] = 1.70383925712239993810214054705e-1
|
||||
A[7, 4] = 1.07262030446373284651809199168e-1
|
||||
A[7, 5] = -1.53194377486244017527936158236e-2
|
||||
A[7, 6] = 8.27378916381402288758473766002e-3
|
||||
|
||||
A[8, 0] = 6.24110958716075717114429577812e-1
|
||||
A[8, 3] = -3.36089262944694129406857109825
|
||||
A[8, 4] = -8.68219346841726006818189891453e-1
|
||||
A[8, 5] = 2.75920996994467083049415600797e1
|
||||
A[8, 6] = 2.01540675504778934086186788979e1
|
||||
A[8, 7] = -4.34898841810699588477366255144e1
|
||||
|
||||
A[9, 0] = 4.77662536438264365890433908527e-1
|
||||
A[9, 3] = -2.48811461997166764192642586468
|
||||
A[9, 4] = -5.90290826836842996371446475743e-1
|
||||
A[9, 5] = 2.12300514481811942347288949897e1
|
||||
A[9, 6] = 1.52792336328824235832596922938e1
|
||||
A[9, 7] = -3.32882109689848629194453265587e1
|
||||
A[9, 8] = -2.03312017085086261358222928593e-2
|
||||
|
||||
A[10, 0] = -9.3714243008598732571704021658e-1
|
||||
A[10, 3] = 5.18637242884406370830023853209
|
||||
A[10, 4] = 1.09143734899672957818500254654
|
||||
A[10, 5] = -8.14978701074692612513997267357
|
||||
A[10, 6] = -1.85200656599969598641566180701e1
|
||||
A[10, 7] = 2.27394870993505042818970056734e1
|
||||
A[10, 8] = 2.49360555267965238987089396762
|
||||
A[10, 9] = -3.0467644718982195003823669022
|
||||
|
||||
A[11, 0] = 2.27331014751653820792359768449
|
||||
A[11, 3] = -1.05344954667372501984066689879e1
|
||||
A[11, 4] = -2.00087205822486249909675718444
|
||||
A[11, 5] = -1.79589318631187989172765950534e1
|
||||
A[11, 6] = 2.79488845294199600508499808837e1
|
||||
A[11, 7] = -2.85899827713502369474065508674
|
||||
A[11, 8] = -8.87285693353062954433549289258
|
||||
A[11, 9] = 1.23605671757943030647266201528e1
|
||||
A[11, 10] = 6.43392746015763530355970484046e-1
|
||||
|
||||
A[12, 0] = 5.42937341165687622380535766363e-2
|
||||
A[12, 5] = 4.45031289275240888144113950566
|
||||
A[12, 6] = 1.89151789931450038304281599044
|
||||
A[12, 7] = -5.8012039600105847814672114227
|
||||
A[12, 8] = 3.1116436695781989440891606237e-1
|
||||
A[12, 9] = -1.52160949662516078556178806805e-1
|
||||
A[12, 10] = 2.01365400804030348374776537501e-1
|
||||
A[12, 11] = 4.47106157277725905176885569043e-2
|
||||
|
||||
A[13, 0] = 5.61675022830479523392909219681e-2
|
||||
A[13, 6] = 2.53500210216624811088794765333e-1
|
||||
A[13, 7] = -2.46239037470802489917441475441e-1
|
||||
A[13, 8] = -1.24191423263816360469010140626e-1
|
||||
A[13, 9] = 1.5329179827876569731206322685e-1
|
||||
A[13, 10] = 8.20105229563468988491666602057e-3
|
||||
A[13, 11] = 7.56789766054569976138603589584e-3
|
||||
A[13, 12] = -8.298e-3
|
||||
|
||||
A[14, 0] = 3.18346481635021405060768473261e-2
|
||||
A[14, 5] = 2.83009096723667755288322961402e-2
|
||||
A[14, 6] = 5.35419883074385676223797384372e-2
|
||||
A[14, 7] = -5.49237485713909884646569340306e-2
|
||||
A[14, 10] = -1.08347328697249322858509316994e-4
|
||||
A[14, 11] = 3.82571090835658412954920192323e-4
|
||||
A[14, 12] = -3.40465008687404560802977114492e-4
|
||||
A[14, 13] = 1.41312443674632500278074618366e-1
|
||||
|
||||
A[15, 0] = -4.28896301583791923408573538692e-1
|
||||
A[15, 5] = -4.69762141536116384314449447206
|
||||
A[15, 6] = 7.68342119606259904184240953878
|
||||
A[15, 7] = 4.06898981839711007970213554331
|
||||
A[15, 8] = 3.56727187455281109270669543021e-1
|
||||
A[15, 12] = -1.39902416515901462129418009734e-3
|
||||
A[15, 13] = 2.9475147891527723389556272149
|
||||
A[15, 14] = -9.15095847217987001081870187138
|
||||
|
||||
|
||||
B = A[N_STAGES, :N_STAGES]
|
||||
|
||||
E3 = np.zeros(N_STAGES + 1)
|
||||
E3[:-1] = B.copy()
|
||||
E3[0] -= 0.244094488188976377952755905512
|
||||
E3[8] -= 0.733846688281611857341361741547
|
||||
E3[11] -= 0.220588235294117647058823529412e-1
|
||||
|
||||
E5 = np.zeros(N_STAGES + 1)
|
||||
E5[0] = 0.1312004499419488073250102996e-1
|
||||
E5[5] = -0.1225156446376204440720569753e+1
|
||||
E5[6] = -0.4957589496572501915214079952
|
||||
E5[7] = 0.1664377182454986536961530415e+1
|
||||
E5[8] = -0.3503288487499736816886487290
|
||||
E5[9] = 0.3341791187130174790297318841
|
||||
E5[10] = 0.8192320648511571246570742613e-1
|
||||
E5[11] = -0.2235530786388629525884427845e-1
|
||||
|
||||
# First 3 coefficients are computed separately.
|
||||
D = np.zeros((INTERPOLATOR_POWER - 3, N_STAGES_EXTENDED))
|
||||
D[0, 0] = -0.84289382761090128651353491142e+1
|
||||
D[0, 5] = 0.56671495351937776962531783590
|
||||
D[0, 6] = -0.30689499459498916912797304727e+1
|
||||
D[0, 7] = 0.23846676565120698287728149680e+1
|
||||
D[0, 8] = 0.21170345824450282767155149946e+1
|
||||
D[0, 9] = -0.87139158377797299206789907490
|
||||
D[0, 10] = 0.22404374302607882758541771650e+1
|
||||
D[0, 11] = 0.63157877876946881815570249290
|
||||
D[0, 12] = -0.88990336451333310820698117400e-1
|
||||
D[0, 13] = 0.18148505520854727256656404962e+2
|
||||
D[0, 14] = -0.91946323924783554000451984436e+1
|
||||
D[0, 15] = -0.44360363875948939664310572000e+1
|
||||
|
||||
D[1, 0] = 0.10427508642579134603413151009e+2
|
||||
D[1, 5] = 0.24228349177525818288430175319e+3
|
||||
D[1, 6] = 0.16520045171727028198505394887e+3
|
||||
D[1, 7] = -0.37454675472269020279518312152e+3
|
||||
D[1, 8] = -0.22113666853125306036270938578e+2
|
||||
D[1, 9] = 0.77334326684722638389603898808e+1
|
||||
D[1, 10] = -0.30674084731089398182061213626e+2
|
||||
D[1, 11] = -0.93321305264302278729567221706e+1
|
||||
D[1, 12] = 0.15697238121770843886131091075e+2
|
||||
D[1, 13] = -0.31139403219565177677282850411e+2
|
||||
D[1, 14] = -0.93529243588444783865713862664e+1
|
||||
D[1, 15] = 0.35816841486394083752465898540e+2
|
||||
|
||||
D[2, 0] = 0.19985053242002433820987653617e+2
|
||||
D[2, 5] = -0.38703730874935176555105901742e+3
|
||||
D[2, 6] = -0.18917813819516756882830838328e+3
|
||||
D[2, 7] = 0.52780815920542364900561016686e+3
|
||||
D[2, 8] = -0.11573902539959630126141871134e+2
|
||||
D[2, 9] = 0.68812326946963000169666922661e+1
|
||||
D[2, 10] = -0.10006050966910838403183860980e+1
|
||||
D[2, 11] = 0.77771377980534432092869265740
|
||||
D[2, 12] = -0.27782057523535084065932004339e+1
|
||||
D[2, 13] = -0.60196695231264120758267380846e+2
|
||||
D[2, 14] = 0.84320405506677161018159903784e+2
|
||||
D[2, 15] = 0.11992291136182789328035130030e+2
|
||||
|
||||
D[3, 0] = -0.25693933462703749003312586129e+2
|
||||
D[3, 5] = -0.15418974869023643374053993627e+3
|
||||
D[3, 6] = -0.23152937917604549567536039109e+3
|
||||
D[3, 7] = 0.35763911791061412378285349910e+3
|
||||
D[3, 8] = 0.93405324183624310003907691704e+2
|
||||
D[3, 9] = -0.37458323136451633156875139351e+2
|
||||
D[3, 10] = 0.10409964950896230045147246184e+3
|
||||
D[3, 11] = 0.29840293426660503123344363579e+2
|
||||
D[3, 12] = -0.43533456590011143754432175058e+2
|
||||
D[3, 13] = 0.96324553959188282948394950600e+2
|
||||
D[3, 14] = -0.39177261675615439165231486172e+2
|
||||
D[3, 15] = -0.14972683625798562581422125276e+3
|
663
venv/Lib/site-packages/scipy/integrate/_ivp/ivp.py
Normal file
663
venv/Lib/site-packages/scipy/integrate/_ivp/ivp.py
Normal file
|
@ -0,0 +1,663 @@
|
|||
import inspect
|
||||
import numpy as np
|
||||
from .bdf import BDF
|
||||
from .radau import Radau
|
||||
from .rk import RK23, RK45, DOP853
|
||||
from .lsoda import LSODA
|
||||
from scipy.optimize import OptimizeResult
|
||||
from .common import EPS, OdeSolution
|
||||
from .base import OdeSolver
|
||||
|
||||
|
||||
METHODS = {'RK23': RK23,
|
||||
'RK45': RK45,
|
||||
'DOP853': DOP853,
|
||||
'Radau': Radau,
|
||||
'BDF': BDF,
|
||||
'LSODA': LSODA}
|
||||
|
||||
|
||||
MESSAGES = {0: "The solver successfully reached the end of the integration interval.",
|
||||
1: "A termination event occurred."}
|
||||
|
||||
|
||||
class OdeResult(OptimizeResult):
|
||||
pass
|
||||
|
||||
|
||||
def prepare_events(events):
|
||||
"""Standardize event functions and extract is_terminal and direction."""
|
||||
if callable(events):
|
||||
events = (events,)
|
||||
|
||||
if events is not None:
|
||||
is_terminal = np.empty(len(events), dtype=bool)
|
||||
direction = np.empty(len(events))
|
||||
for i, event in enumerate(events):
|
||||
try:
|
||||
is_terminal[i] = event.terminal
|
||||
except AttributeError:
|
||||
is_terminal[i] = False
|
||||
|
||||
try:
|
||||
direction[i] = event.direction
|
||||
except AttributeError:
|
||||
direction[i] = 0
|
||||
else:
|
||||
is_terminal = None
|
||||
direction = None
|
||||
|
||||
return events, is_terminal, direction
|
||||
|
||||
|
||||
def solve_event_equation(event, sol, t_old, t):
|
||||
"""Solve an equation corresponding to an ODE event.
|
||||
|
||||
The equation is ``event(t, y(t)) = 0``, here ``y(t)`` is known from an
|
||||
ODE solver using some sort of interpolation. It is solved by
|
||||
`scipy.optimize.brentq` with xtol=atol=4*EPS.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
event : callable
|
||||
Function ``event(t, y)``.
|
||||
sol : callable
|
||||
Function ``sol(t)`` which evaluates an ODE solution between `t_old`
|
||||
and `t`.
|
||||
t_old, t : float
|
||||
Previous and new values of time. They will be used as a bracketing
|
||||
interval.
|
||||
|
||||
Returns
|
||||
-------
|
||||
root : float
|
||||
Found solution.
|
||||
"""
|
||||
from scipy.optimize import brentq
|
||||
return brentq(lambda t: event(t, sol(t)), t_old, t,
|
||||
xtol=4 * EPS, rtol=4 * EPS)
|
||||
|
||||
|
||||
def handle_events(sol, events, active_events, is_terminal, t_old, t):
|
||||
"""Helper function to handle events.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
sol : DenseOutput
|
||||
Function ``sol(t)`` which evaluates an ODE solution between `t_old`
|
||||
and `t`.
|
||||
events : list of callables, length n_events
|
||||
Event functions with signatures ``event(t, y)``.
|
||||
active_events : ndarray
|
||||
Indices of events which occurred.
|
||||
is_terminal : ndarray, shape (n_events,)
|
||||
Which events are terminal.
|
||||
t_old, t : float
|
||||
Previous and new values of time.
|
||||
|
||||
Returns
|
||||
-------
|
||||
root_indices : ndarray
|
||||
Indices of events which take zero between `t_old` and `t` and before
|
||||
a possible termination.
|
||||
roots : ndarray
|
||||
Values of t at which events occurred.
|
||||
terminate : bool
|
||||
Whether a terminal event occurred.
|
||||
"""
|
||||
roots = [solve_event_equation(events[event_index], sol, t_old, t)
|
||||
for event_index in active_events]
|
||||
|
||||
roots = np.asarray(roots)
|
||||
|
||||
if np.any(is_terminal[active_events]):
|
||||
if t > t_old:
|
||||
order = np.argsort(roots)
|
||||
else:
|
||||
order = np.argsort(-roots)
|
||||
active_events = active_events[order]
|
||||
roots = roots[order]
|
||||
t = np.nonzero(is_terminal[active_events])[0][0]
|
||||
active_events = active_events[:t + 1]
|
||||
roots = roots[:t + 1]
|
||||
terminate = True
|
||||
else:
|
||||
terminate = False
|
||||
|
||||
return active_events, roots, terminate
|
||||
|
||||
|
||||
def find_active_events(g, g_new, direction):
|
||||
"""Find which event occurred during an integration step.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
g, g_new : array_like, shape (n_events,)
|
||||
Values of event functions at a current and next points.
|
||||
direction : ndarray, shape (n_events,)
|
||||
Event "direction" according to the definition in `solve_ivp`.
|
||||
|
||||
Returns
|
||||
-------
|
||||
active_events : ndarray
|
||||
Indices of events which occurred during the step.
|
||||
"""
|
||||
g, g_new = np.asarray(g), np.asarray(g_new)
|
||||
up = (g <= 0) & (g_new >= 0)
|
||||
down = (g >= 0) & (g_new <= 0)
|
||||
either = up | down
|
||||
mask = (up & (direction > 0) |
|
||||
down & (direction < 0) |
|
||||
either & (direction == 0))
|
||||
|
||||
return np.nonzero(mask)[0]
|
||||
|
||||
|
||||
def solve_ivp(fun, t_span, y0, method='RK45', t_eval=None, dense_output=False,
|
||||
events=None, vectorized=False, args=None, **options):
|
||||
"""Solve an initial value problem for a system of ODEs.
|
||||
|
||||
This function numerically integrates a system of ordinary differential
|
||||
equations given an initial value::
|
||||
|
||||
dy / dt = f(t, y)
|
||||
y(t0) = y0
|
||||
|
||||
Here t is a 1-D independent variable (time), y(t) is an
|
||||
N-D vector-valued function (state), and an N-D
|
||||
vector-valued function f(t, y) determines the differential equations.
|
||||
The goal is to find y(t) approximately satisfying the differential
|
||||
equations, given an initial value y(t0)=y0.
|
||||
|
||||
Some of the solvers support integration in the complex domain, but note
|
||||
that for stiff ODE solvers, the right-hand side must be
|
||||
complex-differentiable (satisfy Cauchy-Riemann equations [11]_).
|
||||
To solve a problem in the complex domain, pass y0 with a complex data type.
|
||||
Another option always available is to rewrite your problem for real and
|
||||
imaginary parts separately.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
fun : callable
|
||||
Right-hand side of the system. The calling signature is ``fun(t, y)``.
|
||||
Here `t` is a scalar, and there are two options for the ndarray `y`:
|
||||
It can either have shape (n,); then `fun` must return array_like with
|
||||
shape (n,). Alternatively, it can have shape (n, k); then `fun`
|
||||
must return an array_like with shape (n, k), i.e., each column
|
||||
corresponds to a single column in `y`. The choice between the two
|
||||
options is determined by `vectorized` argument (see below). The
|
||||
vectorized implementation allows a faster approximation of the Jacobian
|
||||
by finite differences (required for stiff solvers).
|
||||
t_span : 2-tuple of floats
|
||||
Interval of integration (t0, tf). The solver starts with t=t0 and
|
||||
integrates until it reaches t=tf.
|
||||
y0 : array_like, shape (n,)
|
||||
Initial state. For problems in the complex domain, pass `y0` with a
|
||||
complex data type (even if the initial value is purely real).
|
||||
method : string or `OdeSolver`, optional
|
||||
Integration method to use:
|
||||
|
||||
* 'RK45' (default): Explicit Runge-Kutta method of order 5(4) [1]_.
|
||||
The error is controlled assuming accuracy of the fourth-order
|
||||
method, but steps are taken using the fifth-order accurate
|
||||
formula (local extrapolation is done). A quartic interpolation
|
||||
polynomial is used for the dense output [2]_. Can be applied in
|
||||
the complex domain.
|
||||
* 'RK23': Explicit Runge-Kutta method of order 3(2) [3]_. The error
|
||||
is controlled assuming accuracy of the second-order method, but
|
||||
steps are taken using the third-order accurate formula (local
|
||||
extrapolation is done). A cubic Hermite polynomial is used for the
|
||||
dense output. Can be applied in the complex domain.
|
||||
* 'DOP853': Explicit Runge-Kutta method of order 8 [13]_.
|
||||
Python implementation of the "DOP853" algorithm originally
|
||||
written in Fortran [14]_. A 7-th order interpolation polynomial
|
||||
accurate to 7-th order is used for the dense output.
|
||||
Can be applied in the complex domain.
|
||||
* 'Radau': Implicit Runge-Kutta method of the Radau IIA family of
|
||||
order 5 [4]_. The error is controlled with a third-order accurate
|
||||
embedded formula. A cubic polynomial which satisfies the
|
||||
collocation conditions is used for the dense output.
|
||||
* 'BDF': Implicit multi-step variable-order (1 to 5) method based
|
||||
on a backward differentiation formula for the derivative
|
||||
approximation [5]_. The implementation follows the one described
|
||||
in [6]_. A quasi-constant step scheme is used and accuracy is
|
||||
enhanced using the NDF modification. Can be applied in the
|
||||
complex domain.
|
||||
* 'LSODA': Adams/BDF method with automatic stiffness detection and
|
||||
switching [7]_, [8]_. This is a wrapper of the Fortran solver
|
||||
from ODEPACK.
|
||||
|
||||
Explicit Runge-Kutta methods ('RK23', 'RK45', 'DOP853') should be used
|
||||
for non-stiff problems and implicit methods ('Radau', 'BDF') for
|
||||
stiff problems [9]_. Among Runge-Kutta methods, 'DOP853' is recommended
|
||||
for solving with high precision (low values of `rtol` and `atol`).
|
||||
|
||||
If not sure, first try to run 'RK45'. If it makes unusually many
|
||||
iterations, diverges, or fails, your problem is likely to be stiff and
|
||||
you should use 'Radau' or 'BDF'. 'LSODA' can also be a good universal
|
||||
choice, but it might be somewhat less convenient to work with as it
|
||||
wraps old Fortran code.
|
||||
|
||||
You can also pass an arbitrary class derived from `OdeSolver` which
|
||||
implements the solver.
|
||||
t_eval : array_like or None, optional
|
||||
Times at which to store the computed solution, must be sorted and lie
|
||||
within `t_span`. If None (default), use points selected by the solver.
|
||||
dense_output : bool, optional
|
||||
Whether to compute a continuous solution. Default is False.
|
||||
events : callable, or list of callables, optional
|
||||
Events to track. If None (default), no events will be tracked.
|
||||
Each event occurs at the zeros of a continuous function of time and
|
||||
state. Each function must have the signature ``event(t, y)`` and return
|
||||
a float. The solver will find an accurate value of `t` at which
|
||||
``event(t, y(t)) = 0`` using a root-finding algorithm. By default, all
|
||||
zeros will be found. The solver looks for a sign change over each step,
|
||||
so if multiple zero crossings occur within one step, events may be
|
||||
missed. Additionally each `event` function might have the following
|
||||
attributes:
|
||||
|
||||
terminal: bool, optional
|
||||
Whether to terminate integration if this event occurs.
|
||||
Implicitly False if not assigned.
|
||||
direction: float, optional
|
||||
Direction of a zero crossing. If `direction` is positive,
|
||||
`event` will only trigger when going from negative to positive,
|
||||
and vice versa if `direction` is negative. If 0, then either
|
||||
direction will trigger event. Implicitly 0 if not assigned.
|
||||
|
||||
You can assign attributes like ``event.terminal = True`` to any
|
||||
function in Python.
|
||||
vectorized : bool, optional
|
||||
Whether `fun` is implemented in a vectorized fashion. Default is False.
|
||||
args : tuple, optional
|
||||
Additional arguments to pass to the user-defined functions. If given,
|
||||
the additional arguments are passed to all user-defined functions.
|
||||
So if, for example, `fun` has the signature ``fun(t, y, a, b, c)``,
|
||||
then `jac` (if given) and any event functions must have the same
|
||||
signature, and `args` must be a tuple of length 3.
|
||||
options
|
||||
Options passed to a chosen solver. All options available for already
|
||||
implemented solvers are listed below.
|
||||
first_step : float or None, optional
|
||||
Initial step size. Default is `None` which means that the algorithm
|
||||
should choose.
|
||||
max_step : float, optional
|
||||
Maximum allowed step size. Default is np.inf, i.e., the step size is not
|
||||
bounded and determined solely by the solver.
|
||||
rtol, atol : float or array_like, optional
|
||||
Relative and absolute tolerances. The solver keeps the local error
|
||||
estimates less than ``atol + rtol * abs(y)``. Here `rtol` controls a
|
||||
relative accuracy (number of correct digits). But if a component of `y`
|
||||
is approximately below `atol`, the error only needs to fall within
|
||||
the same `atol` threshold, and the number of correct digits is not
|
||||
guaranteed. If components of y have different scales, it might be
|
||||
beneficial to set different `atol` values for different components by
|
||||
passing array_like with shape (n,) for `atol`. Default values are
|
||||
1e-3 for `rtol` and 1e-6 for `atol`.
|
||||
jac : array_like, sparse_matrix, callable or None, optional
|
||||
Jacobian matrix of the right-hand side of the system with respect
|
||||
to y, required by the 'Radau', 'BDF' and 'LSODA' method. The
|
||||
Jacobian matrix has shape (n, n) and its element (i, j) is equal to
|
||||
``d f_i / d y_j``. There are three ways to define the Jacobian:
|
||||
|
||||
* If array_like or sparse_matrix, the Jacobian is assumed to
|
||||
be constant. Not supported by 'LSODA'.
|
||||
* If callable, the Jacobian is assumed to depend on both
|
||||
t and y; it will be called as ``jac(t, y)``, as necessary.
|
||||
For 'Radau' and 'BDF' methods, the return value might be a
|
||||
sparse matrix.
|
||||
* If None (default), the Jacobian will be approximated by
|
||||
finite differences.
|
||||
|
||||
It is generally recommended to provide the Jacobian rather than
|
||||
relying on a finite-difference approximation.
|
||||
jac_sparsity : array_like, sparse matrix or None, optional
|
||||
Defines a sparsity structure of the Jacobian matrix for a finite-
|
||||
difference approximation. Its shape must be (n, n). This argument
|
||||
is ignored if `jac` is not `None`. If the Jacobian has only few
|
||||
non-zero elements in *each* row, providing the sparsity structure
|
||||
will greatly speed up the computations [10]_. A zero entry means that
|
||||
a corresponding element in the Jacobian is always zero. If None
|
||||
(default), the Jacobian is assumed to be dense.
|
||||
Not supported by 'LSODA', see `lband` and `uband` instead.
|
||||
lband, uband : int or None, optional
|
||||
Parameters defining the bandwidth of the Jacobian for the 'LSODA'
|
||||
method, i.e., ``jac[i, j] != 0 only for i - lband <= j <= i + uband``.
|
||||
Default is None. Setting these requires your jac routine to return the
|
||||
Jacobian in the packed format: the returned array must have ``n``
|
||||
columns and ``uband + lband + 1`` rows in which Jacobian diagonals are
|
||||
written. Specifically ``jac_packed[uband + i - j , j] = jac[i, j]``.
|
||||
The same format is used in `scipy.linalg.solve_banded` (check for an
|
||||
illustration). These parameters can be also used with ``jac=None`` to
|
||||
reduce the number of Jacobian elements estimated by finite differences.
|
||||
min_step : float, optional
|
||||
The minimum allowed step size for 'LSODA' method.
|
||||
By default `min_step` is zero.
|
||||
|
||||
Returns
|
||||
-------
|
||||
Bunch object with the following fields defined:
|
||||
t : ndarray, shape (n_points,)
|
||||
Time points.
|
||||
y : ndarray, shape (n, n_points)
|
||||
Values of the solution at `t`.
|
||||
sol : `OdeSolution` or None
|
||||
Found solution as `OdeSolution` instance; None if `dense_output` was
|
||||
set to False.
|
||||
t_events : list of ndarray or None
|
||||
Contains for each event type a list of arrays at which an event of
|
||||
that type event was detected. None if `events` was None.
|
||||
y_events : list of ndarray or None
|
||||
For each value of `t_events`, the corresponding value of the solution.
|
||||
None if `events` was None.
|
||||
nfev : int
|
||||
Number of evaluations of the right-hand side.
|
||||
njev : int
|
||||
Number of evaluations of the Jacobian.
|
||||
nlu : int
|
||||
Number of LU decompositions.
|
||||
status : int
|
||||
Reason for algorithm termination:
|
||||
|
||||
* -1: Integration step failed.
|
||||
* 0: The solver successfully reached the end of `tspan`.
|
||||
* 1: A termination event occurred.
|
||||
|
||||
message : string
|
||||
Human-readable description of the termination reason.
|
||||
success : bool
|
||||
True if the solver reached the interval end or a termination event
|
||||
occurred (``status >= 0``).
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] J. R. Dormand, P. J. Prince, "A family of embedded Runge-Kutta
|
||||
formulae", Journal of Computational and Applied Mathematics, Vol. 6,
|
||||
No. 1, pp. 19-26, 1980.
|
||||
.. [2] L. W. Shampine, "Some Practical Runge-Kutta Formulas", Mathematics
|
||||
of Computation,, Vol. 46, No. 173, pp. 135-150, 1986.
|
||||
.. [3] P. Bogacki, L.F. Shampine, "A 3(2) Pair of Runge-Kutta Formulas",
|
||||
Appl. Math. Lett. Vol. 2, No. 4. pp. 321-325, 1989.
|
||||
.. [4] E. Hairer, G. Wanner, "Solving Ordinary Differential Equations II:
|
||||
Stiff and Differential-Algebraic Problems", Sec. IV.8.
|
||||
.. [5] `Backward Differentiation Formula
|
||||
<https://en.wikipedia.org/wiki/Backward_differentiation_formula>`_
|
||||
on Wikipedia.
|
||||
.. [6] L. F. Shampine, M. W. Reichelt, "THE MATLAB ODE SUITE", SIAM J. SCI.
|
||||
COMPUTE., Vol. 18, No. 1, pp. 1-22, January 1997.
|
||||
.. [7] A. C. Hindmarsh, "ODEPACK, A Systematized Collection of ODE
|
||||
Solvers," IMACS Transactions on Scientific Computation, Vol 1.,
|
||||
pp. 55-64, 1983.
|
||||
.. [8] L. Petzold, "Automatic selection of methods for solving stiff and
|
||||
nonstiff systems of ordinary differential equations", SIAM Journal
|
||||
on Scientific and Statistical Computing, Vol. 4, No. 1, pp. 136-148,
|
||||
1983.
|
||||
.. [9] `Stiff equation <https://en.wikipedia.org/wiki/Stiff_equation>`_ on
|
||||
Wikipedia.
|
||||
.. [10] A. Curtis, M. J. D. Powell, and J. Reid, "On the estimation of
|
||||
sparse Jacobian matrices", Journal of the Institute of Mathematics
|
||||
and its Applications, 13, pp. 117-120, 1974.
|
||||
.. [11] `Cauchy-Riemann equations
|
||||
<https://en.wikipedia.org/wiki/Cauchy-Riemann_equations>`_ on
|
||||
Wikipedia.
|
||||
.. [12] `Lotka-Volterra equations
|
||||
<https://en.wikipedia.org/wiki/Lotka%E2%80%93Volterra_equations>`_
|
||||
on Wikipedia.
|
||||
.. [13] E. Hairer, S. P. Norsett G. Wanner, "Solving Ordinary Differential
|
||||
Equations I: Nonstiff Problems", Sec. II.
|
||||
.. [14] `Page with original Fortran code of DOP853
|
||||
<http://www.unige.ch/~hairer/software.html>`_.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Basic exponential decay showing automatically chosen time points.
|
||||
|
||||
>>> from scipy.integrate import solve_ivp
|
||||
>>> def exponential_decay(t, y): return -0.5 * y
|
||||
>>> sol = solve_ivp(exponential_decay, [0, 10], [2, 4, 8])
|
||||
>>> print(sol.t)
|
||||
[ 0. 0.11487653 1.26364188 3.06061781 4.81611105 6.57445806
|
||||
8.33328988 10. ]
|
||||
>>> print(sol.y)
|
||||
[[2. 1.88836035 1.06327177 0.43319312 0.18017253 0.07483045
|
||||
0.03107158 0.01350781]
|
||||
[4. 3.7767207 2.12654355 0.86638624 0.36034507 0.14966091
|
||||
0.06214316 0.02701561]
|
||||
[8. 7.5534414 4.25308709 1.73277247 0.72069014 0.29932181
|
||||
0.12428631 0.05403123]]
|
||||
|
||||
Specifying points where the solution is desired.
|
||||
|
||||
>>> sol = solve_ivp(exponential_decay, [0, 10], [2, 4, 8],
|
||||
... t_eval=[0, 1, 2, 4, 10])
|
||||
>>> print(sol.t)
|
||||
[ 0 1 2 4 10]
|
||||
>>> print(sol.y)
|
||||
[[2. 1.21305369 0.73534021 0.27066736 0.01350938]
|
||||
[4. 2.42610739 1.47068043 0.54133472 0.02701876]
|
||||
[8. 4.85221478 2.94136085 1.08266944 0.05403753]]
|
||||
|
||||
Cannon fired upward with terminal event upon impact. The ``terminal`` and
|
||||
``direction`` fields of an event are applied by monkey patching a function.
|
||||
Here ``y[0]`` is position and ``y[1]`` is velocity. The projectile starts
|
||||
at position 0 with velocity +10. Note that the integration never reaches
|
||||
t=100 because the event is terminal.
|
||||
|
||||
>>> def upward_cannon(t, y): return [y[1], -0.5]
|
||||
>>> def hit_ground(t, y): return y[0]
|
||||
>>> hit_ground.terminal = True
|
||||
>>> hit_ground.direction = -1
|
||||
>>> sol = solve_ivp(upward_cannon, [0, 100], [0, 10], events=hit_ground)
|
||||
>>> print(sol.t_events)
|
||||
[array([40.])]
|
||||
>>> print(sol.t)
|
||||
[0.00000000e+00 9.99900010e-05 1.09989001e-03 1.10988901e-02
|
||||
1.11088891e-01 1.11098890e+00 1.11099890e+01 4.00000000e+01]
|
||||
|
||||
Use `dense_output` and `events` to find position, which is 100, at the apex
|
||||
of the cannonball's trajectory. Apex is not defined as terminal, so both
|
||||
apex and hit_ground are found. There is no information at t=20, so the sol
|
||||
attribute is used to evaluate the solution. The sol attribute is returned
|
||||
by setting ``dense_output=True``. Alternatively, the `y_events` attribute
|
||||
can be used to access the solution at the time of the event.
|
||||
|
||||
>>> def apex(t, y): return y[1]
|
||||
>>> sol = solve_ivp(upward_cannon, [0, 100], [0, 10],
|
||||
... events=(hit_ground, apex), dense_output=True)
|
||||
>>> print(sol.t_events)
|
||||
[array([40.]), array([20.])]
|
||||
>>> print(sol.t)
|
||||
[0.00000000e+00 9.99900010e-05 1.09989001e-03 1.10988901e-02
|
||||
1.11088891e-01 1.11098890e+00 1.11099890e+01 4.00000000e+01]
|
||||
>>> print(sol.sol(sol.t_events[1][0]))
|
||||
[100. 0.]
|
||||
>>> print(sol.y_events)
|
||||
[array([[-5.68434189e-14, -1.00000000e+01]]), array([[1.00000000e+02, 1.77635684e-15]])]
|
||||
|
||||
As an example of a system with additional parameters, we'll implement
|
||||
the Lotka-Volterra equations [12]_.
|
||||
|
||||
>>> def lotkavolterra(t, z, a, b, c, d):
|
||||
... x, y = z
|
||||
... return [a*x - b*x*y, -c*y + d*x*y]
|
||||
...
|
||||
|
||||
We pass in the parameter values a=1.5, b=1, c=3 and d=1 with the `args`
|
||||
argument.
|
||||
|
||||
>>> sol = solve_ivp(lotkavolterra, [0, 15], [10, 5], args=(1.5, 1, 3, 1),
|
||||
... dense_output=True)
|
||||
|
||||
Compute a dense solution and plot it.
|
||||
|
||||
>>> t = np.linspace(0, 15, 300)
|
||||
>>> z = sol.sol(t)
|
||||
>>> import matplotlib.pyplot as plt
|
||||
>>> plt.plot(t, z.T)
|
||||
>>> plt.xlabel('t')
|
||||
>>> plt.legend(['x', 'y'], shadow=True)
|
||||
>>> plt.title('Lotka-Volterra System')
|
||||
>>> plt.show()
|
||||
|
||||
"""
|
||||
if method not in METHODS and not (
|
||||
inspect.isclass(method) and issubclass(method, OdeSolver)):
|
||||
raise ValueError("`method` must be one of {} or OdeSolver class."
|
||||
.format(METHODS))
|
||||
|
||||
t0, tf = float(t_span[0]), float(t_span[1])
|
||||
|
||||
if args is not None:
|
||||
# Wrap the user's fun (and jac, if given) in lambdas to hide the
|
||||
# additional parameters. Pass in the original fun as a keyword
|
||||
# argument to keep it in the scope of the lambda.
|
||||
fun = lambda t, x, fun=fun: fun(t, x, *args)
|
||||
jac = options.get('jac')
|
||||
if callable(jac):
|
||||
options['jac'] = lambda t, x: jac(t, x, *args)
|
||||
|
||||
if t_eval is not None:
|
||||
t_eval = np.asarray(t_eval)
|
||||
if t_eval.ndim != 1:
|
||||
raise ValueError("`t_eval` must be 1-dimensional.")
|
||||
|
||||
if np.any(t_eval < min(t0, tf)) or np.any(t_eval > max(t0, tf)):
|
||||
raise ValueError("Values in `t_eval` are not within `t_span`.")
|
||||
|
||||
d = np.diff(t_eval)
|
||||
if tf > t0 and np.any(d <= 0) or tf < t0 and np.any(d >= 0):
|
||||
raise ValueError("Values in `t_eval` are not properly sorted.")
|
||||
|
||||
if tf > t0:
|
||||
t_eval_i = 0
|
||||
else:
|
||||
# Make order of t_eval decreasing to use np.searchsorted.
|
||||
t_eval = t_eval[::-1]
|
||||
# This will be an upper bound for slices.
|
||||
t_eval_i = t_eval.shape[0]
|
||||
|
||||
if method in METHODS:
|
||||
method = METHODS[method]
|
||||
|
||||
solver = method(fun, t0, y0, tf, vectorized=vectorized, **options)
|
||||
|
||||
if t_eval is None:
|
||||
ts = [t0]
|
||||
ys = [y0]
|
||||
elif t_eval is not None and dense_output:
|
||||
ts = []
|
||||
ti = [t0]
|
||||
ys = []
|
||||
else:
|
||||
ts = []
|
||||
ys = []
|
||||
|
||||
interpolants = []
|
||||
|
||||
events, is_terminal, event_dir = prepare_events(events)
|
||||
|
||||
if events is not None:
|
||||
if args is not None:
|
||||
# Wrap user functions in lambdas to hide the additional parameters.
|
||||
# The original event function is passed as a keyword argument to the
|
||||
# lambda to keep the original function in scope (i.e., avoid the
|
||||
# late binding closure "gotcha").
|
||||
events = [lambda t, x, event=event: event(t, x, *args)
|
||||
for event in events]
|
||||
g = [event(t0, y0) for event in events]
|
||||
t_events = [[] for _ in range(len(events))]
|
||||
y_events = [[] for _ in range(len(events))]
|
||||
else:
|
||||
t_events = None
|
||||
y_events = None
|
||||
|
||||
status = None
|
||||
while status is None:
|
||||
message = solver.step()
|
||||
|
||||
if solver.status == 'finished':
|
||||
status = 0
|
||||
elif solver.status == 'failed':
|
||||
status = -1
|
||||
break
|
||||
|
||||
t_old = solver.t_old
|
||||
t = solver.t
|
||||
y = solver.y
|
||||
|
||||
if dense_output:
|
||||
sol = solver.dense_output()
|
||||
interpolants.append(sol)
|
||||
else:
|
||||
sol = None
|
||||
|
||||
if events is not None:
|
||||
g_new = [event(t, y) for event in events]
|
||||
active_events = find_active_events(g, g_new, event_dir)
|
||||
if active_events.size > 0:
|
||||
if sol is None:
|
||||
sol = solver.dense_output()
|
||||
|
||||
root_indices, roots, terminate = handle_events(
|
||||
sol, events, active_events, is_terminal, t_old, t)
|
||||
|
||||
for e, te in zip(root_indices, roots):
|
||||
t_events[e].append(te)
|
||||
y_events[e].append(sol(te))
|
||||
|
||||
if terminate:
|
||||
status = 1
|
||||
t = roots[-1]
|
||||
y = sol(t)
|
||||
|
||||
g = g_new
|
||||
|
||||
if t_eval is None:
|
||||
ts.append(t)
|
||||
ys.append(y)
|
||||
else:
|
||||
# The value in t_eval equal to t will be included.
|
||||
if solver.direction > 0:
|
||||
t_eval_i_new = np.searchsorted(t_eval, t, side='right')
|
||||
t_eval_step = t_eval[t_eval_i:t_eval_i_new]
|
||||
else:
|
||||
t_eval_i_new = np.searchsorted(t_eval, t, side='left')
|
||||
# It has to be done with two slice operations, because
|
||||
# you can't slice to 0th element inclusive using backward
|
||||
# slicing.
|
||||
t_eval_step = t_eval[t_eval_i_new:t_eval_i][::-1]
|
||||
|
||||
if t_eval_step.size > 0:
|
||||
if sol is None:
|
||||
sol = solver.dense_output()
|
||||
ts.append(t_eval_step)
|
||||
ys.append(sol(t_eval_step))
|
||||
t_eval_i = t_eval_i_new
|
||||
|
||||
if t_eval is not None and dense_output:
|
||||
ti.append(t)
|
||||
|
||||
message = MESSAGES.get(status, message)
|
||||
|
||||
if t_events is not None:
|
||||
t_events = [np.asarray(te) for te in t_events]
|
||||
y_events = [np.asarray(ye) for ye in y_events]
|
||||
|
||||
if t_eval is None:
|
||||
ts = np.array(ts)
|
||||
ys = np.vstack(ys).T
|
||||
else:
|
||||
ts = np.hstack(ts)
|
||||
ys = np.hstack(ys)
|
||||
|
||||
if dense_output:
|
||||
if t_eval is None:
|
||||
sol = OdeSolution(ts, interpolants)
|
||||
else:
|
||||
sol = OdeSolution(ti, interpolants)
|
||||
else:
|
||||
sol = None
|
||||
|
||||
return OdeResult(t=ts, y=ys, sol=sol, t_events=t_events, y_events=y_events,
|
||||
nfev=solver.nfev, njev=solver.njev, nlu=solver.nlu,
|
||||
status=status, message=message, success=status >= 0)
|
188
venv/Lib/site-packages/scipy/integrate/_ivp/lsoda.py
Normal file
188
venv/Lib/site-packages/scipy/integrate/_ivp/lsoda.py
Normal file
|
@ -0,0 +1,188 @@
|
|||
import numpy as np
|
||||
from scipy.integrate import ode
|
||||
from .common import validate_tol, validate_first_step, warn_extraneous
|
||||
from .base import OdeSolver, DenseOutput
|
||||
|
||||
|
||||
class LSODA(OdeSolver):
|
||||
"""Adams/BDF method with automatic stiffness detection and switching.
|
||||
|
||||
This is a wrapper to the Fortran solver from ODEPACK [1]_. It switches
|
||||
automatically between the nonstiff Adams method and the stiff BDF method.
|
||||
The method was originally detailed in [2]_.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
fun : callable
|
||||
Right-hand side of the system. The calling signature is ``fun(t, y)``.
|
||||
Here ``t`` is a scalar, and there are two options for the ndarray ``y``:
|
||||
It can either have shape (n,); then ``fun`` must return array_like with
|
||||
shape (n,). Alternatively it can have shape (n, k); then ``fun``
|
||||
must return an array_like with shape (n, k), i.e. each column
|
||||
corresponds to a single column in ``y``. The choice between the two
|
||||
options is determined by `vectorized` argument (see below). The
|
||||
vectorized implementation allows a faster approximation of the Jacobian
|
||||
by finite differences (required for this solver).
|
||||
t0 : float
|
||||
Initial time.
|
||||
y0 : array_like, shape (n,)
|
||||
Initial state.
|
||||
t_bound : float
|
||||
Boundary time - the integration won't continue beyond it. It also
|
||||
determines the direction of the integration.
|
||||
first_step : float or None, optional
|
||||
Initial step size. Default is ``None`` which means that the algorithm
|
||||
should choose.
|
||||
min_step : float, optional
|
||||
Minimum allowed step size. Default is 0.0, i.e., the step size is not
|
||||
bounded and determined solely by the solver.
|
||||
max_step : float, optional
|
||||
Maximum allowed step size. Default is np.inf, i.e., the step size is not
|
||||
bounded and determined solely by the solver.
|
||||
rtol, atol : float and array_like, optional
|
||||
Relative and absolute tolerances. The solver keeps the local error
|
||||
estimates less than ``atol + rtol * abs(y)``. Here `rtol` controls a
|
||||
relative accuracy (number of correct digits). But if a component of `y`
|
||||
is approximately below `atol`, the error only needs to fall within
|
||||
the same `atol` threshold, and the number of correct digits is not
|
||||
guaranteed. If components of y have different scales, it might be
|
||||
beneficial to set different `atol` values for different components by
|
||||
passing array_like with shape (n,) for `atol`. Default values are
|
||||
1e-3 for `rtol` and 1e-6 for `atol`.
|
||||
jac : None or callable, optional
|
||||
Jacobian matrix of the right-hand side of the system with respect to
|
||||
``y``. The Jacobian matrix has shape (n, n) and its element (i, j) is
|
||||
equal to ``d f_i / d y_j``. The function will be called as
|
||||
``jac(t, y)``. If None (default), the Jacobian will be
|
||||
approximated by finite differences. It is generally recommended to
|
||||
provide the Jacobian rather than relying on a finite-difference
|
||||
approximation.
|
||||
lband, uband : int or None
|
||||
Parameters defining the bandwidth of the Jacobian,
|
||||
i.e., ``jac[i, j] != 0 only for i - lband <= j <= i + uband``. Setting
|
||||
these requires your jac routine to return the Jacobian in the packed format:
|
||||
the returned array must have ``n`` columns and ``uband + lband + 1``
|
||||
rows in which Jacobian diagonals are written. Specifically
|
||||
``jac_packed[uband + i - j , j] = jac[i, j]``. The same format is used
|
||||
in `scipy.linalg.solve_banded` (check for an illustration).
|
||||
These parameters can be also used with ``jac=None`` to reduce the
|
||||
number of Jacobian elements estimated by finite differences.
|
||||
vectorized : bool, optional
|
||||
Whether `fun` is implemented in a vectorized fashion. A vectorized
|
||||
implementation offers no advantages for this solver. Default is False.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
n : int
|
||||
Number of equations.
|
||||
status : string
|
||||
Current status of the solver: 'running', 'finished' or 'failed'.
|
||||
t_bound : float
|
||||
Boundary time.
|
||||
direction : float
|
||||
Integration direction: +1 or -1.
|
||||
t : float
|
||||
Current time.
|
||||
y : ndarray
|
||||
Current state.
|
||||
t_old : float
|
||||
Previous time. None if no steps were made yet.
|
||||
nfev : int
|
||||
Number of evaluations of the right-hand side.
|
||||
njev : int
|
||||
Number of evaluations of the Jacobian.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] A. C. Hindmarsh, "ODEPACK, A Systematized Collection of ODE
|
||||
Solvers," IMACS Transactions on Scientific Computation, Vol 1.,
|
||||
pp. 55-64, 1983.
|
||||
.. [2] L. Petzold, "Automatic selection of methods for solving stiff and
|
||||
nonstiff systems of ordinary differential equations", SIAM Journal
|
||||
on Scientific and Statistical Computing, Vol. 4, No. 1, pp. 136-148,
|
||||
1983.
|
||||
"""
|
||||
def __init__(self, fun, t0, y0, t_bound, first_step=None, min_step=0.0,
|
||||
max_step=np.inf, rtol=1e-3, atol=1e-6, jac=None, lband=None,
|
||||
uband=None, vectorized=False, **extraneous):
|
||||
warn_extraneous(extraneous)
|
||||
super(LSODA, self).__init__(fun, t0, y0, t_bound, vectorized)
|
||||
|
||||
if first_step is None:
|
||||
first_step = 0 # LSODA value for automatic selection.
|
||||
else:
|
||||
first_step = validate_first_step(first_step, t0, t_bound)
|
||||
|
||||
first_step *= self.direction
|
||||
|
||||
if max_step == np.inf:
|
||||
max_step = 0 # LSODA value for infinity.
|
||||
elif max_step <= 0:
|
||||
raise ValueError("`max_step` must be positive.")
|
||||
|
||||
if min_step < 0:
|
||||
raise ValueError("`min_step` must be nonnegative.")
|
||||
|
||||
rtol, atol = validate_tol(rtol, atol, self.n)
|
||||
|
||||
solver = ode(self.fun, jac)
|
||||
solver.set_integrator('lsoda', rtol=rtol, atol=atol, max_step=max_step,
|
||||
min_step=min_step, first_step=first_step,
|
||||
lband=lband, uband=uband)
|
||||
solver.set_initial_value(y0, t0)
|
||||
|
||||
# Inject t_bound into rwork array as needed for itask=5.
|
||||
solver._integrator.rwork[0] = self.t_bound
|
||||
solver._integrator.call_args[4] = solver._integrator.rwork
|
||||
|
||||
self._lsoda_solver = solver
|
||||
|
||||
def _step_impl(self):
|
||||
solver = self._lsoda_solver
|
||||
integrator = solver._integrator
|
||||
|
||||
# From lsoda.step and lsoda.integrate itask=5 means take a single
|
||||
# step and do not go past t_bound.
|
||||
itask = integrator.call_args[2]
|
||||
integrator.call_args[2] = 5
|
||||
solver._y, solver.t = integrator.run(
|
||||
solver.f, solver.jac or (lambda: None), solver._y, solver.t,
|
||||
self.t_bound, solver.f_params, solver.jac_params)
|
||||
integrator.call_args[2] = itask
|
||||
|
||||
if solver.successful():
|
||||
self.t = solver.t
|
||||
self.y = solver._y
|
||||
# From LSODA Fortran source njev is equal to nlu.
|
||||
self.njev = integrator.iwork[12]
|
||||
self.nlu = integrator.iwork[12]
|
||||
return True, None
|
||||
else:
|
||||
return False, 'Unexpected istate in LSODA.'
|
||||
|
||||
def _dense_output_impl(self):
|
||||
iwork = self._lsoda_solver._integrator.iwork
|
||||
rwork = self._lsoda_solver._integrator.rwork
|
||||
|
||||
order = iwork[14]
|
||||
h = rwork[11]
|
||||
yh = np.reshape(rwork[20:20 + (order + 1) * self.n],
|
||||
(self.n, order + 1), order='F').copy()
|
||||
|
||||
return LsodaDenseOutput(self.t_old, self.t, h, order, yh)
|
||||
|
||||
|
||||
class LsodaDenseOutput(DenseOutput):
|
||||
def __init__(self, t_old, t, h, order, yh):
|
||||
super(LsodaDenseOutput, self).__init__(t_old, t)
|
||||
self.h = h
|
||||
self.yh = yh
|
||||
self.p = np.arange(order + 1)
|
||||
|
||||
def _call_impl(self, t):
|
||||
if t.ndim == 0:
|
||||
x = ((t - self.t) / self.h) ** self.p
|
||||
else:
|
||||
x = ((t - self.t) / self.h) ** self.p[:, None]
|
||||
|
||||
return np.dot(self.yh, x)
|
561
venv/Lib/site-packages/scipy/integrate/_ivp/radau.py
Normal file
561
venv/Lib/site-packages/scipy/integrate/_ivp/radau.py
Normal file
|
@ -0,0 +1,561 @@
|
|||
import numpy as np
|
||||
from scipy.linalg import lu_factor, lu_solve
|
||||
from scipy.sparse import csc_matrix, issparse, eye
|
||||
from scipy.sparse.linalg import splu
|
||||
from scipy.optimize._numdiff import group_columns
|
||||
from .common import (validate_max_step, validate_tol, select_initial_step,
|
||||
norm, num_jac, EPS, warn_extraneous,
|
||||
validate_first_step)
|
||||
from .base import OdeSolver, DenseOutput
|
||||
|
||||
S6 = 6 ** 0.5
|
||||
|
||||
# Butcher tableau. A is not used directly, see below.
|
||||
C = np.array([(4 - S6) / 10, (4 + S6) / 10, 1])
|
||||
E = np.array([-13 - 7 * S6, -13 + 7 * S6, -1]) / 3
|
||||
|
||||
# Eigendecomposition of A is done: A = T L T**-1. There is 1 real eigenvalue
|
||||
# and a complex conjugate pair. They are written below.
|
||||
MU_REAL = 3 + 3 ** (2 / 3) - 3 ** (1 / 3)
|
||||
MU_COMPLEX = (3 + 0.5 * (3 ** (1 / 3) - 3 ** (2 / 3))
|
||||
- 0.5j * (3 ** (5 / 6) + 3 ** (7 / 6)))
|
||||
|
||||
# These are transformation matrices.
|
||||
T = np.array([
|
||||
[0.09443876248897524, -0.14125529502095421, 0.03002919410514742],
|
||||
[0.25021312296533332, 0.20412935229379994, -0.38294211275726192],
|
||||
[1, 1, 0]])
|
||||
TI = np.array([
|
||||
[4.17871859155190428, 0.32768282076106237, 0.52337644549944951],
|
||||
[-4.17871859155190428, -0.32768282076106237, 0.47662355450055044],
|
||||
[0.50287263494578682, -2.57192694985560522, 0.59603920482822492]])
|
||||
# These linear combinations are used in the algorithm.
|
||||
TI_REAL = TI[0]
|
||||
TI_COMPLEX = TI[1] + 1j * TI[2]
|
||||
|
||||
# Interpolator coefficients.
|
||||
P = np.array([
|
||||
[13/3 + 7*S6/3, -23/3 - 22*S6/3, 10/3 + 5 * S6],
|
||||
[13/3 - 7*S6/3, -23/3 + 22*S6/3, 10/3 - 5 * S6],
|
||||
[1/3, -8/3, 10/3]])
|
||||
|
||||
|
||||
NEWTON_MAXITER = 6 # Maximum number of Newton iterations.
|
||||
MIN_FACTOR = 0.2 # Minimum allowed decrease in a step size.
|
||||
MAX_FACTOR = 10 # Maximum allowed increase in a step size.
|
||||
|
||||
|
||||
def solve_collocation_system(fun, t, y, h, Z0, scale, tol,
|
||||
LU_real, LU_complex, solve_lu):
|
||||
"""Solve the collocation system.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
fun : callable
|
||||
Right-hand side of the system.
|
||||
t : float
|
||||
Current time.
|
||||
y : ndarray, shape (n,)
|
||||
Current state.
|
||||
h : float
|
||||
Step to try.
|
||||
Z0 : ndarray, shape (3, n)
|
||||
Initial guess for the solution. It determines new values of `y` at
|
||||
``t + h * C`` as ``y + Z0``, where ``C`` is the Radau method constants.
|
||||
scale : float
|
||||
Problem tolerance scale, i.e. ``rtol * abs(y) + atol``.
|
||||
tol : float
|
||||
Tolerance to which solve the system. This value is compared with
|
||||
the normalized by `scale` error.
|
||||
LU_real, LU_complex
|
||||
LU decompositions of the system Jacobians.
|
||||
solve_lu : callable
|
||||
Callable which solves a linear system given a LU decomposition. The
|
||||
signature is ``solve_lu(LU, b)``.
|
||||
|
||||
Returns
|
||||
-------
|
||||
converged : bool
|
||||
Whether iterations converged.
|
||||
n_iter : int
|
||||
Number of completed iterations.
|
||||
Z : ndarray, shape (3, n)
|
||||
Found solution.
|
||||
rate : float
|
||||
The rate of convergence.
|
||||
"""
|
||||
n = y.shape[0]
|
||||
M_real = MU_REAL / h
|
||||
M_complex = MU_COMPLEX / h
|
||||
|
||||
W = TI.dot(Z0)
|
||||
Z = Z0
|
||||
|
||||
F = np.empty((3, n))
|
||||
ch = h * C
|
||||
|
||||
dW_norm_old = None
|
||||
dW = np.empty_like(W)
|
||||
converged = False
|
||||
rate = None
|
||||
for k in range(NEWTON_MAXITER):
|
||||
for i in range(3):
|
||||
F[i] = fun(t + ch[i], y + Z[i])
|
||||
|
||||
if not np.all(np.isfinite(F)):
|
||||
break
|
||||
|
||||
f_real = F.T.dot(TI_REAL) - M_real * W[0]
|
||||
f_complex = F.T.dot(TI_COMPLEX) - M_complex * (W[1] + 1j * W[2])
|
||||
|
||||
dW_real = solve_lu(LU_real, f_real)
|
||||
dW_complex = solve_lu(LU_complex, f_complex)
|
||||
|
||||
dW[0] = dW_real
|
||||
dW[1] = dW_complex.real
|
||||
dW[2] = dW_complex.imag
|
||||
|
||||
dW_norm = norm(dW / scale)
|
||||
if dW_norm_old is not None:
|
||||
rate = dW_norm / dW_norm_old
|
||||
|
||||
if (rate is not None and (rate >= 1 or
|
||||
rate ** (NEWTON_MAXITER - k) / (1 - rate) * dW_norm > tol)):
|
||||
break
|
||||
|
||||
W += dW
|
||||
Z = T.dot(W)
|
||||
|
||||
if (dW_norm == 0 or
|
||||
rate is not None and rate / (1 - rate) * dW_norm < tol):
|
||||
converged = True
|
||||
break
|
||||
|
||||
dW_norm_old = dW_norm
|
||||
|
||||
return converged, k + 1, Z, rate
|
||||
|
||||
|
||||
def predict_factor(h_abs, h_abs_old, error_norm, error_norm_old):
|
||||
"""Predict by which factor to increase/decrease the step size.
|
||||
|
||||
The algorithm is described in [1]_.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
h_abs, h_abs_old : float
|
||||
Current and previous values of the step size, `h_abs_old` can be None
|
||||
(see Notes).
|
||||
error_norm, error_norm_old : float
|
||||
Current and previous values of the error norm, `error_norm_old` can
|
||||
be None (see Notes).
|
||||
|
||||
Returns
|
||||
-------
|
||||
factor : float
|
||||
Predicted factor.
|
||||
|
||||
Notes
|
||||
-----
|
||||
If `h_abs_old` and `error_norm_old` are both not None then a two-step
|
||||
algorithm is used, otherwise a one-step algorithm is used.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] E. Hairer, S. P. Norsett G. Wanner, "Solving Ordinary Differential
|
||||
Equations II: Stiff and Differential-Algebraic Problems", Sec. IV.8.
|
||||
"""
|
||||
if error_norm_old is None or h_abs_old is None or error_norm == 0:
|
||||
multiplier = 1
|
||||
else:
|
||||
multiplier = h_abs / h_abs_old * (error_norm_old / error_norm) ** 0.25
|
||||
|
||||
with np.errstate(divide='ignore'):
|
||||
factor = min(1, multiplier) * error_norm ** -0.25
|
||||
|
||||
return factor
|
||||
|
||||
|
||||
class Radau(OdeSolver):
|
||||
"""Implicit Runge-Kutta method of Radau IIA family of order 5.
|
||||
|
||||
The implementation follows [1]_. The error is controlled with a
|
||||
third-order accurate embedded formula. A cubic polynomial which satisfies
|
||||
the collocation conditions is used for the dense output.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
fun : callable
|
||||
Right-hand side of the system. The calling signature is ``fun(t, y)``.
|
||||
Here ``t`` is a scalar, and there are two options for the ndarray ``y``:
|
||||
It can either have shape (n,); then ``fun`` must return array_like with
|
||||
shape (n,). Alternatively it can have shape (n, k); then ``fun``
|
||||
must return an array_like with shape (n, k), i.e., each column
|
||||
corresponds to a single column in ``y``. The choice between the two
|
||||
options is determined by `vectorized` argument (see below). The
|
||||
vectorized implementation allows a faster approximation of the Jacobian
|
||||
by finite differences (required for this solver).
|
||||
t0 : float
|
||||
Initial time.
|
||||
y0 : array_like, shape (n,)
|
||||
Initial state.
|
||||
t_bound : float
|
||||
Boundary time - the integration won't continue beyond it. It also
|
||||
determines the direction of the integration.
|
||||
first_step : float or None, optional
|
||||
Initial step size. Default is ``None`` which means that the algorithm
|
||||
should choose.
|
||||
max_step : float, optional
|
||||
Maximum allowed step size. Default is np.inf, i.e., the step size is not
|
||||
bounded and determined solely by the solver.
|
||||
rtol, atol : float and array_like, optional
|
||||
Relative and absolute tolerances. The solver keeps the local error
|
||||
estimates less than ``atol + rtol * abs(y)``. Here `rtol` controls a
|
||||
relative accuracy (number of correct digits). But if a component of `y`
|
||||
is approximately below `atol`, the error only needs to fall within
|
||||
the same `atol` threshold, and the number of correct digits is not
|
||||
guaranteed. If components of y have different scales, it might be
|
||||
beneficial to set different `atol` values for different components by
|
||||
passing array_like with shape (n,) for `atol`. Default values are
|
||||
1e-3 for `rtol` and 1e-6 for `atol`.
|
||||
jac : {None, array_like, sparse_matrix, callable}, optional
|
||||
Jacobian matrix of the right-hand side of the system with respect to
|
||||
y, required by this method. The Jacobian matrix has shape (n, n) and
|
||||
its element (i, j) is equal to ``d f_i / d y_j``.
|
||||
There are three ways to define the Jacobian:
|
||||
|
||||
* If array_like or sparse_matrix, the Jacobian is assumed to
|
||||
be constant.
|
||||
* If callable, the Jacobian is assumed to depend on both
|
||||
t and y; it will be called as ``jac(t, y)`` as necessary.
|
||||
For the 'Radau' and 'BDF' methods, the return value might be a
|
||||
sparse matrix.
|
||||
* If None (default), the Jacobian will be approximated by
|
||||
finite differences.
|
||||
|
||||
It is generally recommended to provide the Jacobian rather than
|
||||
relying on a finite-difference approximation.
|
||||
jac_sparsity : {None, array_like, sparse matrix}, optional
|
||||
Defines a sparsity structure of the Jacobian matrix for a
|
||||
finite-difference approximation. Its shape must be (n, n). This argument
|
||||
is ignored if `jac` is not `None`. If the Jacobian has only few non-zero
|
||||
elements in *each* row, providing the sparsity structure will greatly
|
||||
speed up the computations [2]_. A zero entry means that a corresponding
|
||||
element in the Jacobian is always zero. If None (default), the Jacobian
|
||||
is assumed to be dense.
|
||||
vectorized : bool, optional
|
||||
Whether `fun` is implemented in a vectorized fashion. Default is False.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
n : int
|
||||
Number of equations.
|
||||
status : string
|
||||
Current status of the solver: 'running', 'finished' or 'failed'.
|
||||
t_bound : float
|
||||
Boundary time.
|
||||
direction : float
|
||||
Integration direction: +1 or -1.
|
||||
t : float
|
||||
Current time.
|
||||
y : ndarray
|
||||
Current state.
|
||||
t_old : float
|
||||
Previous time. None if no steps were made yet.
|
||||
step_size : float
|
||||
Size of the last successful step. None if no steps were made yet.
|
||||
nfev : int
|
||||
Number of evaluations of the right-hand side.
|
||||
njev : int
|
||||
Number of evaluations of the Jacobian.
|
||||
nlu : int
|
||||
Number of LU decompositions.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] E. Hairer, G. Wanner, "Solving Ordinary Differential Equations II:
|
||||
Stiff and Differential-Algebraic Problems", Sec. IV.8.
|
||||
.. [2] A. Curtis, M. J. D. Powell, and J. Reid, "On the estimation of
|
||||
sparse Jacobian matrices", Journal of the Institute of Mathematics
|
||||
and its Applications, 13, pp. 117-120, 1974.
|
||||
"""
|
||||
def __init__(self, fun, t0, y0, t_bound, max_step=np.inf,
|
||||
rtol=1e-3, atol=1e-6, jac=None, jac_sparsity=None,
|
||||
vectorized=False, first_step=None, **extraneous):
|
||||
warn_extraneous(extraneous)
|
||||
super(Radau, self).__init__(fun, t0, y0, t_bound, vectorized)
|
||||
self.y_old = None
|
||||
self.max_step = validate_max_step(max_step)
|
||||
self.rtol, self.atol = validate_tol(rtol, atol, self.n)
|
||||
self.f = self.fun(self.t, self.y)
|
||||
# Select initial step assuming the same order which is used to control
|
||||
# the error.
|
||||
if first_step is None:
|
||||
self.h_abs = select_initial_step(
|
||||
self.fun, self.t, self.y, self.f, self.direction,
|
||||
3, self.rtol, self.atol)
|
||||
else:
|
||||
self.h_abs = validate_first_step(first_step, t0, t_bound)
|
||||
self.h_abs_old = None
|
||||
self.error_norm_old = None
|
||||
|
||||
self.newton_tol = max(10 * EPS / rtol, min(0.03, rtol ** 0.5))
|
||||
self.sol = None
|
||||
|
||||
self.jac_factor = None
|
||||
self.jac, self.J = self._validate_jac(jac, jac_sparsity)
|
||||
if issparse(self.J):
|
||||
def lu(A):
|
||||
self.nlu += 1
|
||||
return splu(A)
|
||||
|
||||
def solve_lu(LU, b):
|
||||
return LU.solve(b)
|
||||
|
||||
I = eye(self.n, format='csc')
|
||||
else:
|
||||
def lu(A):
|
||||
self.nlu += 1
|
||||
return lu_factor(A, overwrite_a=True)
|
||||
|
||||
def solve_lu(LU, b):
|
||||
return lu_solve(LU, b, overwrite_b=True)
|
||||
|
||||
I = np.identity(self.n)
|
||||
|
||||
self.lu = lu
|
||||
self.solve_lu = solve_lu
|
||||
self.I = I
|
||||
|
||||
self.current_jac = True
|
||||
self.LU_real = None
|
||||
self.LU_complex = None
|
||||
self.Z = None
|
||||
|
||||
def _validate_jac(self, jac, sparsity):
|
||||
t0 = self.t
|
||||
y0 = self.y
|
||||
|
||||
if jac is None:
|
||||
if sparsity is not None:
|
||||
if issparse(sparsity):
|
||||
sparsity = csc_matrix(sparsity)
|
||||
groups = group_columns(sparsity)
|
||||
sparsity = (sparsity, groups)
|
||||
|
||||
def jac_wrapped(t, y, f):
|
||||
self.njev += 1
|
||||
J, self.jac_factor = num_jac(self.fun_vectorized, t, y, f,
|
||||
self.atol, self.jac_factor,
|
||||
sparsity)
|
||||
return J
|
||||
J = jac_wrapped(t0, y0, self.f)
|
||||
elif callable(jac):
|
||||
J = jac(t0, y0)
|
||||
self.njev = 1
|
||||
if issparse(J):
|
||||
J = csc_matrix(J)
|
||||
|
||||
def jac_wrapped(t, y, _=None):
|
||||
self.njev += 1
|
||||
return csc_matrix(jac(t, y), dtype=float)
|
||||
|
||||
else:
|
||||
J = np.asarray(J, dtype=float)
|
||||
|
||||
def jac_wrapped(t, y, _=None):
|
||||
self.njev += 1
|
||||
return np.asarray(jac(t, y), dtype=float)
|
||||
|
||||
if J.shape != (self.n, self.n):
|
||||
raise ValueError("`jac` is expected to have shape {}, but "
|
||||
"actually has {}."
|
||||
.format((self.n, self.n), J.shape))
|
||||
else:
|
||||
if issparse(jac):
|
||||
J = csc_matrix(jac)
|
||||
else:
|
||||
J = np.asarray(jac, dtype=float)
|
||||
|
||||
if J.shape != (self.n, self.n):
|
||||
raise ValueError("`jac` is expected to have shape {}, but "
|
||||
"actually has {}."
|
||||
.format((self.n, self.n), J.shape))
|
||||
jac_wrapped = None
|
||||
|
||||
return jac_wrapped, J
|
||||
|
||||
def _step_impl(self):
|
||||
t = self.t
|
||||
y = self.y
|
||||
f = self.f
|
||||
|
||||
max_step = self.max_step
|
||||
atol = self.atol
|
||||
rtol = self.rtol
|
||||
|
||||
min_step = 10 * np.abs(np.nextafter(t, self.direction * np.inf) - t)
|
||||
if self.h_abs > max_step:
|
||||
h_abs = max_step
|
||||
h_abs_old = None
|
||||
error_norm_old = None
|
||||
elif self.h_abs < min_step:
|
||||
h_abs = min_step
|
||||
h_abs_old = None
|
||||
error_norm_old = None
|
||||
else:
|
||||
h_abs = self.h_abs
|
||||
h_abs_old = self.h_abs_old
|
||||
error_norm_old = self.error_norm_old
|
||||
|
||||
J = self.J
|
||||
LU_real = self.LU_real
|
||||
LU_complex = self.LU_complex
|
||||
|
||||
current_jac = self.current_jac
|
||||
jac = self.jac
|
||||
|
||||
rejected = False
|
||||
step_accepted = False
|
||||
message = None
|
||||
while not step_accepted:
|
||||
if h_abs < min_step:
|
||||
return False, self.TOO_SMALL_STEP
|
||||
|
||||
h = h_abs * self.direction
|
||||
t_new = t + h
|
||||
|
||||
if self.direction * (t_new - self.t_bound) > 0:
|
||||
t_new = self.t_bound
|
||||
|
||||
h = t_new - t
|
||||
h_abs = np.abs(h)
|
||||
|
||||
if self.sol is None:
|
||||
Z0 = np.zeros((3, y.shape[0]))
|
||||
else:
|
||||
Z0 = self.sol(t + h * C).T - y
|
||||
|
||||
scale = atol + np.abs(y) * rtol
|
||||
|
||||
converged = False
|
||||
while not converged:
|
||||
if LU_real is None or LU_complex is None:
|
||||
LU_real = self.lu(MU_REAL / h * self.I - J)
|
||||
LU_complex = self.lu(MU_COMPLEX / h * self.I - J)
|
||||
|
||||
converged, n_iter, Z, rate = solve_collocation_system(
|
||||
self.fun, t, y, h, Z0, scale, self.newton_tol,
|
||||
LU_real, LU_complex, self.solve_lu)
|
||||
|
||||
if not converged:
|
||||
if current_jac:
|
||||
break
|
||||
|
||||
J = self.jac(t, y, f)
|
||||
current_jac = True
|
||||
LU_real = None
|
||||
LU_complex = None
|
||||
|
||||
if not converged:
|
||||
h_abs *= 0.5
|
||||
LU_real = None
|
||||
LU_complex = None
|
||||
continue
|
||||
|
||||
y_new = y + Z[-1]
|
||||
ZE = Z.T.dot(E) / h
|
||||
error = self.solve_lu(LU_real, f + ZE)
|
||||
scale = atol + np.maximum(np.abs(y), np.abs(y_new)) * rtol
|
||||
error_norm = norm(error / scale)
|
||||
safety = 0.9 * (2 * NEWTON_MAXITER + 1) / (2 * NEWTON_MAXITER
|
||||
+ n_iter)
|
||||
|
||||
if rejected and error_norm > 1:
|
||||
error = self.solve_lu(LU_real, self.fun(t, y + error) + ZE)
|
||||
error_norm = norm(error / scale)
|
||||
|
||||
if error_norm > 1:
|
||||
factor = predict_factor(h_abs, h_abs_old,
|
||||
error_norm, error_norm_old)
|
||||
h_abs *= max(MIN_FACTOR, safety * factor)
|
||||
|
||||
LU_real = None
|
||||
LU_complex = None
|
||||
rejected = True
|
||||
else:
|
||||
step_accepted = True
|
||||
|
||||
recompute_jac = jac is not None and n_iter > 2 and rate > 1e-3
|
||||
|
||||
factor = predict_factor(h_abs, h_abs_old, error_norm, error_norm_old)
|
||||
factor = min(MAX_FACTOR, safety * factor)
|
||||
|
||||
if not recompute_jac and factor < 1.2:
|
||||
factor = 1
|
||||
else:
|
||||
LU_real = None
|
||||
LU_complex = None
|
||||
|
||||
f_new = self.fun(t_new, y_new)
|
||||
if recompute_jac:
|
||||
J = jac(t_new, y_new, f_new)
|
||||
current_jac = True
|
||||
elif jac is not None:
|
||||
current_jac = False
|
||||
|
||||
self.h_abs_old = self.h_abs
|
||||
self.error_norm_old = error_norm
|
||||
|
||||
self.h_abs = h_abs * factor
|
||||
|
||||
self.y_old = y
|
||||
|
||||
self.t = t_new
|
||||
self.y = y_new
|
||||
self.f = f_new
|
||||
|
||||
self.Z = Z
|
||||
|
||||
self.LU_real = LU_real
|
||||
self.LU_complex = LU_complex
|
||||
self.current_jac = current_jac
|
||||
self.J = J
|
||||
|
||||
self.t_old = t
|
||||
self.sol = self._compute_dense_output()
|
||||
|
||||
return step_accepted, message
|
||||
|
||||
def _compute_dense_output(self):
|
||||
Q = np.dot(self.Z.T, P)
|
||||
return RadauDenseOutput(self.t_old, self.t, self.y_old, Q)
|
||||
|
||||
def _dense_output_impl(self):
|
||||
return self.sol
|
||||
|
||||
|
||||
class RadauDenseOutput(DenseOutput):
|
||||
def __init__(self, t_old, t, y_old, Q):
|
||||
super(RadauDenseOutput, self).__init__(t_old, t)
|
||||
self.h = t - t_old
|
||||
self.Q = Q
|
||||
self.order = Q.shape[1] - 1
|
||||
self.y_old = y_old
|
||||
|
||||
def _call_impl(self, t):
|
||||
x = (t - self.t_old) / self.h
|
||||
if t.ndim == 0:
|
||||
p = np.tile(x, self.order + 1)
|
||||
p = np.cumprod(p)
|
||||
else:
|
||||
p = np.tile(x, (self.order + 1, 1))
|
||||
p = np.cumprod(p, axis=0)
|
||||
# Here we don't multiply by h, not a mistake.
|
||||
y = np.dot(self.Q, p)
|
||||
if y.ndim == 2:
|
||||
y += self.y_old[:, None]
|
||||
else:
|
||||
y += self.y_old
|
||||
|
||||
return y
|
575
venv/Lib/site-packages/scipy/integrate/_ivp/rk.py
Normal file
575
venv/Lib/site-packages/scipy/integrate/_ivp/rk.py
Normal file
|
@ -0,0 +1,575 @@
|
|||
import numpy as np
|
||||
from .base import OdeSolver, DenseOutput
|
||||
from .common import (validate_max_step, validate_tol, select_initial_step,
|
||||
norm, warn_extraneous, validate_first_step)
|
||||
from . import dop853_coefficients
|
||||
|
||||
# Multiply steps computed from asymptotic behaviour of errors by this.
|
||||
SAFETY = 0.9
|
||||
|
||||
MIN_FACTOR = 0.2 # Minimum allowed decrease in a step size.
|
||||
MAX_FACTOR = 10 # Maximum allowed increase in a step size.
|
||||
|
||||
|
||||
def rk_step(fun, t, y, f, h, A, B, C, K):
|
||||
"""Perform a single Runge-Kutta step.
|
||||
|
||||
This function computes a prediction of an explicit Runge-Kutta method and
|
||||
also estimates the error of a less accurate method.
|
||||
|
||||
Notation for Butcher tableau is as in [1]_.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
fun : callable
|
||||
Right-hand side of the system.
|
||||
t : float
|
||||
Current time.
|
||||
y : ndarray, shape (n,)
|
||||
Current state.
|
||||
f : ndarray, shape (n,)
|
||||
Current value of the derivative, i.e., ``fun(x, y)``.
|
||||
h : float
|
||||
Step to use.
|
||||
A : ndarray, shape (n_stages, n_stages)
|
||||
Coefficients for combining previous RK stages to compute the next
|
||||
stage. For explicit methods the coefficients at and above the main
|
||||
diagonal are zeros.
|
||||
B : ndarray, shape (n_stages,)
|
||||
Coefficients for combining RK stages for computing the final
|
||||
prediction.
|
||||
C : ndarray, shape (n_stages,)
|
||||
Coefficients for incrementing time for consecutive RK stages.
|
||||
The value for the first stage is always zero.
|
||||
K : ndarray, shape (n_stages + 1, n)
|
||||
Storage array for putting RK stages here. Stages are stored in rows.
|
||||
The last row is a linear combination of the previous rows with
|
||||
coefficients
|
||||
|
||||
Returns
|
||||
-------
|
||||
y_new : ndarray, shape (n,)
|
||||
Solution at t + h computed with a higher accuracy.
|
||||
f_new : ndarray, shape (n,)
|
||||
Derivative ``fun(t + h, y_new)``.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] E. Hairer, S. P. Norsett G. Wanner, "Solving Ordinary Differential
|
||||
Equations I: Nonstiff Problems", Sec. II.4.
|
||||
"""
|
||||
K[0] = f
|
||||
for s, (a, c) in enumerate(zip(A[1:], C[1:]), start=1):
|
||||
dy = np.dot(K[:s].T, a[:s]) * h
|
||||
K[s] = fun(t + c * h, y + dy)
|
||||
|
||||
y_new = y + h * np.dot(K[:-1].T, B)
|
||||
f_new = fun(t + h, y_new)
|
||||
|
||||
K[-1] = f_new
|
||||
|
||||
return y_new, f_new
|
||||
|
||||
|
||||
class RungeKutta(OdeSolver):
|
||||
"""Base class for explicit Runge-Kutta methods."""
|
||||
C = NotImplemented
|
||||
A = NotImplemented
|
||||
B = NotImplemented
|
||||
E = NotImplemented
|
||||
P = NotImplemented
|
||||
order = NotImplemented
|
||||
error_estimator_order = NotImplemented
|
||||
n_stages = NotImplemented
|
||||
|
||||
def __init__(self, fun, t0, y0, t_bound, max_step=np.inf,
|
||||
rtol=1e-3, atol=1e-6, vectorized=False,
|
||||
first_step=None, **extraneous):
|
||||
warn_extraneous(extraneous)
|
||||
super(RungeKutta, self).__init__(fun, t0, y0, t_bound, vectorized,
|
||||
support_complex=True)
|
||||
self.y_old = None
|
||||
self.max_step = validate_max_step(max_step)
|
||||
self.rtol, self.atol = validate_tol(rtol, atol, self.n)
|
||||
self.f = self.fun(self.t, self.y)
|
||||
if first_step is None:
|
||||
self.h_abs = select_initial_step(
|
||||
self.fun, self.t, self.y, self.f, self.direction,
|
||||
self.error_estimator_order, self.rtol, self.atol)
|
||||
else:
|
||||
self.h_abs = validate_first_step(first_step, t0, t_bound)
|
||||
self.K = np.empty((self.n_stages + 1, self.n), dtype=self.y.dtype)
|
||||
self.error_exponent = -1 / (self.error_estimator_order + 1)
|
||||
self.h_previous = None
|
||||
|
||||
def _estimate_error(self, K, h):
|
||||
return np.dot(K.T, self.E) * h
|
||||
|
||||
def _estimate_error_norm(self, K, h, scale):
|
||||
return norm(self._estimate_error(K, h) / scale)
|
||||
|
||||
def _step_impl(self):
|
||||
t = self.t
|
||||
y = self.y
|
||||
|
||||
max_step = self.max_step
|
||||
rtol = self.rtol
|
||||
atol = self.atol
|
||||
|
||||
min_step = 10 * np.abs(np.nextafter(t, self.direction * np.inf) - t)
|
||||
|
||||
if self.h_abs > max_step:
|
||||
h_abs = max_step
|
||||
elif self.h_abs < min_step:
|
||||
h_abs = min_step
|
||||
else:
|
||||
h_abs = self.h_abs
|
||||
|
||||
step_accepted = False
|
||||
step_rejected = False
|
||||
|
||||
while not step_accepted:
|
||||
if h_abs < min_step:
|
||||
return False, self.TOO_SMALL_STEP
|
||||
|
||||
h = h_abs * self.direction
|
||||
t_new = t + h
|
||||
|
||||
if self.direction * (t_new - self.t_bound) > 0:
|
||||
t_new = self.t_bound
|
||||
|
||||
h = t_new - t
|
||||
h_abs = np.abs(h)
|
||||
|
||||
y_new, f_new = rk_step(self.fun, t, y, self.f, h, self.A,
|
||||
self.B, self.C, self.K)
|
||||
scale = atol + np.maximum(np.abs(y), np.abs(y_new)) * rtol
|
||||
error_norm = self._estimate_error_norm(self.K, h, scale)
|
||||
|
||||
if error_norm < 1:
|
||||
if error_norm == 0:
|
||||
factor = MAX_FACTOR
|
||||
else:
|
||||
factor = min(MAX_FACTOR,
|
||||
SAFETY * error_norm ** self.error_exponent)
|
||||
|
||||
if step_rejected:
|
||||
factor = min(1, factor)
|
||||
|
||||
h_abs *= factor
|
||||
|
||||
step_accepted = True
|
||||
else:
|
||||
h_abs *= max(MIN_FACTOR,
|
||||
SAFETY * error_norm ** self.error_exponent)
|
||||
step_rejected = True
|
||||
|
||||
self.h_previous = h
|
||||
self.y_old = y
|
||||
|
||||
self.t = t_new
|
||||
self.y = y_new
|
||||
|
||||
self.h_abs = h_abs
|
||||
self.f = f_new
|
||||
|
||||
return True, None
|
||||
|
||||
def _dense_output_impl(self):
|
||||
Q = self.K.T.dot(self.P)
|
||||
return RkDenseOutput(self.t_old, self.t, self.y_old, Q)
|
||||
|
||||
|
||||
class RK23(RungeKutta):
|
||||
"""Explicit Runge-Kutta method of order 3(2).
|
||||
|
||||
This uses the Bogacki-Shampine pair of formulas [1]_. The error is controlled
|
||||
assuming accuracy of the second-order method, but steps are taken using the
|
||||
third-order accurate formula (local extrapolation is done). A cubic Hermite
|
||||
polynomial is used for the dense output.
|
||||
|
||||
Can be applied in the complex domain.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
fun : callable
|
||||
Right-hand side of the system. The calling signature is ``fun(t, y)``.
|
||||
Here ``t`` is a scalar and there are two options for ndarray ``y``.
|
||||
It can either have shape (n,), then ``fun`` must return array_like with
|
||||
shape (n,). Or alternatively it can have shape (n, k), then ``fun``
|
||||
must return array_like with shape (n, k), i.e. each column
|
||||
corresponds to a single column in ``y``. The choice between the two
|
||||
options is determined by `vectorized` argument (see below).
|
||||
t0 : float
|
||||
Initial time.
|
||||
y0 : array_like, shape (n,)
|
||||
Initial state.
|
||||
t_bound : float
|
||||
Boundary time - the integration won't continue beyond it. It also
|
||||
determines the direction of the integration.
|
||||
first_step : float or None, optional
|
||||
Initial step size. Default is ``None`` which means that the algorithm
|
||||
should choose.
|
||||
max_step : float, optional
|
||||
Maximum allowed step size. Default is np.inf, i.e., the step size is not
|
||||
bounded and determined solely by the solver.
|
||||
rtol, atol : float and array_like, optional
|
||||
Relative and absolute tolerances. The solver keeps the local error
|
||||
estimates less than ``atol + rtol * abs(y)``. Here, `rtol` controls a
|
||||
relative accuracy (number of correct digits). But if a component of `y`
|
||||
is approximately below `atol`, the error only needs to fall within
|
||||
the same `atol` threshold, and the number of correct digits is not
|
||||
guaranteed. If components of y have different scales, it might be
|
||||
beneficial to set different `atol` values for different components by
|
||||
passing array_like with shape (n,) for `atol`. Default values are
|
||||
1e-3 for `rtol` and 1e-6 for `atol`.
|
||||
vectorized : bool, optional
|
||||
Whether `fun` is implemented in a vectorized fashion. Default is False.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
n : int
|
||||
Number of equations.
|
||||
status : string
|
||||
Current status of the solver: 'running', 'finished' or 'failed'.
|
||||
t_bound : float
|
||||
Boundary time.
|
||||
direction : float
|
||||
Integration direction: +1 or -1.
|
||||
t : float
|
||||
Current time.
|
||||
y : ndarray
|
||||
Current state.
|
||||
t_old : float
|
||||
Previous time. None if no steps were made yet.
|
||||
step_size : float
|
||||
Size of the last successful step. None if no steps were made yet.
|
||||
nfev : int
|
||||
Number evaluations of the system's right-hand side.
|
||||
njev : int
|
||||
Number of evaluations of the Jacobian. Is always 0 for this solver as it does not use the Jacobian.
|
||||
nlu : int
|
||||
Number of LU decompositions. Is always 0 for this solver.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] P. Bogacki, L.F. Shampine, "A 3(2) Pair of Runge-Kutta Formulas",
|
||||
Appl. Math. Lett. Vol. 2, No. 4. pp. 321-325, 1989.
|
||||
"""
|
||||
order = 3
|
||||
error_estimator_order = 2
|
||||
n_stages = 3
|
||||
C = np.array([0, 1/2, 3/4])
|
||||
A = np.array([
|
||||
[0, 0, 0],
|
||||
[1/2, 0, 0],
|
||||
[0, 3/4, 0]
|
||||
])
|
||||
B = np.array([2/9, 1/3, 4/9])
|
||||
E = np.array([5/72, -1/12, -1/9, 1/8])
|
||||
P = np.array([[1, -4 / 3, 5 / 9],
|
||||
[0, 1, -2/3],
|
||||
[0, 4/3, -8/9],
|
||||
[0, -1, 1]])
|
||||
|
||||
|
||||
class RK45(RungeKutta):
|
||||
"""Explicit Runge-Kutta method of order 5(4).
|
||||
|
||||
This uses the Dormand-Prince pair of formulas [1]_. The error is controlled
|
||||
assuming accuracy of the fourth-order method accuracy, but steps are taken
|
||||
using the fifth-order accurate formula (local extrapolation is done).
|
||||
A quartic interpolation polynomial is used for the dense output [2]_.
|
||||
|
||||
Can be applied in the complex domain.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
fun : callable
|
||||
Right-hand side of the system. The calling signature is ``fun(t, y)``.
|
||||
Here ``t`` is a scalar, and there are two options for the ndarray ``y``:
|
||||
It can either have shape (n,); then ``fun`` must return array_like with
|
||||
shape (n,). Alternatively it can have shape (n, k); then ``fun``
|
||||
must return an array_like with shape (n, k), i.e., each column
|
||||
corresponds to a single column in ``y``. The choice between the two
|
||||
options is determined by `vectorized` argument (see below).
|
||||
t0 : float
|
||||
Initial time.
|
||||
y0 : array_like, shape (n,)
|
||||
Initial state.
|
||||
t_bound : float
|
||||
Boundary time - the integration won't continue beyond it. It also
|
||||
determines the direction of the integration.
|
||||
first_step : float or None, optional
|
||||
Initial step size. Default is ``None`` which means that the algorithm
|
||||
should choose.
|
||||
max_step : float, optional
|
||||
Maximum allowed step size. Default is np.inf, i.e., the step size is not
|
||||
bounded and determined solely by the solver.
|
||||
rtol, atol : float and array_like, optional
|
||||
Relative and absolute tolerances. The solver keeps the local error
|
||||
estimates less than ``atol + rtol * abs(y)``. Here `rtol` controls a
|
||||
relative accuracy (number of correct digits). But if a component of `y`
|
||||
is approximately below `atol`, the error only needs to fall within
|
||||
the same `atol` threshold, and the number of correct digits is not
|
||||
guaranteed. If components of y have different scales, it might be
|
||||
beneficial to set different `atol` values for different components by
|
||||
passing array_like with shape (n,) for `atol`. Default values are
|
||||
1e-3 for `rtol` and 1e-6 for `atol`.
|
||||
vectorized : bool, optional
|
||||
Whether `fun` is implemented in a vectorized fashion. Default is False.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
n : int
|
||||
Number of equations.
|
||||
status : string
|
||||
Current status of the solver: 'running', 'finished' or 'failed'.
|
||||
t_bound : float
|
||||
Boundary time.
|
||||
direction : float
|
||||
Integration direction: +1 or -1.
|
||||
t : float
|
||||
Current time.
|
||||
y : ndarray
|
||||
Current state.
|
||||
t_old : float
|
||||
Previous time. None if no steps were made yet.
|
||||
step_size : float
|
||||
Size of the last successful step. None if no steps were made yet.
|
||||
nfev : int
|
||||
Number evaluations of the system's right-hand side.
|
||||
njev : int
|
||||
Number of evaluations of the Jacobian. Is always 0 for this solver as it does not use the Jacobian.
|
||||
nlu : int
|
||||
Number of LU decompositions. Is always 0 for this solver.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] J. R. Dormand, P. J. Prince, "A family of embedded Runge-Kutta
|
||||
formulae", Journal of Computational and Applied Mathematics, Vol. 6,
|
||||
No. 1, pp. 19-26, 1980.
|
||||
.. [2] L. W. Shampine, "Some Practical Runge-Kutta Formulas", Mathematics
|
||||
of Computation,, Vol. 46, No. 173, pp. 135-150, 1986.
|
||||
"""
|
||||
order = 5
|
||||
error_estimator_order = 4
|
||||
n_stages = 6
|
||||
C = np.array([0, 1/5, 3/10, 4/5, 8/9, 1])
|
||||
A = np.array([
|
||||
[0, 0, 0, 0, 0],
|
||||
[1/5, 0, 0, 0, 0],
|
||||
[3/40, 9/40, 0, 0, 0],
|
||||
[44/45, -56/15, 32/9, 0, 0],
|
||||
[19372/6561, -25360/2187, 64448/6561, -212/729, 0],
|
||||
[9017/3168, -355/33, 46732/5247, 49/176, -5103/18656]
|
||||
])
|
||||
B = np.array([35/384, 0, 500/1113, 125/192, -2187/6784, 11/84])
|
||||
E = np.array([-71/57600, 0, 71/16695, -71/1920, 17253/339200, -22/525,
|
||||
1/40])
|
||||
# Corresponds to the optimum value of c_6 from [2]_.
|
||||
P = np.array([
|
||||
[1, -8048581381/2820520608, 8663915743/2820520608,
|
||||
-12715105075/11282082432],
|
||||
[0, 0, 0, 0],
|
||||
[0, 131558114200/32700410799, -68118460800/10900136933,
|
||||
87487479700/32700410799],
|
||||
[0, -1754552775/470086768, 14199869525/1410260304,
|
||||
-10690763975/1880347072],
|
||||
[0, 127303824393/49829197408, -318862633887/49829197408,
|
||||
701980252875 / 199316789632],
|
||||
[0, -282668133/205662961, 2019193451/616988883, -1453857185/822651844],
|
||||
[0, 40617522/29380423, -110615467/29380423, 69997945/29380423]])
|
||||
|
||||
|
||||
class DOP853(RungeKutta):
|
||||
"""Explicit Runge-Kutta method of order 8.
|
||||
|
||||
This is a Python implementation of "DOP853" algorithm originally written
|
||||
in Fortran [1]_, [2]_. Note that this is not a literate translation, but
|
||||
the algorithmic core and coefficients are the same.
|
||||
|
||||
Can be applied in the complex domain.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
fun : callable
|
||||
Right-hand side of the system. The calling signature is ``fun(t, y)``.
|
||||
Here, ``t`` is a scalar, and there are two options for the ndarray ``y``:
|
||||
It can either have shape (n,); then ``fun`` must return array_like with
|
||||
shape (n,). Alternatively it can have shape (n, k); then ``fun``
|
||||
must return an array_like with shape (n, k), i.e. each column
|
||||
corresponds to a single column in ``y``. The choice between the two
|
||||
options is determined by `vectorized` argument (see below).
|
||||
t0 : float
|
||||
Initial time.
|
||||
y0 : array_like, shape (n,)
|
||||
Initial state.
|
||||
t_bound : float
|
||||
Boundary time - the integration won't continue beyond it. It also
|
||||
determines the direction of the integration.
|
||||
first_step : float or None, optional
|
||||
Initial step size. Default is ``None`` which means that the algorithm
|
||||
should choose.
|
||||
max_step : float, optional
|
||||
Maximum allowed step size. Default is np.inf, i.e. the step size is not
|
||||
bounded and determined solely by the solver.
|
||||
rtol, atol : float and array_like, optional
|
||||
Relative and absolute tolerances. The solver keeps the local error
|
||||
estimates less than ``atol + rtol * abs(y)``. Here `rtol` controls a
|
||||
relative accuracy (number of correct digits). But if a component of `y`
|
||||
is approximately below `atol`, the error only needs to fall within
|
||||
the same `atol` threshold, and the number of correct digits is not
|
||||
guaranteed. If components of y have different scales, it might be
|
||||
beneficial to set different `atol` values for different components by
|
||||
passing array_like with shape (n,) for `atol`. Default values are
|
||||
1e-3 for `rtol` and 1e-6 for `atol`.
|
||||
vectorized : bool, optional
|
||||
Whether `fun` is implemented in a vectorized fashion. Default is False.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
n : int
|
||||
Number of equations.
|
||||
status : string
|
||||
Current status of the solver: 'running', 'finished' or 'failed'.
|
||||
t_bound : float
|
||||
Boundary time.
|
||||
direction : float
|
||||
Integration direction: +1 or -1.
|
||||
t : float
|
||||
Current time.
|
||||
y : ndarray
|
||||
Current state.
|
||||
t_old : float
|
||||
Previous time. None if no steps were made yet.
|
||||
step_size : float
|
||||
Size of the last successful step. None if no steps were made yet.
|
||||
nfev : int
|
||||
Number evaluations of the system's right-hand side.
|
||||
njev : int
|
||||
Number of evaluations of the Jacobian. Is always 0 for this solver
|
||||
as it does not use the Jacobian.
|
||||
nlu : int
|
||||
Number of LU decompositions. Is always 0 for this solver.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] E. Hairer, S. P. Norsett G. Wanner, "Solving Ordinary Differential
|
||||
Equations I: Nonstiff Problems", Sec. II.
|
||||
.. [2] `Page with original Fortran code of DOP853
|
||||
<http://www.unige.ch/~hairer/software.html>`_.
|
||||
"""
|
||||
n_stages = dop853_coefficients.N_STAGES
|
||||
order = 8
|
||||
error_estimator_order = 7
|
||||
A = dop853_coefficients.A[:n_stages, :n_stages]
|
||||
B = dop853_coefficients.B
|
||||
C = dop853_coefficients.C[:n_stages]
|
||||
E3 = dop853_coefficients.E3
|
||||
E5 = dop853_coefficients.E5
|
||||
D = dop853_coefficients.D
|
||||
|
||||
A_EXTRA = dop853_coefficients.A[n_stages + 1:]
|
||||
C_EXTRA = dop853_coefficients.C[n_stages + 1:]
|
||||
|
||||
def __init__(self, fun, t0, y0, t_bound, max_step=np.inf,
|
||||
rtol=1e-3, atol=1e-6, vectorized=False,
|
||||
first_step=None, **extraneous):
|
||||
super(DOP853, self).__init__(fun, t0, y0, t_bound, max_step,
|
||||
rtol, atol, vectorized, first_step,
|
||||
**extraneous)
|
||||
self.K_extended = np.empty((dop853_coefficients.N_STAGES_EXTENDED,
|
||||
self.n), dtype=self.y.dtype)
|
||||
self.K = self.K_extended[:self.n_stages + 1]
|
||||
|
||||
def _estimate_error(self, K, h): # Left for testing purposes.
|
||||
err5 = np.dot(K.T, self.E5)
|
||||
err3 = np.dot(K.T, self.E3)
|
||||
denom = np.hypot(np.abs(err5), 0.1 * np.abs(err3))
|
||||
correction_factor = np.ones_like(err5)
|
||||
mask = denom > 0
|
||||
correction_factor[mask] = np.abs(err5[mask]) / denom[mask]
|
||||
return h * err5 * correction_factor
|
||||
|
||||
def _estimate_error_norm(self, K, h, scale):
|
||||
err5 = np.dot(K.T, self.E5) / scale
|
||||
err3 = np.dot(K.T, self.E3) / scale
|
||||
|
||||
err5_norm_2 = np.sum(err5**2)
|
||||
err3_norm_2 = np.sum(err3**2)
|
||||
denom = err5_norm_2 + 0.01 * err3_norm_2
|
||||
return np.abs(h) * err5_norm_2 / np.sqrt(denom * len(scale))
|
||||
|
||||
def _dense_output_impl(self):
|
||||
K = self.K_extended
|
||||
h = self.h_previous
|
||||
for s, (a, c) in enumerate(zip(self.A_EXTRA, self.C_EXTRA),
|
||||
start=self.n_stages + 1):
|
||||
dy = np.dot(K[:s].T, a[:s]) * h
|
||||
K[s] = self.fun(self.t_old + c * h, self.y_old + dy)
|
||||
|
||||
F = np.empty((dop853_coefficients.INTERPOLATOR_POWER, self.n),
|
||||
dtype=self.y_old.dtype)
|
||||
|
||||
f_old = K[0]
|
||||
delta_y = self.y - self.y_old
|
||||
|
||||
F[0] = delta_y
|
||||
F[1] = h * f_old - delta_y
|
||||
F[2] = 2 * delta_y - h * (self.f + f_old)
|
||||
F[3:] = h * np.dot(self.D, K)
|
||||
|
||||
return Dop853DenseOutput(self.t_old, self.t, self.y_old, F)
|
||||
|
||||
|
||||
class RkDenseOutput(DenseOutput):
|
||||
def __init__(self, t_old, t, y_old, Q):
|
||||
super(RkDenseOutput, self).__init__(t_old, t)
|
||||
self.h = t - t_old
|
||||
self.Q = Q
|
||||
self.order = Q.shape[1] - 1
|
||||
self.y_old = y_old
|
||||
|
||||
def _call_impl(self, t):
|
||||
x = (t - self.t_old) / self.h
|
||||
if t.ndim == 0:
|
||||
p = np.tile(x, self.order + 1)
|
||||
p = np.cumprod(p)
|
||||
else:
|
||||
p = np.tile(x, (self.order + 1, 1))
|
||||
p = np.cumprod(p, axis=0)
|
||||
y = self.h * np.dot(self.Q, p)
|
||||
if y.ndim == 2:
|
||||
y += self.y_old[:, None]
|
||||
else:
|
||||
y += self.y_old
|
||||
|
||||
return y
|
||||
|
||||
|
||||
class Dop853DenseOutput(DenseOutput):
|
||||
def __init__(self, t_old, t, y_old, F):
|
||||
super(Dop853DenseOutput, self).__init__(t_old, t)
|
||||
self.h = t - t_old
|
||||
self.F = F
|
||||
self.y_old = y_old
|
||||
|
||||
def _call_impl(self, t):
|
||||
x = (t - self.t_old) / self.h
|
||||
|
||||
if t.ndim == 0:
|
||||
y = np.zeros_like(self.y_old)
|
||||
else:
|
||||
x = x[:, None]
|
||||
y = np.zeros((len(x), len(self.y_old)), dtype=self.y_old.dtype)
|
||||
|
||||
for i, f in enumerate(reversed(self.F)):
|
||||
y += f
|
||||
if i % 2 == 0:
|
||||
y *= x
|
||||
else:
|
||||
y *= 1 - x
|
||||
y += self.y_old
|
||||
|
||||
return y.T
|
1374
venv/Lib/site-packages/scipy/integrate/_ode.py
Normal file
1374
venv/Lib/site-packages/scipy/integrate/_ode.py
Normal file
File diff suppressed because it is too large
Load diff
BIN
venv/Lib/site-packages/scipy/integrate/_odepack.cp36-win32.pyd
Normal file
BIN
venv/Lib/site-packages/scipy/integrate/_odepack.cp36-win32.pyd
Normal file
Binary file not shown.
638
venv/Lib/site-packages/scipy/integrate/_quad_vec.py
Normal file
638
venv/Lib/site-packages/scipy/integrate/_quad_vec.py
Normal file
|
@ -0,0 +1,638 @@
|
|||
import sys
|
||||
import copy
|
||||
import heapq
|
||||
import collections
|
||||
import functools
|
||||
|
||||
import numpy as np
|
||||
|
||||
from scipy._lib._util import MapWrapper
|
||||
|
||||
|
||||
class LRUDict(collections.OrderedDict):
|
||||
def __init__(self, max_size):
|
||||
self.__max_size = max_size
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
existing_key = (key in self)
|
||||
super(LRUDict, self).__setitem__(key, value)
|
||||
if existing_key:
|
||||
self.move_to_end(key)
|
||||
elif len(self) > self.__max_size:
|
||||
self.popitem(last=False)
|
||||
|
||||
def update(self, other):
|
||||
# Not needed below
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class SemiInfiniteFunc(object):
|
||||
"""
|
||||
Argument transform from (start, +-oo) to (0, 1)
|
||||
"""
|
||||
def __init__(self, func, start, infty):
|
||||
self._func = func
|
||||
self._start = start
|
||||
self._sgn = -1 if infty < 0 else 1
|
||||
|
||||
# Overflow threshold for the 1/t**2 factor
|
||||
self._tmin = sys.float_info.min**0.5
|
||||
|
||||
def get_t(self, x):
|
||||
z = self._sgn * (x - self._start) + 1
|
||||
if z == 0:
|
||||
# Can happen only if point not in range
|
||||
return np.inf
|
||||
return 1 / z
|
||||
|
||||
def __call__(self, t):
|
||||
if t < self._tmin:
|
||||
return 0.0
|
||||
else:
|
||||
x = self._start + self._sgn * (1 - t) / t
|
||||
f = self._func(x)
|
||||
return self._sgn * (f / t) / t
|
||||
|
||||
|
||||
class DoubleInfiniteFunc(object):
|
||||
"""
|
||||
Argument transform from (-oo, oo) to (-1, 1)
|
||||
"""
|
||||
def __init__(self, func):
|
||||
self._func = func
|
||||
|
||||
# Overflow threshold for the 1/t**2 factor
|
||||
self._tmin = sys.float_info.min**0.5
|
||||
|
||||
def get_t(self, x):
|
||||
s = -1 if x < 0 else 1
|
||||
return s / (abs(x) + 1)
|
||||
|
||||
def __call__(self, t):
|
||||
if abs(t) < self._tmin:
|
||||
return 0.0
|
||||
else:
|
||||
x = (1 - abs(t)) / t
|
||||
f = self._func(x)
|
||||
return (f / t) / t
|
||||
|
||||
|
||||
def _max_norm(x):
|
||||
return np.amax(abs(x))
|
||||
|
||||
|
||||
def _get_sizeof(obj):
|
||||
try:
|
||||
return sys.getsizeof(obj)
|
||||
except TypeError:
|
||||
# occurs on pypy
|
||||
if hasattr(obj, '__sizeof__'):
|
||||
return int(obj.__sizeof__())
|
||||
return 64
|
||||
|
||||
|
||||
class _Bunch(object):
|
||||
def __init__(self, **kwargs):
|
||||
self.__keys = kwargs.keys()
|
||||
self.__dict__.update(**kwargs)
|
||||
|
||||
def __repr__(self):
|
||||
return "_Bunch({})".format(", ".join("{}={}".format(k, repr(self.__dict__[k]))
|
||||
for k in self.__keys))
|
||||
|
||||
|
||||
def quad_vec(f, a, b, epsabs=1e-200, epsrel=1e-8, norm='2', cache_size=100e6, limit=10000,
|
||||
workers=1, points=None, quadrature=None, full_output=False):
|
||||
r"""Adaptive integration of a vector-valued function.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
f : callable
|
||||
Vector-valued function f(x) to integrate.
|
||||
a : float
|
||||
Initial point.
|
||||
b : float
|
||||
Final point.
|
||||
epsabs : float, optional
|
||||
Absolute tolerance.
|
||||
epsrel : float, optional
|
||||
Relative tolerance.
|
||||
norm : {'max', '2'}, optional
|
||||
Vector norm to use for error estimation.
|
||||
cache_size : int, optional
|
||||
Number of bytes to use for memoization.
|
||||
workers : int or map-like callable, optional
|
||||
If `workers` is an integer, part of the computation is done in
|
||||
parallel subdivided to this many tasks (using
|
||||
:class:`python:multiprocessing.pool.Pool`).
|
||||
Supply `-1` to use all cores available to the Process.
|
||||
Alternatively, supply a map-like callable, such as
|
||||
:meth:`python:multiprocessing.pool.Pool.map` for evaluating the
|
||||
population in parallel.
|
||||
This evaluation is carried out as ``workers(func, iterable)``.
|
||||
points : list, optional
|
||||
List of additional breakpoints.
|
||||
quadrature : {'gk21', 'gk15', 'trapz'}, optional
|
||||
Quadrature rule to use on subintervals.
|
||||
Options: 'gk21' (Gauss-Kronrod 21-point rule),
|
||||
'gk15' (Gauss-Kronrod 15-point rule),
|
||||
'trapz' (composite trapezoid rule).
|
||||
Default: 'gk21' for finite intervals and 'gk15' for (semi-)infinite
|
||||
full_output : bool, optional
|
||||
Return an additional ``info`` dictionary.
|
||||
|
||||
Returns
|
||||
-------
|
||||
res : {float, array-like}
|
||||
Estimate for the result
|
||||
err : float
|
||||
Error estimate for the result in the given norm
|
||||
info : dict
|
||||
Returned only when ``full_output=True``.
|
||||
Info dictionary. Is an object with the attributes:
|
||||
|
||||
success : bool
|
||||
Whether integration reached target precision.
|
||||
status : int
|
||||
Indicator for convergence, success (0),
|
||||
failure (1), and failure due to rounding error (2).
|
||||
neval : int
|
||||
Number of function evaluations.
|
||||
intervals : ndarray, shape (num_intervals, 2)
|
||||
Start and end points of subdivision intervals.
|
||||
integrals : ndarray, shape (num_intervals, ...)
|
||||
Integral for each interval.
|
||||
Note that at most ``cache_size`` values are recorded,
|
||||
and the array may contains *nan* for missing items.
|
||||
errors : ndarray, shape (num_intervals,)
|
||||
Estimated integration error for each interval.
|
||||
|
||||
Notes
|
||||
-----
|
||||
The algorithm mainly follows the implementation of QUADPACK's
|
||||
DQAG* algorithms, implementing global error control and adaptive
|
||||
subdivision.
|
||||
|
||||
The algorithm here has some differences to the QUADPACK approach:
|
||||
|
||||
Instead of subdividing one interval at a time, the algorithm
|
||||
subdivides N intervals with largest errors at once. This enables
|
||||
(partial) parallelization of the integration.
|
||||
|
||||
The logic of subdividing "next largest" intervals first is then
|
||||
not implemented, and we rely on the above extension to avoid
|
||||
concentrating on "small" intervals only.
|
||||
|
||||
The Wynn epsilon table extrapolation is not used (QUADPACK uses it
|
||||
for infinite intervals). This is because the algorithm here is
|
||||
supposed to work on vector-valued functions, in an user-specified
|
||||
norm, and the extension of the epsilon algorithm to this case does
|
||||
not appear to be widely agreed. For max-norm, using elementwise
|
||||
Wynn epsilon could be possible, but we do not do this here with
|
||||
the hope that the epsilon extrapolation is mainly useful in
|
||||
special cases.
|
||||
|
||||
References
|
||||
----------
|
||||
[1] R. Piessens, E. de Doncker, QUADPACK (1983).
|
||||
|
||||
Examples
|
||||
--------
|
||||
We can compute integrations of a vector-valued function:
|
||||
|
||||
>>> from scipy.integrate import quad_vec
|
||||
>>> import matplotlib.pyplot as plt
|
||||
>>> alpha = np.linspace(0.0, 2.0, num=30)
|
||||
>>> f = lambda x: x**alpha
|
||||
>>> x0, x1 = 0, 2
|
||||
>>> y, err = quad_vec(f, x0, x1)
|
||||
>>> plt.plot(alpha, y)
|
||||
>>> plt.xlabel(r"$\alpha$")
|
||||
>>> plt.ylabel(r"$\int_{0}^{2} x^\alpha dx$")
|
||||
>>> plt.show()
|
||||
|
||||
"""
|
||||
a = float(a)
|
||||
b = float(b)
|
||||
|
||||
# Use simple transformations to deal with integrals over infinite
|
||||
# intervals.
|
||||
kwargs = dict(epsabs=epsabs,
|
||||
epsrel=epsrel,
|
||||
norm=norm,
|
||||
cache_size=cache_size,
|
||||
limit=limit,
|
||||
workers=workers,
|
||||
points=points,
|
||||
quadrature='gk15' if quadrature is None else quadrature,
|
||||
full_output=full_output)
|
||||
if np.isfinite(a) and np.isinf(b):
|
||||
f2 = SemiInfiniteFunc(f, start=a, infty=b)
|
||||
if points is not None:
|
||||
kwargs['points'] = tuple(f2.get_t(xp) for xp in points)
|
||||
return quad_vec(f2, 0, 1, **kwargs)
|
||||
elif np.isfinite(b) and np.isinf(a):
|
||||
f2 = SemiInfiniteFunc(f, start=b, infty=a)
|
||||
if points is not None:
|
||||
kwargs['points'] = tuple(f2.get_t(xp) for xp in points)
|
||||
res = quad_vec(f2, 0, 1, **kwargs)
|
||||
return (-res[0],) + res[1:]
|
||||
elif np.isinf(a) and np.isinf(b):
|
||||
sgn = -1 if b < a else 1
|
||||
|
||||
# NB. explicitly split integral at t=0, which separates
|
||||
# the positive and negative sides
|
||||
f2 = DoubleInfiniteFunc(f)
|
||||
if points is not None:
|
||||
kwargs['points'] = (0,) + tuple(f2.get_t(xp) for xp in points)
|
||||
else:
|
||||
kwargs['points'] = (0,)
|
||||
|
||||
if a != b:
|
||||
res = quad_vec(f2, -1, 1, **kwargs)
|
||||
else:
|
||||
res = quad_vec(f2, 1, 1, **kwargs)
|
||||
|
||||
return (res[0]*sgn,) + res[1:]
|
||||
elif not (np.isfinite(a) and np.isfinite(b)):
|
||||
raise ValueError("invalid integration bounds a={}, b={}".format(a, b))
|
||||
|
||||
norm_funcs = {
|
||||
None: _max_norm,
|
||||
'max': _max_norm,
|
||||
'2': np.linalg.norm
|
||||
}
|
||||
if callable(norm):
|
||||
norm_func = norm
|
||||
else:
|
||||
norm_func = norm_funcs[norm]
|
||||
|
||||
mapwrapper = MapWrapper(workers)
|
||||
|
||||
parallel_count = 128
|
||||
min_intervals = 2
|
||||
|
||||
try:
|
||||
_quadrature = {None: _quadrature_gk21,
|
||||
'gk21': _quadrature_gk21,
|
||||
'gk15': _quadrature_gk15,
|
||||
'trapz': _quadrature_trapz}[quadrature]
|
||||
except KeyError:
|
||||
raise ValueError("unknown quadrature {!r}".format(quadrature))
|
||||
|
||||
# Initial interval set
|
||||
if points is None:
|
||||
initial_intervals = [(a, b)]
|
||||
else:
|
||||
prev = a
|
||||
initial_intervals = []
|
||||
for p in sorted(points):
|
||||
p = float(p)
|
||||
if not (a < p < b) or p == prev:
|
||||
continue
|
||||
initial_intervals.append((prev, p))
|
||||
prev = p
|
||||
initial_intervals.append((prev, b))
|
||||
|
||||
global_integral = None
|
||||
global_error = None
|
||||
rounding_error = None
|
||||
interval_cache = None
|
||||
intervals = []
|
||||
neval = 0
|
||||
|
||||
for x1, x2 in initial_intervals:
|
||||
ig, err, rnd = _quadrature(x1, x2, f, norm_func)
|
||||
neval += _quadrature.num_eval
|
||||
|
||||
if global_integral is None:
|
||||
if isinstance(ig, (float, complex)):
|
||||
# Specialize for scalars
|
||||
if norm_func in (_max_norm, np.linalg.norm):
|
||||
norm_func = abs
|
||||
|
||||
global_integral = ig
|
||||
global_error = float(err)
|
||||
rounding_error = float(rnd)
|
||||
|
||||
cache_count = cache_size // _get_sizeof(ig)
|
||||
interval_cache = LRUDict(cache_count)
|
||||
else:
|
||||
global_integral += ig
|
||||
global_error += err
|
||||
rounding_error += rnd
|
||||
|
||||
interval_cache[(x1, x2)] = copy.copy(ig)
|
||||
intervals.append((-err, x1, x2))
|
||||
|
||||
heapq.heapify(intervals)
|
||||
|
||||
CONVERGED = 0
|
||||
NOT_CONVERGED = 1
|
||||
ROUNDING_ERROR = 2
|
||||
NOT_A_NUMBER = 3
|
||||
|
||||
status_msg = {
|
||||
CONVERGED: "Target precision reached.",
|
||||
NOT_CONVERGED: "Target precision not reached.",
|
||||
ROUNDING_ERROR: "Target precision could not be reached due to rounding error.",
|
||||
NOT_A_NUMBER: "Non-finite values encountered."
|
||||
}
|
||||
|
||||
# Process intervals
|
||||
with mapwrapper:
|
||||
ier = NOT_CONVERGED
|
||||
|
||||
while intervals and len(intervals) < limit:
|
||||
# Select intervals with largest errors for subdivision
|
||||
tol = max(epsabs, epsrel*norm_func(global_integral))
|
||||
|
||||
to_process = []
|
||||
err_sum = 0
|
||||
|
||||
for j in range(parallel_count):
|
||||
if not intervals:
|
||||
break
|
||||
|
||||
if j > 0 and err_sum > global_error - tol/8:
|
||||
# avoid unnecessary parallel splitting
|
||||
break
|
||||
|
||||
interval = heapq.heappop(intervals)
|
||||
|
||||
neg_old_err, a, b = interval
|
||||
old_int = interval_cache.pop((a, b), None)
|
||||
to_process.append(((-neg_old_err, a, b, old_int), f, norm_func, _quadrature))
|
||||
err_sum += -neg_old_err
|
||||
|
||||
# Subdivide intervals
|
||||
for dint, derr, dround_err, subint, dneval in mapwrapper(_subdivide_interval, to_process):
|
||||
neval += dneval
|
||||
global_integral += dint
|
||||
global_error += derr
|
||||
rounding_error += dround_err
|
||||
for x in subint:
|
||||
x1, x2, ig, err = x
|
||||
interval_cache[(x1, x2)] = ig
|
||||
heapq.heappush(intervals, (-err, x1, x2))
|
||||
|
||||
# Termination check
|
||||
if len(intervals) >= min_intervals:
|
||||
tol = max(epsabs, epsrel*norm_func(global_integral))
|
||||
if global_error < tol/8:
|
||||
ier = CONVERGED
|
||||
break
|
||||
if global_error < rounding_error:
|
||||
ier = ROUNDING_ERROR
|
||||
break
|
||||
|
||||
if not (np.isfinite(global_error) and np.isfinite(rounding_error)):
|
||||
ier = NOT_A_NUMBER
|
||||
break
|
||||
|
||||
res = global_integral
|
||||
err = global_error + rounding_error
|
||||
|
||||
if full_output:
|
||||
res_arr = np.asarray(res)
|
||||
dummy = np.full(res_arr.shape, np.nan, dtype=res_arr.dtype)
|
||||
integrals = np.array([interval_cache.get((z[1], z[2]), dummy)
|
||||
for z in intervals], dtype=res_arr.dtype)
|
||||
errors = np.array([-z[0] for z in intervals])
|
||||
intervals = np.array([[z[1], z[2]] for z in intervals])
|
||||
|
||||
info = _Bunch(neval=neval,
|
||||
success=(ier == CONVERGED),
|
||||
status=ier,
|
||||
message=status_msg[ier],
|
||||
intervals=intervals,
|
||||
integrals=integrals,
|
||||
errors=errors)
|
||||
return (res, err, info)
|
||||
else:
|
||||
return (res, err)
|
||||
|
||||
|
||||
def _subdivide_interval(args):
|
||||
interval, f, norm_func, _quadrature = args
|
||||
old_err, a, b, old_int = interval
|
||||
|
||||
c = 0.5 * (a + b)
|
||||
|
||||
# Left-hand side
|
||||
if getattr(_quadrature, 'cache_size', 0) > 0:
|
||||
f = functools.lru_cache(_quadrature.cache_size)(f)
|
||||
|
||||
s1, err1, round1 = _quadrature(a, c, f, norm_func)
|
||||
dneval = _quadrature.num_eval
|
||||
s2, err2, round2 = _quadrature(c, b, f, norm_func)
|
||||
dneval += _quadrature.num_eval
|
||||
if old_int is None:
|
||||
old_int, _, _ = _quadrature(a, b, f, norm_func)
|
||||
dneval += _quadrature.num_eval
|
||||
|
||||
if getattr(_quadrature, 'cache_size', 0) > 0:
|
||||
dneval = f.cache_info().misses
|
||||
|
||||
dint = s1 + s2 - old_int
|
||||
derr = err1 + err2 - old_err
|
||||
dround_err = round1 + round2
|
||||
|
||||
subintervals = ((a, c, s1, err1), (c, b, s2, err2))
|
||||
return dint, derr, dround_err, subintervals, dneval
|
||||
|
||||
|
||||
def _quadrature_trapz(x1, x2, f, norm_func):
|
||||
"""
|
||||
Composite trapezoid quadrature
|
||||
"""
|
||||
x3 = 0.5*(x1 + x2)
|
||||
f1 = f(x1)
|
||||
f2 = f(x2)
|
||||
f3 = f(x3)
|
||||
|
||||
s2 = 0.25 * (x2 - x1) * (f1 + 2*f3 + f2)
|
||||
|
||||
round_err = 0.25 * abs(x2 - x1) * (float(norm_func(f1))
|
||||
+ 2*float(norm_func(f3))
|
||||
+ float(norm_func(f2))) * 2e-16
|
||||
|
||||
s1 = 0.5 * (x2 - x1) * (f1 + f2)
|
||||
err = 1/3 * float(norm_func(s1 - s2))
|
||||
return s2, err, round_err
|
||||
|
||||
|
||||
_quadrature_trapz.cache_size = 3 * 3
|
||||
_quadrature_trapz.num_eval = 3
|
||||
|
||||
|
||||
def _quadrature_gk(a, b, f, norm_func, x, w, v):
|
||||
"""
|
||||
Generic Gauss-Kronrod quadrature
|
||||
"""
|
||||
|
||||
fv = [0.0]*len(x)
|
||||
|
||||
c = 0.5 * (a + b)
|
||||
h = 0.5 * (b - a)
|
||||
|
||||
# Gauss-Kronrod
|
||||
s_k = 0.0
|
||||
s_k_abs = 0.0
|
||||
for i in range(len(x)):
|
||||
ff = f(c + h*x[i])
|
||||
fv[i] = ff
|
||||
|
||||
vv = v[i]
|
||||
|
||||
# \int f(x)
|
||||
s_k += vv * ff
|
||||
# \int |f(x)|
|
||||
s_k_abs += vv * abs(ff)
|
||||
|
||||
# Gauss
|
||||
s_g = 0.0
|
||||
for i in range(len(w)):
|
||||
s_g += w[i] * fv[2*i + 1]
|
||||
|
||||
# Quadrature of abs-deviation from average
|
||||
s_k_dabs = 0.0
|
||||
y0 = s_k / 2.0
|
||||
for i in range(len(x)):
|
||||
# \int |f(x) - y0|
|
||||
s_k_dabs += v[i] * abs(fv[i] - y0)
|
||||
|
||||
# Use similar error estimation as quadpack
|
||||
err = float(norm_func((s_k - s_g) * h))
|
||||
dabs = float(norm_func(s_k_dabs * h))
|
||||
if dabs != 0 and err != 0:
|
||||
err = dabs * min(1.0, (200 * err / dabs)**1.5)
|
||||
|
||||
eps = sys.float_info.epsilon
|
||||
round_err = float(norm_func(50 * eps * h * s_k_abs))
|
||||
|
||||
if round_err > sys.float_info.min:
|
||||
err = max(err, round_err)
|
||||
|
||||
return h * s_k, err, round_err
|
||||
|
||||
|
||||
def _quadrature_gk21(a, b, f, norm_func):
|
||||
"""
|
||||
Gauss-Kronrod 21 quadrature with error estimate
|
||||
"""
|
||||
# Gauss-Kronrod points
|
||||
x = (0.995657163025808080735527280689003,
|
||||
0.973906528517171720077964012084452,
|
||||
0.930157491355708226001207180059508,
|
||||
0.865063366688984510732096688423493,
|
||||
0.780817726586416897063717578345042,
|
||||
0.679409568299024406234327365114874,
|
||||
0.562757134668604683339000099272694,
|
||||
0.433395394129247190799265943165784,
|
||||
0.294392862701460198131126603103866,
|
||||
0.148874338981631210884826001129720,
|
||||
0,
|
||||
-0.148874338981631210884826001129720,
|
||||
-0.294392862701460198131126603103866,
|
||||
-0.433395394129247190799265943165784,
|
||||
-0.562757134668604683339000099272694,
|
||||
-0.679409568299024406234327365114874,
|
||||
-0.780817726586416897063717578345042,
|
||||
-0.865063366688984510732096688423493,
|
||||
-0.930157491355708226001207180059508,
|
||||
-0.973906528517171720077964012084452,
|
||||
-0.995657163025808080735527280689003)
|
||||
|
||||
# 10-point weights
|
||||
w = (0.066671344308688137593568809893332,
|
||||
0.149451349150580593145776339657697,
|
||||
0.219086362515982043995534934228163,
|
||||
0.269266719309996355091226921569469,
|
||||
0.295524224714752870173892994651338,
|
||||
0.295524224714752870173892994651338,
|
||||
0.269266719309996355091226921569469,
|
||||
0.219086362515982043995534934228163,
|
||||
0.149451349150580593145776339657697,
|
||||
0.066671344308688137593568809893332)
|
||||
|
||||
# 21-point weights
|
||||
v = (0.011694638867371874278064396062192,
|
||||
0.032558162307964727478818972459390,
|
||||
0.054755896574351996031381300244580,
|
||||
0.075039674810919952767043140916190,
|
||||
0.093125454583697605535065465083366,
|
||||
0.109387158802297641899210590325805,
|
||||
0.123491976262065851077958109831074,
|
||||
0.134709217311473325928054001771707,
|
||||
0.142775938577060080797094273138717,
|
||||
0.147739104901338491374841515972068,
|
||||
0.149445554002916905664936468389821,
|
||||
0.147739104901338491374841515972068,
|
||||
0.142775938577060080797094273138717,
|
||||
0.134709217311473325928054001771707,
|
||||
0.123491976262065851077958109831074,
|
||||
0.109387158802297641899210590325805,
|
||||
0.093125454583697605535065465083366,
|
||||
0.075039674810919952767043140916190,
|
||||
0.054755896574351996031381300244580,
|
||||
0.032558162307964727478818972459390,
|
||||
0.011694638867371874278064396062192)
|
||||
|
||||
return _quadrature_gk(a, b, f, norm_func, x, w, v)
|
||||
|
||||
|
||||
_quadrature_gk21.num_eval = 21
|
||||
|
||||
|
||||
def _quadrature_gk15(a, b, f, norm_func):
|
||||
"""
|
||||
Gauss-Kronrod 15 quadrature with error estimate
|
||||
"""
|
||||
# Gauss-Kronrod points
|
||||
x = (0.991455371120812639206854697526329,
|
||||
0.949107912342758524526189684047851,
|
||||
0.864864423359769072789712788640926,
|
||||
0.741531185599394439863864773280788,
|
||||
0.586087235467691130294144838258730,
|
||||
0.405845151377397166906606412076961,
|
||||
0.207784955007898467600689403773245,
|
||||
0.000000000000000000000000000000000,
|
||||
-0.207784955007898467600689403773245,
|
||||
-0.405845151377397166906606412076961,
|
||||
-0.586087235467691130294144838258730,
|
||||
-0.741531185599394439863864773280788,
|
||||
-0.864864423359769072789712788640926,
|
||||
-0.949107912342758524526189684047851,
|
||||
-0.991455371120812639206854697526329)
|
||||
|
||||
# 7-point weights
|
||||
w = (0.129484966168869693270611432679082,
|
||||
0.279705391489276667901467771423780,
|
||||
0.381830050505118944950369775488975,
|
||||
0.417959183673469387755102040816327,
|
||||
0.381830050505118944950369775488975,
|
||||
0.279705391489276667901467771423780,
|
||||
0.129484966168869693270611432679082)
|
||||
|
||||
# 15-point weights
|
||||
v = (0.022935322010529224963732008058970,
|
||||
0.063092092629978553290700663189204,
|
||||
0.104790010322250183839876322541518,
|
||||
0.140653259715525918745189590510238,
|
||||
0.169004726639267902826583426598550,
|
||||
0.190350578064785409913256402421014,
|
||||
0.204432940075298892414161999234649,
|
||||
0.209482141084727828012999174891714,
|
||||
0.204432940075298892414161999234649,
|
||||
0.190350578064785409913256402421014,
|
||||
0.169004726639267902826583426598550,
|
||||
0.140653259715525918745189590510238,
|
||||
0.104790010322250183839876322541518,
|
||||
0.063092092629978553290700663189204,
|
||||
0.022935322010529224963732008058970)
|
||||
|
||||
return _quadrature_gk(a, b, f, norm_func, x, w, v)
|
||||
|
||||
|
||||
_quadrature_gk15.num_eval = 15
|
BIN
venv/Lib/site-packages/scipy/integrate/_quadpack.cp36-win32.pyd
Normal file
BIN
venv/Lib/site-packages/scipy/integrate/_quadpack.cp36-win32.pyd
Normal file
Binary file not shown.
971
venv/Lib/site-packages/scipy/integrate/_quadrature.py
Normal file
971
venv/Lib/site-packages/scipy/integrate/_quadrature.py
Normal file
|
@ -0,0 +1,971 @@
|
|||
import functools
|
||||
import numpy as np
|
||||
import math
|
||||
import types
|
||||
import warnings
|
||||
|
||||
# trapz is a public function for scipy.integrate,
|
||||
# even though it's actually a NumPy function.
|
||||
from numpy import trapz
|
||||
from scipy.special import roots_legendre
|
||||
from scipy.special import gammaln
|
||||
|
||||
__all__ = ['fixed_quad', 'quadrature', 'romberg', 'trapz', 'simps', 'romb',
|
||||
'cumtrapz', 'newton_cotes', 'AccuracyWarning']
|
||||
|
||||
|
||||
# Make See Also linking for our local copy work properly
|
||||
def _copy_func(f):
|
||||
"""Based on http://stackoverflow.com/a/6528148/190597 (Glenn Maynard)"""
|
||||
g = types.FunctionType(f.__code__, f.__globals__, name=f.__name__,
|
||||
argdefs=f.__defaults__, closure=f.__closure__)
|
||||
g = functools.update_wrapper(g, f)
|
||||
g.__kwdefaults__ = f.__kwdefaults__
|
||||
return g
|
||||
|
||||
|
||||
trapz = _copy_func(trapz)
|
||||
if trapz.__doc__:
|
||||
trapz.__doc__ = trapz.__doc__.replace('sum, cumsum', 'numpy.cumsum')
|
||||
|
||||
|
||||
class AccuracyWarning(Warning):
|
||||
pass
|
||||
|
||||
|
||||
def _cached_roots_legendre(n):
|
||||
"""
|
||||
Cache roots_legendre results to speed up calls of the fixed_quad
|
||||
function.
|
||||
"""
|
||||
if n in _cached_roots_legendre.cache:
|
||||
return _cached_roots_legendre.cache[n]
|
||||
|
||||
_cached_roots_legendre.cache[n] = roots_legendre(n)
|
||||
return _cached_roots_legendre.cache[n]
|
||||
|
||||
|
||||
_cached_roots_legendre.cache = dict()
|
||||
|
||||
|
||||
def fixed_quad(func, a, b, args=(), n=5):
|
||||
"""
|
||||
Compute a definite integral using fixed-order Gaussian quadrature.
|
||||
|
||||
Integrate `func` from `a` to `b` using Gaussian quadrature of
|
||||
order `n`.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
func : callable
|
||||
A Python function or method to integrate (must accept vector inputs).
|
||||
If integrating a vector-valued function, the returned array must have
|
||||
shape ``(..., len(x))``.
|
||||
a : float
|
||||
Lower limit of integration.
|
||||
b : float
|
||||
Upper limit of integration.
|
||||
args : tuple, optional
|
||||
Extra arguments to pass to function, if any.
|
||||
n : int, optional
|
||||
Order of quadrature integration. Default is 5.
|
||||
|
||||
Returns
|
||||
-------
|
||||
val : float
|
||||
Gaussian quadrature approximation to the integral
|
||||
none : None
|
||||
Statically returned value of None
|
||||
|
||||
|
||||
See Also
|
||||
--------
|
||||
quad : adaptive quadrature using QUADPACK
|
||||
dblquad : double integrals
|
||||
tplquad : triple integrals
|
||||
romberg : adaptive Romberg quadrature
|
||||
quadrature : adaptive Gaussian quadrature
|
||||
romb : integrators for sampled data
|
||||
simps : integrators for sampled data
|
||||
cumtrapz : cumulative integration for sampled data
|
||||
ode : ODE integrator
|
||||
odeint : ODE integrator
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from scipy import integrate
|
||||
>>> f = lambda x: x**8
|
||||
>>> integrate.fixed_quad(f, 0.0, 1.0, n=4)
|
||||
(0.1110884353741496, None)
|
||||
>>> integrate.fixed_quad(f, 0.0, 1.0, n=5)
|
||||
(0.11111111111111102, None)
|
||||
>>> print(1/9.0) # analytical result
|
||||
0.1111111111111111
|
||||
|
||||
>>> integrate.fixed_quad(np.cos, 0.0, np.pi/2, n=4)
|
||||
(0.9999999771971152, None)
|
||||
>>> integrate.fixed_quad(np.cos, 0.0, np.pi/2, n=5)
|
||||
(1.000000000039565, None)
|
||||
>>> np.sin(np.pi/2)-np.sin(0) # analytical result
|
||||
1.0
|
||||
|
||||
"""
|
||||
x, w = _cached_roots_legendre(n)
|
||||
x = np.real(x)
|
||||
if np.isinf(a) or np.isinf(b):
|
||||
raise ValueError("Gaussian quadrature is only available for "
|
||||
"finite limits.")
|
||||
y = (b-a)*(x+1)/2.0 + a
|
||||
return (b-a)/2.0 * np.sum(w*func(y, *args), axis=-1), None
|
||||
|
||||
|
||||
def vectorize1(func, args=(), vec_func=False):
|
||||
"""Vectorize the call to a function.
|
||||
|
||||
This is an internal utility function used by `romberg` and
|
||||
`quadrature` to create a vectorized version of a function.
|
||||
|
||||
If `vec_func` is True, the function `func` is assumed to take vector
|
||||
arguments.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
func : callable
|
||||
User defined function.
|
||||
args : tuple, optional
|
||||
Extra arguments for the function.
|
||||
vec_func : bool, optional
|
||||
True if the function func takes vector arguments.
|
||||
|
||||
Returns
|
||||
-------
|
||||
vfunc : callable
|
||||
A function that will take a vector argument and return the
|
||||
result.
|
||||
|
||||
"""
|
||||
if vec_func:
|
||||
def vfunc(x):
|
||||
return func(x, *args)
|
||||
else:
|
||||
def vfunc(x):
|
||||
if np.isscalar(x):
|
||||
return func(x, *args)
|
||||
x = np.asarray(x)
|
||||
# call with first point to get output type
|
||||
y0 = func(x[0], *args)
|
||||
n = len(x)
|
||||
dtype = getattr(y0, 'dtype', type(y0))
|
||||
output = np.empty((n,), dtype=dtype)
|
||||
output[0] = y0
|
||||
for i in range(1, n):
|
||||
output[i] = func(x[i], *args)
|
||||
return output
|
||||
return vfunc
|
||||
|
||||
|
||||
def quadrature(func, a, b, args=(), tol=1.49e-8, rtol=1.49e-8, maxiter=50,
|
||||
vec_func=True, miniter=1):
|
||||
"""
|
||||
Compute a definite integral using fixed-tolerance Gaussian quadrature.
|
||||
|
||||
Integrate `func` from `a` to `b` using Gaussian quadrature
|
||||
with absolute tolerance `tol`.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
func : function
|
||||
A Python function or method to integrate.
|
||||
a : float
|
||||
Lower limit of integration.
|
||||
b : float
|
||||
Upper limit of integration.
|
||||
args : tuple, optional
|
||||
Extra arguments to pass to function.
|
||||
tol, rtol : float, optional
|
||||
Iteration stops when error between last two iterates is less than
|
||||
`tol` OR the relative change is less than `rtol`.
|
||||
maxiter : int, optional
|
||||
Maximum order of Gaussian quadrature.
|
||||
vec_func : bool, optional
|
||||
True or False if func handles arrays as arguments (is
|
||||
a "vector" function). Default is True.
|
||||
miniter : int, optional
|
||||
Minimum order of Gaussian quadrature.
|
||||
|
||||
Returns
|
||||
-------
|
||||
val : float
|
||||
Gaussian quadrature approximation (within tolerance) to integral.
|
||||
err : float
|
||||
Difference between last two estimates of the integral.
|
||||
|
||||
See also
|
||||
--------
|
||||
romberg: adaptive Romberg quadrature
|
||||
fixed_quad: fixed-order Gaussian quadrature
|
||||
quad: adaptive quadrature using QUADPACK
|
||||
dblquad: double integrals
|
||||
tplquad: triple integrals
|
||||
romb: integrator for sampled data
|
||||
simps: integrator for sampled data
|
||||
cumtrapz: cumulative integration for sampled data
|
||||
ode: ODE integrator
|
||||
odeint: ODE integrator
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from scipy import integrate
|
||||
>>> f = lambda x: x**8
|
||||
>>> integrate.quadrature(f, 0.0, 1.0)
|
||||
(0.11111111111111106, 4.163336342344337e-17)
|
||||
>>> print(1/9.0) # analytical result
|
||||
0.1111111111111111
|
||||
|
||||
>>> integrate.quadrature(np.cos, 0.0, np.pi/2)
|
||||
(0.9999999999999536, 3.9611425250996035e-11)
|
||||
>>> np.sin(np.pi/2)-np.sin(0) # analytical result
|
||||
1.0
|
||||
|
||||
"""
|
||||
if not isinstance(args, tuple):
|
||||
args = (args,)
|
||||
vfunc = vectorize1(func, args, vec_func=vec_func)
|
||||
val = np.inf
|
||||
err = np.inf
|
||||
maxiter = max(miniter+1, maxiter)
|
||||
for n in range(miniter, maxiter+1):
|
||||
newval = fixed_quad(vfunc, a, b, (), n)[0]
|
||||
err = abs(newval-val)
|
||||
val = newval
|
||||
|
||||
if err < tol or err < rtol*abs(val):
|
||||
break
|
||||
else:
|
||||
warnings.warn(
|
||||
"maxiter (%d) exceeded. Latest difference = %e" % (maxiter, err),
|
||||
AccuracyWarning)
|
||||
return val, err
|
||||
|
||||
|
||||
def tupleset(t, i, value):
|
||||
l = list(t)
|
||||
l[i] = value
|
||||
return tuple(l)
|
||||
|
||||
|
||||
def cumtrapz(y, x=None, dx=1.0, axis=-1, initial=None):
|
||||
"""
|
||||
Cumulatively integrate y(x) using the composite trapezoidal rule.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
y : array_like
|
||||
Values to integrate.
|
||||
x : array_like, optional
|
||||
The coordinate to integrate along. If None (default), use spacing `dx`
|
||||
between consecutive elements in `y`.
|
||||
dx : float, optional
|
||||
Spacing between elements of `y`. Only used if `x` is None.
|
||||
axis : int, optional
|
||||
Specifies the axis to cumulate. Default is -1 (last axis).
|
||||
initial : scalar, optional
|
||||
If given, insert this value at the beginning of the returned result.
|
||||
Typically this value should be 0. Default is None, which means no
|
||||
value at ``x[0]`` is returned and `res` has one element less than `y`
|
||||
along the axis of integration.
|
||||
|
||||
Returns
|
||||
-------
|
||||
res : ndarray
|
||||
The result of cumulative integration of `y` along `axis`.
|
||||
If `initial` is None, the shape is such that the axis of integration
|
||||
has one less value than `y`. If `initial` is given, the shape is equal
|
||||
to that of `y`.
|
||||
|
||||
See Also
|
||||
--------
|
||||
numpy.cumsum, numpy.cumprod
|
||||
quad: adaptive quadrature using QUADPACK
|
||||
romberg: adaptive Romberg quadrature
|
||||
quadrature: adaptive Gaussian quadrature
|
||||
fixed_quad: fixed-order Gaussian quadrature
|
||||
dblquad: double integrals
|
||||
tplquad: triple integrals
|
||||
romb: integrators for sampled data
|
||||
ode: ODE integrators
|
||||
odeint: ODE integrators
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from scipy import integrate
|
||||
>>> import matplotlib.pyplot as plt
|
||||
|
||||
>>> x = np.linspace(-2, 2, num=20)
|
||||
>>> y = x
|
||||
>>> y_int = integrate.cumtrapz(y, x, initial=0)
|
||||
>>> plt.plot(x, y_int, 'ro', x, y[0] + 0.5 * x**2, 'b-')
|
||||
>>> plt.show()
|
||||
|
||||
"""
|
||||
y = np.asarray(y)
|
||||
if x is None:
|
||||
d = dx
|
||||
else:
|
||||
x = np.asarray(x)
|
||||
if x.ndim == 1:
|
||||
d = np.diff(x)
|
||||
# reshape to correct shape
|
||||
shape = [1] * y.ndim
|
||||
shape[axis] = -1
|
||||
d = d.reshape(shape)
|
||||
elif len(x.shape) != len(y.shape):
|
||||
raise ValueError("If given, shape of x must be 1-D or the "
|
||||
"same as y.")
|
||||
else:
|
||||
d = np.diff(x, axis=axis)
|
||||
|
||||
if d.shape[axis] != y.shape[axis] - 1:
|
||||
raise ValueError("If given, length of x along axis must be the "
|
||||
"same as y.")
|
||||
|
||||
nd = len(y.shape)
|
||||
slice1 = tupleset((slice(None),)*nd, axis, slice(1, None))
|
||||
slice2 = tupleset((slice(None),)*nd, axis, slice(None, -1))
|
||||
res = np.cumsum(d * (y[slice1] + y[slice2]) / 2.0, axis=axis)
|
||||
|
||||
if initial is not None:
|
||||
if not np.isscalar(initial):
|
||||
raise ValueError("`initial` parameter should be a scalar.")
|
||||
|
||||
shape = list(res.shape)
|
||||
shape[axis] = 1
|
||||
res = np.concatenate([np.full(shape, initial, dtype=res.dtype), res],
|
||||
axis=axis)
|
||||
|
||||
return res
|
||||
|
||||
|
||||
def _basic_simps(y, start, stop, x, dx, axis):
|
||||
nd = len(y.shape)
|
||||
if start is None:
|
||||
start = 0
|
||||
step = 2
|
||||
slice_all = (slice(None),)*nd
|
||||
slice0 = tupleset(slice_all, axis, slice(start, stop, step))
|
||||
slice1 = tupleset(slice_all, axis, slice(start+1, stop+1, step))
|
||||
slice2 = tupleset(slice_all, axis, slice(start+2, stop+2, step))
|
||||
|
||||
if x is None: # Even-spaced Simpson's rule.
|
||||
result = np.sum(dx/3.0 * (y[slice0]+4*y[slice1]+y[slice2]),
|
||||
axis=axis)
|
||||
else:
|
||||
# Account for possibly different spacings.
|
||||
# Simpson's rule changes a bit.
|
||||
h = np.diff(x, axis=axis)
|
||||
sl0 = tupleset(slice_all, axis, slice(start, stop, step))
|
||||
sl1 = tupleset(slice_all, axis, slice(start+1, stop+1, step))
|
||||
h0 = h[sl0]
|
||||
h1 = h[sl1]
|
||||
hsum = h0 + h1
|
||||
hprod = h0 * h1
|
||||
h0divh1 = h0 / h1
|
||||
tmp = hsum/6.0 * (y[slice0]*(2-1.0/h0divh1) +
|
||||
y[slice1]*hsum*hsum/hprod +
|
||||
y[slice2]*(2-h0divh1))
|
||||
result = np.sum(tmp, axis=axis)
|
||||
return result
|
||||
|
||||
|
||||
def simps(y, x=None, dx=1, axis=-1, even='avg'):
|
||||
"""
|
||||
Integrate y(x) using samples along the given axis and the composite
|
||||
Simpson's rule. If x is None, spacing of dx is assumed.
|
||||
|
||||
If there are an even number of samples, N, then there are an odd
|
||||
number of intervals (N-1), but Simpson's rule requires an even number
|
||||
of intervals. The parameter 'even' controls how this is handled.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
y : array_like
|
||||
Array to be integrated.
|
||||
x : array_like, optional
|
||||
If given, the points at which `y` is sampled.
|
||||
dx : int, optional
|
||||
Spacing of integration points along axis of `x`. Only used when
|
||||
`x` is None. Default is 1.
|
||||
axis : int, optional
|
||||
Axis along which to integrate. Default is the last axis.
|
||||
even : str {'avg', 'first', 'last'}, optional
|
||||
'avg' : Average two results:1) use the first N-2 intervals with
|
||||
a trapezoidal rule on the last interval and 2) use the last
|
||||
N-2 intervals with a trapezoidal rule on the first interval.
|
||||
|
||||
'first' : Use Simpson's rule for the first N-2 intervals with
|
||||
a trapezoidal rule on the last interval.
|
||||
|
||||
'last' : Use Simpson's rule for the last N-2 intervals with a
|
||||
trapezoidal rule on the first interval.
|
||||
|
||||
See Also
|
||||
--------
|
||||
quad: adaptive quadrature using QUADPACK
|
||||
romberg: adaptive Romberg quadrature
|
||||
quadrature: adaptive Gaussian quadrature
|
||||
fixed_quad: fixed-order Gaussian quadrature
|
||||
dblquad: double integrals
|
||||
tplquad: triple integrals
|
||||
romb: integrators for sampled data
|
||||
cumtrapz: cumulative integration for sampled data
|
||||
ode: ODE integrators
|
||||
odeint: ODE integrators
|
||||
|
||||
Notes
|
||||
-----
|
||||
For an odd number of samples that are equally spaced the result is
|
||||
exact if the function is a polynomial of order 3 or less. If
|
||||
the samples are not equally spaced, then the result is exact only
|
||||
if the function is a polynomial of order 2 or less.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from scipy import integrate
|
||||
>>> x = np.arange(0, 10)
|
||||
>>> y = np.arange(0, 10)
|
||||
|
||||
>>> integrate.simps(y, x)
|
||||
40.5
|
||||
|
||||
>>> y = np.power(x, 3)
|
||||
>>> integrate.simps(y, x)
|
||||
1642.5
|
||||
>>> integrate.quad(lambda x: x**3, 0, 9)[0]
|
||||
1640.25
|
||||
|
||||
>>> integrate.simps(y, x, even='first')
|
||||
1644.5
|
||||
|
||||
"""
|
||||
y = np.asarray(y)
|
||||
nd = len(y.shape)
|
||||
N = y.shape[axis]
|
||||
last_dx = dx
|
||||
first_dx = dx
|
||||
returnshape = 0
|
||||
if x is not None:
|
||||
x = np.asarray(x)
|
||||
if len(x.shape) == 1:
|
||||
shapex = [1] * nd
|
||||
shapex[axis] = x.shape[0]
|
||||
saveshape = x.shape
|
||||
returnshape = 1
|
||||
x = x.reshape(tuple(shapex))
|
||||
elif len(x.shape) != len(y.shape):
|
||||
raise ValueError("If given, shape of x must be 1-D or the "
|
||||
"same as y.")
|
||||
if x.shape[axis] != N:
|
||||
raise ValueError("If given, length of x along axis must be the "
|
||||
"same as y.")
|
||||
if N % 2 == 0:
|
||||
val = 0.0
|
||||
result = 0.0
|
||||
slice1 = (slice(None),)*nd
|
||||
slice2 = (slice(None),)*nd
|
||||
if even not in ['avg', 'last', 'first']:
|
||||
raise ValueError("Parameter 'even' must be "
|
||||
"'avg', 'last', or 'first'.")
|
||||
# Compute using Simpson's rule on first intervals
|
||||
if even in ['avg', 'first']:
|
||||
slice1 = tupleset(slice1, axis, -1)
|
||||
slice2 = tupleset(slice2, axis, -2)
|
||||
if x is not None:
|
||||
last_dx = x[slice1] - x[slice2]
|
||||
val += 0.5*last_dx*(y[slice1]+y[slice2])
|
||||
result = _basic_simps(y, 0, N-3, x, dx, axis)
|
||||
# Compute using Simpson's rule on last set of intervals
|
||||
if even in ['avg', 'last']:
|
||||
slice1 = tupleset(slice1, axis, 0)
|
||||
slice2 = tupleset(slice2, axis, 1)
|
||||
if x is not None:
|
||||
first_dx = x[tuple(slice2)] - x[tuple(slice1)]
|
||||
val += 0.5*first_dx*(y[slice2]+y[slice1])
|
||||
result += _basic_simps(y, 1, N-2, x, dx, axis)
|
||||
if even == 'avg':
|
||||
val /= 2.0
|
||||
result /= 2.0
|
||||
result = result + val
|
||||
else:
|
||||
result = _basic_simps(y, 0, N-2, x, dx, axis)
|
||||
if returnshape:
|
||||
x = x.reshape(saveshape)
|
||||
return result
|
||||
|
||||
|
||||
def romb(y, dx=1.0, axis=-1, show=False):
|
||||
"""
|
||||
Romberg integration using samples of a function.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
y : array_like
|
||||
A vector of ``2**k + 1`` equally-spaced samples of a function.
|
||||
dx : float, optional
|
||||
The sample spacing. Default is 1.
|
||||
axis : int, optional
|
||||
The axis along which to integrate. Default is -1 (last axis).
|
||||
show : bool, optional
|
||||
When `y` is a single 1-D array, then if this argument is True
|
||||
print the table showing Richardson extrapolation from the
|
||||
samples. Default is False.
|
||||
|
||||
Returns
|
||||
-------
|
||||
romb : ndarray
|
||||
The integrated result for `axis`.
|
||||
|
||||
See also
|
||||
--------
|
||||
quad : adaptive quadrature using QUADPACK
|
||||
romberg : adaptive Romberg quadrature
|
||||
quadrature : adaptive Gaussian quadrature
|
||||
fixed_quad : fixed-order Gaussian quadrature
|
||||
dblquad : double integrals
|
||||
tplquad : triple integrals
|
||||
simps : integrators for sampled data
|
||||
cumtrapz : cumulative integration for sampled data
|
||||
ode : ODE integrators
|
||||
odeint : ODE integrators
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from scipy import integrate
|
||||
>>> x = np.arange(10, 14.25, 0.25)
|
||||
>>> y = np.arange(3, 12)
|
||||
|
||||
>>> integrate.romb(y)
|
||||
56.0
|
||||
|
||||
>>> y = np.sin(np.power(x, 2.5))
|
||||
>>> integrate.romb(y)
|
||||
-0.742561336672229
|
||||
|
||||
>>> integrate.romb(y, show=True)
|
||||
Richardson Extrapolation Table for Romberg Integration
|
||||
====================================================================
|
||||
-0.81576
|
||||
4.63862 6.45674
|
||||
-1.10581 -3.02062 -3.65245
|
||||
-2.57379 -3.06311 -3.06595 -3.05664
|
||||
-1.34093 -0.92997 -0.78776 -0.75160 -0.74256
|
||||
====================================================================
|
||||
-0.742561336672229
|
||||
"""
|
||||
y = np.asarray(y)
|
||||
nd = len(y.shape)
|
||||
Nsamps = y.shape[axis]
|
||||
Ninterv = Nsamps-1
|
||||
n = 1
|
||||
k = 0
|
||||
while n < Ninterv:
|
||||
n <<= 1
|
||||
k += 1
|
||||
if n != Ninterv:
|
||||
raise ValueError("Number of samples must be one plus a "
|
||||
"non-negative power of 2.")
|
||||
|
||||
R = {}
|
||||
slice_all = (slice(None),) * nd
|
||||
slice0 = tupleset(slice_all, axis, 0)
|
||||
slicem1 = tupleset(slice_all, axis, -1)
|
||||
h = Ninterv * np.asarray(dx, dtype=float)
|
||||
R[(0, 0)] = (y[slice0] + y[slicem1])/2.0*h
|
||||
slice_R = slice_all
|
||||
start = stop = step = Ninterv
|
||||
for i in range(1, k+1):
|
||||
start >>= 1
|
||||
slice_R = tupleset(slice_R, axis, slice(start, stop, step))
|
||||
step >>= 1
|
||||
R[(i, 0)] = 0.5*(R[(i-1, 0)] + h*y[slice_R].sum(axis=axis))
|
||||
for j in range(1, i+1):
|
||||
prev = R[(i, j-1)]
|
||||
R[(i, j)] = prev + (prev-R[(i-1, j-1)]) / ((1 << (2*j))-1)
|
||||
h /= 2.0
|
||||
|
||||
if show:
|
||||
if not np.isscalar(R[(0, 0)]):
|
||||
print("*** Printing table only supported for integrals" +
|
||||
" of a single data set.")
|
||||
else:
|
||||
try:
|
||||
precis = show[0]
|
||||
except (TypeError, IndexError):
|
||||
precis = 5
|
||||
try:
|
||||
width = show[1]
|
||||
except (TypeError, IndexError):
|
||||
width = 8
|
||||
formstr = "%%%d.%df" % (width, precis)
|
||||
|
||||
title = "Richardson Extrapolation Table for Romberg Integration"
|
||||
print("", title.center(68), "=" * 68, sep="\n", end="\n")
|
||||
for i in range(k+1):
|
||||
for j in range(i+1):
|
||||
print(formstr % R[(i, j)], end=" ")
|
||||
print()
|
||||
print("=" * 68)
|
||||
print()
|
||||
|
||||
return R[(k, k)]
|
||||
|
||||
# Romberg quadratures for numeric integration.
|
||||
#
|
||||
# Written by Scott M. Ransom <ransom@cfa.harvard.edu>
|
||||
# last revision: 14 Nov 98
|
||||
#
|
||||
# Cosmetic changes by Konrad Hinsen <hinsen@cnrs-orleans.fr>
|
||||
# last revision: 1999-7-21
|
||||
#
|
||||
# Adapted to SciPy by Travis Oliphant <oliphant.travis@ieee.org>
|
||||
# last revision: Dec 2001
|
||||
|
||||
|
||||
def _difftrap(function, interval, numtraps):
|
||||
"""
|
||||
Perform part of the trapezoidal rule to integrate a function.
|
||||
Assume that we had called difftrap with all lower powers-of-2
|
||||
starting with 1. Calling difftrap only returns the summation
|
||||
of the new ordinates. It does _not_ multiply by the width
|
||||
of the trapezoids. This must be performed by the caller.
|
||||
'function' is the function to evaluate (must accept vector arguments).
|
||||
'interval' is a sequence with lower and upper limits
|
||||
of integration.
|
||||
'numtraps' is the number of trapezoids to use (must be a
|
||||
power-of-2).
|
||||
"""
|
||||
if numtraps <= 0:
|
||||
raise ValueError("numtraps must be > 0 in difftrap().")
|
||||
elif numtraps == 1:
|
||||
return 0.5*(function(interval[0])+function(interval[1]))
|
||||
else:
|
||||
numtosum = numtraps/2
|
||||
h = float(interval[1]-interval[0])/numtosum
|
||||
lox = interval[0] + 0.5 * h
|
||||
points = lox + h * np.arange(numtosum)
|
||||
s = np.sum(function(points), axis=0)
|
||||
return s
|
||||
|
||||
|
||||
def _romberg_diff(b, c, k):
|
||||
"""
|
||||
Compute the differences for the Romberg quadrature corrections.
|
||||
See Forman Acton's "Real Computing Made Real," p 143.
|
||||
"""
|
||||
tmp = 4.0**k
|
||||
return (tmp * c - b)/(tmp - 1.0)
|
||||
|
||||
|
||||
def _printresmat(function, interval, resmat):
|
||||
# Print the Romberg result matrix.
|
||||
i = j = 0
|
||||
print('Romberg integration of', repr(function), end=' ')
|
||||
print('from', interval)
|
||||
print('')
|
||||
print('%6s %9s %9s' % ('Steps', 'StepSize', 'Results'))
|
||||
for i in range(len(resmat)):
|
||||
print('%6d %9f' % (2**i, (interval[1]-interval[0])/(2.**i)), end=' ')
|
||||
for j in range(i+1):
|
||||
print('%9f' % (resmat[i][j]), end=' ')
|
||||
print('')
|
||||
print('')
|
||||
print('The final result is', resmat[i][j], end=' ')
|
||||
print('after', 2**(len(resmat)-1)+1, 'function evaluations.')
|
||||
|
||||
|
||||
def romberg(function, a, b, args=(), tol=1.48e-8, rtol=1.48e-8, show=False,
|
||||
divmax=10, vec_func=False):
|
||||
"""
|
||||
Romberg integration of a callable function or method.
|
||||
|
||||
Returns the integral of `function` (a function of one variable)
|
||||
over the interval (`a`, `b`).
|
||||
|
||||
If `show` is 1, the triangular array of the intermediate results
|
||||
will be printed. If `vec_func` is True (default is False), then
|
||||
`function` is assumed to support vector arguments.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
function : callable
|
||||
Function to be integrated.
|
||||
a : float
|
||||
Lower limit of integration.
|
||||
b : float
|
||||
Upper limit of integration.
|
||||
|
||||
Returns
|
||||
-------
|
||||
results : float
|
||||
Result of the integration.
|
||||
|
||||
Other Parameters
|
||||
----------------
|
||||
args : tuple, optional
|
||||
Extra arguments to pass to function. Each element of `args` will
|
||||
be passed as a single argument to `func`. Default is to pass no
|
||||
extra arguments.
|
||||
tol, rtol : float, optional
|
||||
The desired absolute and relative tolerances. Defaults are 1.48e-8.
|
||||
show : bool, optional
|
||||
Whether to print the results. Default is False.
|
||||
divmax : int, optional
|
||||
Maximum order of extrapolation. Default is 10.
|
||||
vec_func : bool, optional
|
||||
Whether `func` handles arrays as arguments (i.e., whether it is a
|
||||
"vector" function). Default is False.
|
||||
|
||||
See Also
|
||||
--------
|
||||
fixed_quad : Fixed-order Gaussian quadrature.
|
||||
quad : Adaptive quadrature using QUADPACK.
|
||||
dblquad : Double integrals.
|
||||
tplquad : Triple integrals.
|
||||
romb : Integrators for sampled data.
|
||||
simps : Integrators for sampled data.
|
||||
cumtrapz : Cumulative integration for sampled data.
|
||||
ode : ODE integrator.
|
||||
odeint : ODE integrator.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] 'Romberg's method' https://en.wikipedia.org/wiki/Romberg%27s_method
|
||||
|
||||
Examples
|
||||
--------
|
||||
Integrate a gaussian from 0 to 1 and compare to the error function.
|
||||
|
||||
>>> from scipy import integrate
|
||||
>>> from scipy.special import erf
|
||||
>>> gaussian = lambda x: 1/np.sqrt(np.pi) * np.exp(-x**2)
|
||||
>>> result = integrate.romberg(gaussian, 0, 1, show=True)
|
||||
Romberg integration of <function vfunc at ...> from [0, 1]
|
||||
|
||||
::
|
||||
|
||||
Steps StepSize Results
|
||||
1 1.000000 0.385872
|
||||
2 0.500000 0.412631 0.421551
|
||||
4 0.250000 0.419184 0.421368 0.421356
|
||||
8 0.125000 0.420810 0.421352 0.421350 0.421350
|
||||
16 0.062500 0.421215 0.421350 0.421350 0.421350 0.421350
|
||||
32 0.031250 0.421317 0.421350 0.421350 0.421350 0.421350 0.421350
|
||||
|
||||
The final result is 0.421350396475 after 33 function evaluations.
|
||||
|
||||
>>> print("%g %g" % (2*result, erf(1)))
|
||||
0.842701 0.842701
|
||||
|
||||
"""
|
||||
if np.isinf(a) or np.isinf(b):
|
||||
raise ValueError("Romberg integration only available "
|
||||
"for finite limits.")
|
||||
vfunc = vectorize1(function, args, vec_func=vec_func)
|
||||
n = 1
|
||||
interval = [a, b]
|
||||
intrange = b - a
|
||||
ordsum = _difftrap(vfunc, interval, n)
|
||||
result = intrange * ordsum
|
||||
resmat = [[result]]
|
||||
err = np.inf
|
||||
last_row = resmat[0]
|
||||
for i in range(1, divmax+1):
|
||||
n *= 2
|
||||
ordsum += _difftrap(vfunc, interval, n)
|
||||
row = [intrange * ordsum / n]
|
||||
for k in range(i):
|
||||
row.append(_romberg_diff(last_row[k], row[k], k+1))
|
||||
result = row[i]
|
||||
lastresult = last_row[i-1]
|
||||
if show:
|
||||
resmat.append(row)
|
||||
err = abs(result - lastresult)
|
||||
if err < tol or err < rtol * abs(result):
|
||||
break
|
||||
last_row = row
|
||||
else:
|
||||
warnings.warn(
|
||||
"divmax (%d) exceeded. Latest difference = %e" % (divmax, err),
|
||||
AccuracyWarning)
|
||||
|
||||
if show:
|
||||
_printresmat(vfunc, interval, resmat)
|
||||
return result
|
||||
|
||||
|
||||
# Coefficients for Newton-Cotes quadrature
|
||||
#
|
||||
# These are the points being used
|
||||
# to construct the local interpolating polynomial
|
||||
# a are the weights for Newton-Cotes integration
|
||||
# B is the error coefficient.
|
||||
# error in these coefficients grows as N gets larger.
|
||||
# or as samples are closer and closer together
|
||||
|
||||
# You can use maxima to find these rational coefficients
|
||||
# for equally spaced data using the commands
|
||||
# a(i,N) := integrate(product(r-j,j,0,i-1) * product(r-j,j,i+1,N),r,0,N) / ((N-i)! * i!) * (-1)^(N-i);
|
||||
# Be(N) := N^(N+2)/(N+2)! * (N/(N+3) - sum((i/N)^(N+2)*a(i,N),i,0,N));
|
||||
# Bo(N) := N^(N+1)/(N+1)! * (N/(N+2) - sum((i/N)^(N+1)*a(i,N),i,0,N));
|
||||
# B(N) := (if (mod(N,2)=0) then Be(N) else Bo(N));
|
||||
#
|
||||
# pre-computed for equally-spaced weights
|
||||
#
|
||||
# num_a, den_a, int_a, num_B, den_B = _builtincoeffs[N]
|
||||
#
|
||||
# a = num_a*array(int_a)/den_a
|
||||
# B = num_B*1.0 / den_B
|
||||
#
|
||||
# integrate(f(x),x,x_0,x_N) = dx*sum(a*f(x_i)) + B*(dx)^(2k+3) f^(2k+2)(x*)
|
||||
# where k = N // 2
|
||||
#
|
||||
_builtincoeffs = {
|
||||
1: (1,2,[1,1],-1,12),
|
||||
2: (1,3,[1,4,1],-1,90),
|
||||
3: (3,8,[1,3,3,1],-3,80),
|
||||
4: (2,45,[7,32,12,32,7],-8,945),
|
||||
5: (5,288,[19,75,50,50,75,19],-275,12096),
|
||||
6: (1,140,[41,216,27,272,27,216,41],-9,1400),
|
||||
7: (7,17280,[751,3577,1323,2989,2989,1323,3577,751],-8183,518400),
|
||||
8: (4,14175,[989,5888,-928,10496,-4540,10496,-928,5888,989],
|
||||
-2368,467775),
|
||||
9: (9,89600,[2857,15741,1080,19344,5778,5778,19344,1080,
|
||||
15741,2857], -4671, 394240),
|
||||
10: (5,299376,[16067,106300,-48525,272400,-260550,427368,
|
||||
-260550,272400,-48525,106300,16067],
|
||||
-673175, 163459296),
|
||||
11: (11,87091200,[2171465,13486539,-3237113, 25226685,-9595542,
|
||||
15493566,15493566,-9595542,25226685,-3237113,
|
||||
13486539,2171465], -2224234463, 237758976000),
|
||||
12: (1, 5255250, [1364651,9903168,-7587864,35725120,-51491295,
|
||||
87516288,-87797136,87516288,-51491295,35725120,
|
||||
-7587864,9903168,1364651], -3012, 875875),
|
||||
13: (13, 402361344000,[8181904909, 56280729661, -31268252574,
|
||||
156074417954,-151659573325,206683437987,
|
||||
-43111992612,-43111992612,206683437987,
|
||||
-151659573325,156074417954,-31268252574,
|
||||
56280729661,8181904909], -2639651053,
|
||||
344881152000),
|
||||
14: (7, 2501928000, [90241897,710986864,-770720657,3501442784,
|
||||
-6625093363,12630121616,-16802270373,19534438464,
|
||||
-16802270373,12630121616,-6625093363,3501442784,
|
||||
-770720657,710986864,90241897], -3740727473,
|
||||
1275983280000)
|
||||
}
|
||||
|
||||
|
||||
def newton_cotes(rn, equal=0):
|
||||
r"""
|
||||
Return weights and error coefficient for Newton-Cotes integration.
|
||||
|
||||
Suppose we have (N+1) samples of f at the positions
|
||||
x_0, x_1, ..., x_N. Then an N-point Newton-Cotes formula for the
|
||||
integral between x_0 and x_N is:
|
||||
|
||||
:math:`\int_{x_0}^{x_N} f(x)dx = \Delta x \sum_{i=0}^{N} a_i f(x_i)
|
||||
+ B_N (\Delta x)^{N+2} f^{N+1} (\xi)`
|
||||
|
||||
where :math:`\xi \in [x_0,x_N]`
|
||||
and :math:`\Delta x = \frac{x_N-x_0}{N}` is the average samples spacing.
|
||||
|
||||
If the samples are equally-spaced and N is even, then the error
|
||||
term is :math:`B_N (\Delta x)^{N+3} f^{N+2}(\xi)`.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
rn : int
|
||||
The integer order for equally-spaced data or the relative positions of
|
||||
the samples with the first sample at 0 and the last at N, where N+1 is
|
||||
the length of `rn`. N is the order of the Newton-Cotes integration.
|
||||
equal : int, optional
|
||||
Set to 1 to enforce equally spaced data.
|
||||
|
||||
Returns
|
||||
-------
|
||||
an : ndarray
|
||||
1-D array of weights to apply to the function at the provided sample
|
||||
positions.
|
||||
B : float
|
||||
Error coefficient.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Compute the integral of sin(x) in [0, :math:`\pi`]:
|
||||
|
||||
>>> from scipy.integrate import newton_cotes
|
||||
>>> def f(x):
|
||||
... return np.sin(x)
|
||||
>>> a = 0
|
||||
>>> b = np.pi
|
||||
>>> exact = 2
|
||||
>>> for N in [2, 4, 6, 8, 10]:
|
||||
... x = np.linspace(a, b, N + 1)
|
||||
... an, B = newton_cotes(N, 1)
|
||||
... dx = (b - a) / N
|
||||
... quad = dx * np.sum(an * f(x))
|
||||
... error = abs(quad - exact)
|
||||
... print('{:2d} {:10.9f} {:.5e}'.format(N, quad, error))
|
||||
...
|
||||
2 2.094395102 9.43951e-02
|
||||
4 1.998570732 1.42927e-03
|
||||
6 2.000017814 1.78136e-05
|
||||
8 1.999999835 1.64725e-07
|
||||
10 2.000000001 1.14677e-09
|
||||
|
||||
Notes
|
||||
-----
|
||||
Normally, the Newton-Cotes rules are used on smaller integration
|
||||
regions and a composite rule is used to return the total integral.
|
||||
|
||||
"""
|
||||
try:
|
||||
N = len(rn)-1
|
||||
if equal:
|
||||
rn = np.arange(N+1)
|
||||
elif np.all(np.diff(rn) == 1):
|
||||
equal = 1
|
||||
except Exception:
|
||||
N = rn
|
||||
rn = np.arange(N+1)
|
||||
equal = 1
|
||||
|
||||
if equal and N in _builtincoeffs:
|
||||
na, da, vi, nb, db = _builtincoeffs[N]
|
||||
an = na * np.array(vi, dtype=float) / da
|
||||
return an, float(nb)/db
|
||||
|
||||
if (rn[0] != 0) or (rn[-1] != N):
|
||||
raise ValueError("The sample positions must start at 0"
|
||||
" and end at N")
|
||||
yi = rn / float(N)
|
||||
ti = 2 * yi - 1
|
||||
nvec = np.arange(N+1)
|
||||
C = ti ** nvec[:, np.newaxis]
|
||||
Cinv = np.linalg.inv(C)
|
||||
# improve precision of result
|
||||
for i in range(2):
|
||||
Cinv = 2*Cinv - Cinv.dot(C).dot(Cinv)
|
||||
vec = 2.0 / (nvec[::2]+1)
|
||||
ai = Cinv[:, ::2].dot(vec) * (N / 2.)
|
||||
|
||||
if (N % 2 == 0) and equal:
|
||||
BN = N/(N+3.)
|
||||
power = N+2
|
||||
else:
|
||||
BN = N/(N+2.)
|
||||
power = N+1
|
||||
|
||||
BN = BN - np.dot(yi**power, ai)
|
||||
p1 = power+1
|
||||
fac = power*math.log(N) - gammaln(p1)
|
||||
fac = math.exp(fac)
|
||||
return ai, BN*fac
|
Binary file not shown.
Binary file not shown.
BIN
venv/Lib/site-packages/scipy/integrate/lsoda.cp36-win32.pyd
Normal file
BIN
venv/Lib/site-packages/scipy/integrate/lsoda.cp36-win32.pyd
Normal file
Binary file not shown.
259
venv/Lib/site-packages/scipy/integrate/odepack.py
Normal file
259
venv/Lib/site-packages/scipy/integrate/odepack.py
Normal file
|
@ -0,0 +1,259 @@
|
|||
# Author: Travis Oliphant
|
||||
|
||||
__all__ = ['odeint']
|
||||
|
||||
import numpy as np
|
||||
from . import _odepack
|
||||
from copy import copy
|
||||
import warnings
|
||||
|
||||
|
||||
class ODEintWarning(Warning):
|
||||
pass
|
||||
|
||||
|
||||
_msgs = {2: "Integration successful.",
|
||||
1: "Nothing was done; the integration time was 0.",
|
||||
-1: "Excess work done on this call (perhaps wrong Dfun type).",
|
||||
-2: "Excess accuracy requested (tolerances too small).",
|
||||
-3: "Illegal input detected (internal error).",
|
||||
-4: "Repeated error test failures (internal error).",
|
||||
-5: "Repeated convergence failures (perhaps bad Jacobian or tolerances).",
|
||||
-6: "Error weight became zero during problem.",
|
||||
-7: "Internal workspace insufficient to finish (internal error).",
|
||||
-8: "Run terminated (internal error)."
|
||||
}
|
||||
|
||||
|
||||
def odeint(func, y0, t, args=(), Dfun=None, col_deriv=0, full_output=0,
|
||||
ml=None, mu=None, rtol=None, atol=None, tcrit=None, h0=0.0,
|
||||
hmax=0.0, hmin=0.0, ixpr=0, mxstep=0, mxhnil=0, mxordn=12,
|
||||
mxords=5, printmessg=0, tfirst=False):
|
||||
"""
|
||||
Integrate a system of ordinary differential equations.
|
||||
|
||||
.. note:: For new code, use `scipy.integrate.solve_ivp` to solve a
|
||||
differential equation.
|
||||
|
||||
Solve a system of ordinary differential equations using lsoda from the
|
||||
FORTRAN library odepack.
|
||||
|
||||
Solves the initial value problem for stiff or non-stiff systems
|
||||
of first order ode-s::
|
||||
|
||||
dy/dt = func(y, t, ...) [or func(t, y, ...)]
|
||||
|
||||
where y can be a vector.
|
||||
|
||||
.. note:: By default, the required order of the first two arguments of
|
||||
`func` are in the opposite order of the arguments in the system
|
||||
definition function used by the `scipy.integrate.ode` class and
|
||||
the function `scipy.integrate.solve_ivp`. To use a function with
|
||||
the signature ``func(t, y, ...)``, the argument `tfirst` must be
|
||||
set to ``True``.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
func : callable(y, t, ...) or callable(t, y, ...)
|
||||
Computes the derivative of y at t.
|
||||
If the signature is ``callable(t, y, ...)``, then the argument
|
||||
`tfirst` must be set ``True``.
|
||||
y0 : array
|
||||
Initial condition on y (can be a vector).
|
||||
t : array
|
||||
A sequence of time points for which to solve for y. The initial
|
||||
value point should be the first element of this sequence.
|
||||
This sequence must be monotonically increasing or monotonically
|
||||
decreasing; repeated values are allowed.
|
||||
args : tuple, optional
|
||||
Extra arguments to pass to function.
|
||||
Dfun : callable(y, t, ...) or callable(t, y, ...)
|
||||
Gradient (Jacobian) of `func`.
|
||||
If the signature is ``callable(t, y, ...)``, then the argument
|
||||
`tfirst` must be set ``True``.
|
||||
col_deriv : bool, optional
|
||||
True if `Dfun` defines derivatives down columns (faster),
|
||||
otherwise `Dfun` should define derivatives across rows.
|
||||
full_output : bool, optional
|
||||
True if to return a dictionary of optional outputs as the second output
|
||||
printmessg : bool, optional
|
||||
Whether to print the convergence message
|
||||
tfirst: bool, optional
|
||||
If True, the first two arguments of `func` (and `Dfun`, if given)
|
||||
must ``t, y`` instead of the default ``y, t``.
|
||||
|
||||
.. versionadded:: 1.1.0
|
||||
|
||||
Returns
|
||||
-------
|
||||
y : array, shape (len(t), len(y0))
|
||||
Array containing the value of y for each desired time in t,
|
||||
with the initial value `y0` in the first row.
|
||||
infodict : dict, only returned if full_output == True
|
||||
Dictionary containing additional output information
|
||||
|
||||
======= ============================================================
|
||||
key meaning
|
||||
======= ============================================================
|
||||
'hu' vector of step sizes successfully used for each time step
|
||||
'tcur' vector with the value of t reached for each time step
|
||||
(will always be at least as large as the input times)
|
||||
'tolsf' vector of tolerance scale factors, greater than 1.0,
|
||||
computed when a request for too much accuracy was detected
|
||||
'tsw' value of t at the time of the last method switch
|
||||
(given for each time step)
|
||||
'nst' cumulative number of time steps
|
||||
'nfe' cumulative number of function evaluations for each time step
|
||||
'nje' cumulative number of jacobian evaluations for each time step
|
||||
'nqu' a vector of method orders for each successful step
|
||||
'imxer' index of the component of largest magnitude in the
|
||||
weighted local error vector (e / ewt) on an error return, -1
|
||||
otherwise
|
||||
'lenrw' the length of the double work array required
|
||||
'leniw' the length of integer work array required
|
||||
'mused' a vector of method indicators for each successful time step:
|
||||
1: adams (nonstiff), 2: bdf (stiff)
|
||||
======= ============================================================
|
||||
|
||||
Other Parameters
|
||||
----------------
|
||||
ml, mu : int, optional
|
||||
If either of these are not None or non-negative, then the
|
||||
Jacobian is assumed to be banded. These give the number of
|
||||
lower and upper non-zero diagonals in this banded matrix.
|
||||
For the banded case, `Dfun` should return a matrix whose
|
||||
rows contain the non-zero bands (starting with the lowest diagonal).
|
||||
Thus, the return matrix `jac` from `Dfun` should have shape
|
||||
``(ml + mu + 1, len(y0))`` when ``ml >=0`` or ``mu >=0``.
|
||||
The data in `jac` must be stored such that ``jac[i - j + mu, j]``
|
||||
holds the derivative of the `i`th equation with respect to the `j`th
|
||||
state variable. If `col_deriv` is True, the transpose of this
|
||||
`jac` must be returned.
|
||||
rtol, atol : float, optional
|
||||
The input parameters `rtol` and `atol` determine the error
|
||||
control performed by the solver. The solver will control the
|
||||
vector, e, of estimated local errors in y, according to an
|
||||
inequality of the form ``max-norm of (e / ewt) <= 1``,
|
||||
where ewt is a vector of positive error weights computed as
|
||||
``ewt = rtol * abs(y) + atol``.
|
||||
rtol and atol can be either vectors the same length as y or scalars.
|
||||
Defaults to 1.49012e-8.
|
||||
tcrit : ndarray, optional
|
||||
Vector of critical points (e.g., singularities) where integration
|
||||
care should be taken.
|
||||
h0 : float, (0: solver-determined), optional
|
||||
The step size to be attempted on the first step.
|
||||
hmax : float, (0: solver-determined), optional
|
||||
The maximum absolute step size allowed.
|
||||
hmin : float, (0: solver-determined), optional
|
||||
The minimum absolute step size allowed.
|
||||
ixpr : bool, optional
|
||||
Whether to generate extra printing at method switches.
|
||||
mxstep : int, (0: solver-determined), optional
|
||||
Maximum number of (internally defined) steps allowed for each
|
||||
integration point in t.
|
||||
mxhnil : int, (0: solver-determined), optional
|
||||
Maximum number of messages printed.
|
||||
mxordn : int, (0: solver-determined), optional
|
||||
Maximum order to be allowed for the non-stiff (Adams) method.
|
||||
mxords : int, (0: solver-determined), optional
|
||||
Maximum order to be allowed for the stiff (BDF) method.
|
||||
|
||||
See Also
|
||||
--------
|
||||
solve_ivp : solve an initial value problem for a system of ODEs
|
||||
ode : a more object-oriented integrator based on VODE
|
||||
quad : for finding the area under a curve
|
||||
|
||||
Examples
|
||||
--------
|
||||
The second order differential equation for the angle `theta` of a
|
||||
pendulum acted on by gravity with friction can be written::
|
||||
|
||||
theta''(t) + b*theta'(t) + c*sin(theta(t)) = 0
|
||||
|
||||
where `b` and `c` are positive constants, and a prime (') denotes a
|
||||
derivative. To solve this equation with `odeint`, we must first convert
|
||||
it to a system of first order equations. By defining the angular
|
||||
velocity ``omega(t) = theta'(t)``, we obtain the system::
|
||||
|
||||
theta'(t) = omega(t)
|
||||
omega'(t) = -b*omega(t) - c*sin(theta(t))
|
||||
|
||||
Let `y` be the vector [`theta`, `omega`]. We implement this system
|
||||
in Python as:
|
||||
|
||||
>>> def pend(y, t, b, c):
|
||||
... theta, omega = y
|
||||
... dydt = [omega, -b*omega - c*np.sin(theta)]
|
||||
... return dydt
|
||||
...
|
||||
|
||||
We assume the constants are `b` = 0.25 and `c` = 5.0:
|
||||
|
||||
>>> b = 0.25
|
||||
>>> c = 5.0
|
||||
|
||||
For initial conditions, we assume the pendulum is nearly vertical
|
||||
with `theta(0)` = `pi` - 0.1, and is initially at rest, so
|
||||
`omega(0)` = 0. Then the vector of initial conditions is
|
||||
|
||||
>>> y0 = [np.pi - 0.1, 0.0]
|
||||
|
||||
We will generate a solution at 101 evenly spaced samples in the interval
|
||||
0 <= `t` <= 10. So our array of times is:
|
||||
|
||||
>>> t = np.linspace(0, 10, 101)
|
||||
|
||||
Call `odeint` to generate the solution. To pass the parameters
|
||||
`b` and `c` to `pend`, we give them to `odeint` using the `args`
|
||||
argument.
|
||||
|
||||
>>> from scipy.integrate import odeint
|
||||
>>> sol = odeint(pend, y0, t, args=(b, c))
|
||||
|
||||
The solution is an array with shape (101, 2). The first column
|
||||
is `theta(t)`, and the second is `omega(t)`. The following code
|
||||
plots both components.
|
||||
|
||||
>>> import matplotlib.pyplot as plt
|
||||
>>> plt.plot(t, sol[:, 0], 'b', label='theta(t)')
|
||||
>>> plt.plot(t, sol[:, 1], 'g', label='omega(t)')
|
||||
>>> plt.legend(loc='best')
|
||||
>>> plt.xlabel('t')
|
||||
>>> plt.grid()
|
||||
>>> plt.show()
|
||||
"""
|
||||
|
||||
if ml is None:
|
||||
ml = -1 # changed to zero inside function call
|
||||
if mu is None:
|
||||
mu = -1 # changed to zero inside function call
|
||||
|
||||
dt = np.diff(t)
|
||||
if not((dt >= 0).all() or (dt <= 0).all()):
|
||||
raise ValueError("The values in t must be monotonically increasing "
|
||||
"or monotonically decreasing; repeated values are "
|
||||
"allowed.")
|
||||
|
||||
t = copy(t)
|
||||
y0 = copy(y0)
|
||||
output = _odepack.odeint(func, y0, t, args, Dfun, col_deriv, ml, mu,
|
||||
full_output, rtol, atol, tcrit, h0, hmax, hmin,
|
||||
ixpr, mxstep, mxhnil, mxordn, mxords,
|
||||
int(bool(tfirst)))
|
||||
if output[-1] < 0:
|
||||
warning_msg = _msgs[output[-1]] + " Run with full_output = 1 to get quantitative information."
|
||||
warnings.warn(warning_msg, ODEintWarning)
|
||||
elif printmessg:
|
||||
warning_msg = _msgs[output[-1]]
|
||||
warnings.warn(warning_msg, ODEintWarning)
|
||||
|
||||
if full_output:
|
||||
output[1]['message'] = _msgs[output[-1]]
|
||||
|
||||
output = output[:-1]
|
||||
if len(output) == 1:
|
||||
return output[0]
|
||||
else:
|
||||
return output
|
899
venv/Lib/site-packages/scipy/integrate/quadpack.py
Normal file
899
venv/Lib/site-packages/scipy/integrate/quadpack.py
Normal file
|
@ -0,0 +1,899 @@
|
|||
# Author: Travis Oliphant 2001
|
||||
# Author: Nathan Woods 2013 (nquad &c)
|
||||
import sys
|
||||
import warnings
|
||||
from functools import partial
|
||||
|
||||
from . import _quadpack
|
||||
import numpy
|
||||
from numpy import Inf
|
||||
|
||||
__all__ = ['quad', 'dblquad', 'tplquad', 'nquad', 'quad_explain',
|
||||
'IntegrationWarning']
|
||||
|
||||
|
||||
error = _quadpack.error
|
||||
|
||||
class IntegrationWarning(UserWarning):
|
||||
"""
|
||||
Warning on issues during integration.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
def quad_explain(output=sys.stdout):
|
||||
"""
|
||||
Print extra information about integrate.quad() parameters and returns.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
output : instance with "write" method, optional
|
||||
Information about `quad` is passed to ``output.write()``.
|
||||
Default is ``sys.stdout``.
|
||||
|
||||
Returns
|
||||
-------
|
||||
None
|
||||
|
||||
Examples
|
||||
--------
|
||||
We can show detailed information of the `integrate.quad` function in stdout:
|
||||
|
||||
>>> from scipy.integrate import quad_explain
|
||||
>>> quad_explain()
|
||||
|
||||
"""
|
||||
output.write(quad.__doc__)
|
||||
|
||||
|
||||
def quad(func, a, b, args=(), full_output=0, epsabs=1.49e-8, epsrel=1.49e-8,
|
||||
limit=50, points=None, weight=None, wvar=None, wopts=None, maxp1=50,
|
||||
limlst=50):
|
||||
"""
|
||||
Compute a definite integral.
|
||||
|
||||
Integrate func from `a` to `b` (possibly infinite interval) using a
|
||||
technique from the Fortran library QUADPACK.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
func : {function, scipy.LowLevelCallable}
|
||||
A Python function or method to integrate. If `func` takes many
|
||||
arguments, it is integrated along the axis corresponding to the
|
||||
first argument.
|
||||
|
||||
If the user desires improved integration performance, then `f` may
|
||||
be a `scipy.LowLevelCallable` with one of the signatures::
|
||||
|
||||
double func(double x)
|
||||
double func(double x, void *user_data)
|
||||
double func(int n, double *xx)
|
||||
double func(int n, double *xx, void *user_data)
|
||||
|
||||
The ``user_data`` is the data contained in the `scipy.LowLevelCallable`.
|
||||
In the call forms with ``xx``, ``n`` is the length of the ``xx``
|
||||
array which contains ``xx[0] == x`` and the rest of the items are
|
||||
numbers contained in the ``args`` argument of quad.
|
||||
|
||||
In addition, certain ctypes call signatures are supported for
|
||||
backward compatibility, but those should not be used in new code.
|
||||
a : float
|
||||
Lower limit of integration (use -numpy.inf for -infinity).
|
||||
b : float
|
||||
Upper limit of integration (use numpy.inf for +infinity).
|
||||
args : tuple, optional
|
||||
Extra arguments to pass to `func`.
|
||||
full_output : int, optional
|
||||
Non-zero to return a dictionary of integration information.
|
||||
If non-zero, warning messages are also suppressed and the
|
||||
message is appended to the output tuple.
|
||||
|
||||
Returns
|
||||
-------
|
||||
y : float
|
||||
The integral of func from `a` to `b`.
|
||||
abserr : float
|
||||
An estimate of the absolute error in the result.
|
||||
infodict : dict
|
||||
A dictionary containing additional information.
|
||||
Run scipy.integrate.quad_explain() for more information.
|
||||
message
|
||||
A convergence message.
|
||||
explain
|
||||
Appended only with 'cos' or 'sin' weighting and infinite
|
||||
integration limits, it contains an explanation of the codes in
|
||||
infodict['ierlst']
|
||||
|
||||
Other Parameters
|
||||
----------------
|
||||
epsabs : float or int, optional
|
||||
Absolute error tolerance. Default is 1.49e-8. `quad` tries to obtain
|
||||
an accuracy of ``abs(i-result) <= max(epsabs, epsrel*abs(i))``
|
||||
where ``i`` = integral of `func` from `a` to `b`, and ``result`` is the
|
||||
numerical approximation. See `epsrel` below.
|
||||
epsrel : float or int, optional
|
||||
Relative error tolerance. Default is 1.49e-8.
|
||||
If ``epsabs <= 0``, `epsrel` must be greater than both 5e-29
|
||||
and ``50 * (machine epsilon)``. See `epsabs` above.
|
||||
limit : float or int, optional
|
||||
An upper bound on the number of subintervals used in the adaptive
|
||||
algorithm.
|
||||
points : (sequence of floats,ints), optional
|
||||
A sequence of break points in the bounded integration interval
|
||||
where local difficulties of the integrand may occur (e.g.,
|
||||
singularities, discontinuities). The sequence does not have
|
||||
to be sorted. Note that this option cannot be used in conjunction
|
||||
with ``weight``.
|
||||
weight : float or int, optional
|
||||
String indicating weighting function. Full explanation for this
|
||||
and the remaining arguments can be found below.
|
||||
wvar : optional
|
||||
Variables for use with weighting functions.
|
||||
wopts : optional
|
||||
Optional input for reusing Chebyshev moments.
|
||||
maxp1 : float or int, optional
|
||||
An upper bound on the number of Chebyshev moments.
|
||||
limlst : int, optional
|
||||
Upper bound on the number of cycles (>=3) for use with a sinusoidal
|
||||
weighting and an infinite end-point.
|
||||
|
||||
See Also
|
||||
--------
|
||||
dblquad : double integral
|
||||
tplquad : triple integral
|
||||
nquad : n-dimensional integrals (uses `quad` recursively)
|
||||
fixed_quad : fixed-order Gaussian quadrature
|
||||
quadrature : adaptive Gaussian quadrature
|
||||
odeint : ODE integrator
|
||||
ode : ODE integrator
|
||||
simps : integrator for sampled data
|
||||
romb : integrator for sampled data
|
||||
scipy.special : for coefficients and roots of orthogonal polynomials
|
||||
|
||||
Notes
|
||||
-----
|
||||
|
||||
**Extra information for quad() inputs and outputs**
|
||||
|
||||
If full_output is non-zero, then the third output argument
|
||||
(infodict) is a dictionary with entries as tabulated below. For
|
||||
infinite limits, the range is transformed to (0,1) and the
|
||||
optional outputs are given with respect to this transformed range.
|
||||
Let M be the input argument limit and let K be infodict['last'].
|
||||
The entries are:
|
||||
|
||||
'neval'
|
||||
The number of function evaluations.
|
||||
'last'
|
||||
The number, K, of subintervals produced in the subdivision process.
|
||||
'alist'
|
||||
A rank-1 array of length M, the first K elements of which are the
|
||||
left end points of the subintervals in the partition of the
|
||||
integration range.
|
||||
'blist'
|
||||
A rank-1 array of length M, the first K elements of which are the
|
||||
right end points of the subintervals.
|
||||
'rlist'
|
||||
A rank-1 array of length M, the first K elements of which are the
|
||||
integral approximations on the subintervals.
|
||||
'elist'
|
||||
A rank-1 array of length M, the first K elements of which are the
|
||||
moduli of the absolute error estimates on the subintervals.
|
||||
'iord'
|
||||
A rank-1 integer array of length M, the first L elements of
|
||||
which are pointers to the error estimates over the subintervals
|
||||
with ``L=K`` if ``K<=M/2+2`` or ``L=M+1-K`` otherwise. Let I be the
|
||||
sequence ``infodict['iord']`` and let E be the sequence
|
||||
``infodict['elist']``. Then ``E[I[1]], ..., E[I[L]]`` forms a
|
||||
decreasing sequence.
|
||||
|
||||
If the input argument points is provided (i.e., it is not None),
|
||||
the following additional outputs are placed in the output
|
||||
dictionary. Assume the points sequence is of length P.
|
||||
|
||||
'pts'
|
||||
A rank-1 array of length P+2 containing the integration limits
|
||||
and the break points of the intervals in ascending order.
|
||||
This is an array giving the subintervals over which integration
|
||||
will occur.
|
||||
'level'
|
||||
A rank-1 integer array of length M (=limit), containing the
|
||||
subdivision levels of the subintervals, i.e., if (aa,bb) is a
|
||||
subinterval of ``(pts[1], pts[2])`` where ``pts[0]`` and ``pts[2]``
|
||||
are adjacent elements of ``infodict['pts']``, then (aa,bb) has level l
|
||||
if ``|bb-aa| = |pts[2]-pts[1]| * 2**(-l)``.
|
||||
'ndin'
|
||||
A rank-1 integer array of length P+2. After the first integration
|
||||
over the intervals (pts[1], pts[2]), the error estimates over some
|
||||
of the intervals may have been increased artificially in order to
|
||||
put their subdivision forward. This array has ones in slots
|
||||
corresponding to the subintervals for which this happens.
|
||||
|
||||
**Weighting the integrand**
|
||||
|
||||
The input variables, *weight* and *wvar*, are used to weight the
|
||||
integrand by a select list of functions. Different integration
|
||||
methods are used to compute the integral with these weighting
|
||||
functions, and these do not support specifying break points. The
|
||||
possible values of weight and the corresponding weighting functions are.
|
||||
|
||||
========== =================================== =====================
|
||||
``weight`` Weight function used ``wvar``
|
||||
========== =================================== =====================
|
||||
'cos' cos(w*x) wvar = w
|
||||
'sin' sin(w*x) wvar = w
|
||||
'alg' g(x) = ((x-a)**alpha)*((b-x)**beta) wvar = (alpha, beta)
|
||||
'alg-loga' g(x)*log(x-a) wvar = (alpha, beta)
|
||||
'alg-logb' g(x)*log(b-x) wvar = (alpha, beta)
|
||||
'alg-log' g(x)*log(x-a)*log(b-x) wvar = (alpha, beta)
|
||||
'cauchy' 1/(x-c) wvar = c
|
||||
========== =================================== =====================
|
||||
|
||||
wvar holds the parameter w, (alpha, beta), or c depending on the weight
|
||||
selected. In these expressions, a and b are the integration limits.
|
||||
|
||||
For the 'cos' and 'sin' weighting, additional inputs and outputs are
|
||||
available.
|
||||
|
||||
For finite integration limits, the integration is performed using a
|
||||
Clenshaw-Curtis method which uses Chebyshev moments. For repeated
|
||||
calculations, these moments are saved in the output dictionary:
|
||||
|
||||
'momcom'
|
||||
The maximum level of Chebyshev moments that have been computed,
|
||||
i.e., if ``M_c`` is ``infodict['momcom']`` then the moments have been
|
||||
computed for intervals of length ``|b-a| * 2**(-l)``,
|
||||
``l=0,1,...,M_c``.
|
||||
'nnlog'
|
||||
A rank-1 integer array of length M(=limit), containing the
|
||||
subdivision levels of the subintervals, i.e., an element of this
|
||||
array is equal to l if the corresponding subinterval is
|
||||
``|b-a|* 2**(-l)``.
|
||||
'chebmo'
|
||||
A rank-2 array of shape (25, maxp1) containing the computed
|
||||
Chebyshev moments. These can be passed on to an integration
|
||||
over the same interval by passing this array as the second
|
||||
element of the sequence wopts and passing infodict['momcom'] as
|
||||
the first element.
|
||||
|
||||
If one of the integration limits is infinite, then a Fourier integral is
|
||||
computed (assuming w neq 0). If full_output is 1 and a numerical error
|
||||
is encountered, besides the error message attached to the output tuple,
|
||||
a dictionary is also appended to the output tuple which translates the
|
||||
error codes in the array ``info['ierlst']`` to English messages. The
|
||||
output information dictionary contains the following entries instead of
|
||||
'last', 'alist', 'blist', 'rlist', and 'elist':
|
||||
|
||||
'lst'
|
||||
The number of subintervals needed for the integration (call it ``K_f``).
|
||||
'rslst'
|
||||
A rank-1 array of length M_f=limlst, whose first ``K_f`` elements
|
||||
contain the integral contribution over the interval
|
||||
``(a+(k-1)c, a+kc)`` where ``c = (2*floor(|w|) + 1) * pi / |w|``
|
||||
and ``k=1,2,...,K_f``.
|
||||
'erlst'
|
||||
A rank-1 array of length ``M_f`` containing the error estimate
|
||||
corresponding to the interval in the same position in
|
||||
``infodict['rslist']``.
|
||||
'ierlst'
|
||||
A rank-1 integer array of length ``M_f`` containing an error flag
|
||||
corresponding to the interval in the same position in
|
||||
``infodict['rslist']``. See the explanation dictionary (last entry
|
||||
in the output tuple) for the meaning of the codes.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Calculate :math:`\\int^4_0 x^2 dx` and compare with an analytic result
|
||||
|
||||
>>> from scipy import integrate
|
||||
>>> x2 = lambda x: x**2
|
||||
>>> integrate.quad(x2, 0, 4)
|
||||
(21.333333333333332, 2.3684757858670003e-13)
|
||||
>>> print(4**3 / 3.) # analytical result
|
||||
21.3333333333
|
||||
|
||||
Calculate :math:`\\int^\\infty_0 e^{-x} dx`
|
||||
|
||||
>>> invexp = lambda x: np.exp(-x)
|
||||
>>> integrate.quad(invexp, 0, np.inf)
|
||||
(1.0, 5.842605999138044e-11)
|
||||
|
||||
>>> f = lambda x,a : a*x
|
||||
>>> y, err = integrate.quad(f, 0, 1, args=(1,))
|
||||
>>> y
|
||||
0.5
|
||||
>>> y, err = integrate.quad(f, 0, 1, args=(3,))
|
||||
>>> y
|
||||
1.5
|
||||
|
||||
Calculate :math:`\\int^1_0 x^2 + y^2 dx` with ctypes, holding
|
||||
y parameter as 1::
|
||||
|
||||
testlib.c =>
|
||||
double func(int n, double args[n]){
|
||||
return args[0]*args[0] + args[1]*args[1];}
|
||||
compile to library testlib.*
|
||||
|
||||
::
|
||||
|
||||
from scipy import integrate
|
||||
import ctypes
|
||||
lib = ctypes.CDLL('/home/.../testlib.*') #use absolute path
|
||||
lib.func.restype = ctypes.c_double
|
||||
lib.func.argtypes = (ctypes.c_int,ctypes.c_double)
|
||||
integrate.quad(lib.func,0,1,(1))
|
||||
#(1.3333333333333333, 1.4802973661668752e-14)
|
||||
print((1.0**3/3.0 + 1.0) - (0.0**3/3.0 + 0.0)) #Analytic result
|
||||
# 1.3333333333333333
|
||||
|
||||
Be aware that pulse shapes and other sharp features as compared to the
|
||||
size of the integration interval may not be integrated correctly using
|
||||
this method. A simplified example of this limitation is integrating a
|
||||
y-axis reflected step function with many zero values within the integrals
|
||||
bounds.
|
||||
|
||||
>>> y = lambda x: 1 if x<=0 else 0
|
||||
>>> integrate.quad(y, -1, 1)
|
||||
(1.0, 1.1102230246251565e-14)
|
||||
>>> integrate.quad(y, -1, 100)
|
||||
(1.0000000002199108, 1.0189464580163188e-08)
|
||||
>>> integrate.quad(y, -1, 10000)
|
||||
(0.0, 0.0)
|
||||
|
||||
"""
|
||||
if not isinstance(args, tuple):
|
||||
args = (args,)
|
||||
|
||||
# check the limits of integration: \int_a^b, expect a < b
|
||||
flip, a, b = b < a, min(a, b), max(a, b)
|
||||
|
||||
if weight is None:
|
||||
retval = _quad(func, a, b, args, full_output, epsabs, epsrel, limit,
|
||||
points)
|
||||
else:
|
||||
if points is not None:
|
||||
msg = ("Break points cannot be specified when using weighted integrand.\n"
|
||||
"Continuing, ignoring specified points.")
|
||||
warnings.warn(msg, IntegrationWarning, stacklevel=2)
|
||||
retval = _quad_weight(func, a, b, args, full_output, epsabs, epsrel,
|
||||
limlst, limit, maxp1, weight, wvar, wopts)
|
||||
|
||||
if flip:
|
||||
retval = (-retval[0],) + retval[1:]
|
||||
|
||||
ier = retval[-1]
|
||||
if ier == 0:
|
||||
return retval[:-1]
|
||||
|
||||
msgs = {80: "A Python error occurred possibly while calling the function.",
|
||||
1: "The maximum number of subdivisions (%d) has been achieved.\n If increasing the limit yields no improvement it is advised to analyze \n the integrand in order to determine the difficulties. If the position of a \n local difficulty can be determined (singularity, discontinuity) one will \n probably gain from splitting up the interval and calling the integrator \n on the subranges. Perhaps a special-purpose integrator should be used." % limit,
|
||||
2: "The occurrence of roundoff error is detected, which prevents \n the requested tolerance from being achieved. The error may be \n underestimated.",
|
||||
3: "Extremely bad integrand behavior occurs at some points of the\n integration interval.",
|
||||
4: "The algorithm does not converge. Roundoff error is detected\n in the extrapolation table. It is assumed that the requested tolerance\n cannot be achieved, and that the returned result (if full_output = 1) is \n the best which can be obtained.",
|
||||
5: "The integral is probably divergent, or slowly convergent.",
|
||||
6: "The input is invalid.",
|
||||
7: "Abnormal termination of the routine. The estimates for result\n and error are less reliable. It is assumed that the requested accuracy\n has not been achieved.",
|
||||
'unknown': "Unknown error."}
|
||||
|
||||
if weight in ['cos','sin'] and (b == Inf or a == -Inf):
|
||||
msgs[1] = "The maximum number of cycles allowed has been achieved., e.e.\n of subintervals (a+(k-1)c, a+kc) where c = (2*int(abs(omega)+1))\n *pi/abs(omega), for k = 1, 2, ..., lst. One can allow more cycles by increasing the value of limlst. Look at info['ierlst'] with full_output=1."
|
||||
msgs[4] = "The extrapolation table constructed for convergence acceleration\n of the series formed by the integral contributions over the cycles, \n does not converge to within the requested accuracy. Look at \n info['ierlst'] with full_output=1."
|
||||
msgs[7] = "Bad integrand behavior occurs within one or more of the cycles.\n Location and type of the difficulty involved can be determined from \n the vector info['ierlist'] obtained with full_output=1."
|
||||
explain = {1: "The maximum number of subdivisions (= limit) has been \n achieved on this cycle.",
|
||||
2: "The occurrence of roundoff error is detected and prevents\n the tolerance imposed on this cycle from being achieved.",
|
||||
3: "Extremely bad integrand behavior occurs at some points of\n this cycle.",
|
||||
4: "The integral over this cycle does not converge (to within the required accuracy) due to roundoff in the extrapolation procedure invoked on this cycle. It is assumed that the result on this interval is the best which can be obtained.",
|
||||
5: "The integral over this cycle is probably divergent or slowly convergent."}
|
||||
|
||||
try:
|
||||
msg = msgs[ier]
|
||||
except KeyError:
|
||||
msg = msgs['unknown']
|
||||
|
||||
if ier in [1,2,3,4,5,7]:
|
||||
if full_output:
|
||||
if weight in ['cos', 'sin'] and (b == Inf or a == -Inf):
|
||||
return retval[:-1] + (msg, explain)
|
||||
else:
|
||||
return retval[:-1] + (msg,)
|
||||
else:
|
||||
warnings.warn(msg, IntegrationWarning, stacklevel=2)
|
||||
return retval[:-1]
|
||||
|
||||
elif ier == 6: # Forensic decision tree when QUADPACK throws ier=6
|
||||
if epsabs <= 0: # Small error tolerance - applies to all methods
|
||||
if epsrel < max(50 * sys.float_info.epsilon, 5e-29):
|
||||
msg = ("If 'epsabs'<=0, 'epsrel' must be greater than both"
|
||||
" 5e-29 and 50*(machine epsilon).")
|
||||
elif weight in ['sin', 'cos'] and (abs(a) + abs(b) == Inf):
|
||||
msg = ("Sine or cosine weighted intergals with infinite domain"
|
||||
" must have 'epsabs'>0.")
|
||||
|
||||
elif weight is None:
|
||||
if points is None: # QAGSE/QAGIE
|
||||
msg = ("Invalid 'limit' argument. There must be"
|
||||
" at least one subinterval")
|
||||
else: # QAGPE
|
||||
if not (min(a, b) <= min(points) <= max(points) <= max(a, b)):
|
||||
msg = ("All break points in 'points' must lie within the"
|
||||
" integration limits.")
|
||||
elif len(points) >= limit:
|
||||
msg = ("Number of break points ({:d})"
|
||||
" must be less than subinterval"
|
||||
" limit ({:d})").format(len(points), limit)
|
||||
|
||||
else:
|
||||
if maxp1 < 1:
|
||||
msg = "Chebyshev moment limit maxp1 must be >=1."
|
||||
|
||||
elif weight in ('cos', 'sin') and abs(a+b) == Inf: # QAWFE
|
||||
msg = "Cycle limit limlst must be >=3."
|
||||
|
||||
elif weight.startswith('alg'): # QAWSE
|
||||
if min(wvar) < -1:
|
||||
msg = "wvar parameters (alpha, beta) must both be >= -1."
|
||||
if b < a:
|
||||
msg = "Integration limits a, b must satistfy a<b."
|
||||
|
||||
elif weight == 'cauchy' and wvar in (a, b):
|
||||
msg = ("Parameter 'wvar' must not equal"
|
||||
" integration limits 'a' or 'b'.")
|
||||
|
||||
raise ValueError(msg)
|
||||
|
||||
|
||||
def _quad(func,a,b,args,full_output,epsabs,epsrel,limit,points):
|
||||
infbounds = 0
|
||||
if (b != Inf and a != -Inf):
|
||||
pass # standard integration
|
||||
elif (b == Inf and a != -Inf):
|
||||
infbounds = 1
|
||||
bound = a
|
||||
elif (b == Inf and a == -Inf):
|
||||
infbounds = 2
|
||||
bound = 0 # ignored
|
||||
elif (b != Inf and a == -Inf):
|
||||
infbounds = -1
|
||||
bound = b
|
||||
else:
|
||||
raise RuntimeError("Infinity comparisons don't work for you.")
|
||||
|
||||
if points is None:
|
||||
if infbounds == 0:
|
||||
return _quadpack._qagse(func,a,b,args,full_output,epsabs,epsrel,limit)
|
||||
else:
|
||||
return _quadpack._qagie(func,bound,infbounds,args,full_output,epsabs,epsrel,limit)
|
||||
else:
|
||||
if infbounds != 0:
|
||||
raise ValueError("Infinity inputs cannot be used with break points.")
|
||||
else:
|
||||
#Duplicates force function evaluation at singular points
|
||||
the_points = numpy.unique(points)
|
||||
the_points = the_points[a < the_points]
|
||||
the_points = the_points[the_points < b]
|
||||
the_points = numpy.concatenate((the_points, (0., 0.)))
|
||||
return _quadpack._qagpe(func,a,b,the_points,args,full_output,epsabs,epsrel,limit)
|
||||
|
||||
|
||||
def _quad_weight(func,a,b,args,full_output,epsabs,epsrel,limlst,limit,maxp1,weight,wvar,wopts):
|
||||
if weight not in ['cos','sin','alg','alg-loga','alg-logb','alg-log','cauchy']:
|
||||
raise ValueError("%s not a recognized weighting function." % weight)
|
||||
|
||||
strdict = {'cos':1,'sin':2,'alg':1,'alg-loga':2,'alg-logb':3,'alg-log':4}
|
||||
|
||||
if weight in ['cos','sin']:
|
||||
integr = strdict[weight]
|
||||
if (b != Inf and a != -Inf): # finite limits
|
||||
if wopts is None: # no precomputed Chebyshev moments
|
||||
return _quadpack._qawoe(func, a, b, wvar, integr, args, full_output,
|
||||
epsabs, epsrel, limit, maxp1,1)
|
||||
else: # precomputed Chebyshev moments
|
||||
momcom = wopts[0]
|
||||
chebcom = wopts[1]
|
||||
return _quadpack._qawoe(func, a, b, wvar, integr, args, full_output,
|
||||
epsabs, epsrel, limit, maxp1, 2, momcom, chebcom)
|
||||
|
||||
elif (b == Inf and a != -Inf):
|
||||
return _quadpack._qawfe(func, a, wvar, integr, args, full_output,
|
||||
epsabs,limlst,limit,maxp1)
|
||||
elif (b != Inf and a == -Inf): # remap function and interval
|
||||
if weight == 'cos':
|
||||
def thefunc(x,*myargs):
|
||||
y = -x
|
||||
func = myargs[0]
|
||||
myargs = (y,) + myargs[1:]
|
||||
return func(*myargs)
|
||||
else:
|
||||
def thefunc(x,*myargs):
|
||||
y = -x
|
||||
func = myargs[0]
|
||||
myargs = (y,) + myargs[1:]
|
||||
return -func(*myargs)
|
||||
args = (func,) + args
|
||||
return _quadpack._qawfe(thefunc, -b, wvar, integr, args,
|
||||
full_output, epsabs, limlst, limit, maxp1)
|
||||
else:
|
||||
raise ValueError("Cannot integrate with this weight from -Inf to +Inf.")
|
||||
else:
|
||||
if a in [-Inf,Inf] or b in [-Inf,Inf]:
|
||||
raise ValueError("Cannot integrate with this weight over an infinite interval.")
|
||||
|
||||
if weight.startswith('alg'):
|
||||
integr = strdict[weight]
|
||||
return _quadpack._qawse(func, a, b, wvar, integr, args,
|
||||
full_output, epsabs, epsrel, limit)
|
||||
else: # weight == 'cauchy'
|
||||
return _quadpack._qawce(func, a, b, wvar, args, full_output,
|
||||
epsabs, epsrel, limit)
|
||||
|
||||
|
||||
def dblquad(func, a, b, gfun, hfun, args=(), epsabs=1.49e-8, epsrel=1.49e-8):
|
||||
"""
|
||||
Compute a double integral.
|
||||
|
||||
Return the double (definite) integral of ``func(y, x)`` from ``x = a..b``
|
||||
and ``y = gfun(x)..hfun(x)``.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
func : callable
|
||||
A Python function or method of at least two variables: y must be the
|
||||
first argument and x the second argument.
|
||||
a, b : float
|
||||
The limits of integration in x: `a` < `b`
|
||||
gfun : callable or float
|
||||
The lower boundary curve in y which is a function taking a single
|
||||
floating point argument (x) and returning a floating point result
|
||||
or a float indicating a constant boundary curve.
|
||||
hfun : callable or float
|
||||
The upper boundary curve in y (same requirements as `gfun`).
|
||||
args : sequence, optional
|
||||
Extra arguments to pass to `func`.
|
||||
epsabs : float, optional
|
||||
Absolute tolerance passed directly to the inner 1-D quadrature
|
||||
integration. Default is 1.49e-8. `dblquad`` tries to obtain
|
||||
an accuracy of ``abs(i-result) <= max(epsabs, epsrel*abs(i))``
|
||||
where ``i`` = inner integral of ``func(y, x)`` from ``gfun(x)``
|
||||
to ``hfun(x)``, and ``result`` is the numerical approximation.
|
||||
See `epsrel` below.
|
||||
epsrel : float, optional
|
||||
Relative tolerance of the inner 1-D integrals. Default is 1.49e-8.
|
||||
If ``epsabs <= 0``, `epsrel` must be greater than both 5e-29
|
||||
and ``50 * (machine epsilon)``. See `epsabs` above.
|
||||
|
||||
Returns
|
||||
-------
|
||||
y : float
|
||||
The resultant integral.
|
||||
abserr : float
|
||||
An estimate of the error.
|
||||
|
||||
See also
|
||||
--------
|
||||
quad : single integral
|
||||
tplquad : triple integral
|
||||
nquad : N-dimensional integrals
|
||||
fixed_quad : fixed-order Gaussian quadrature
|
||||
quadrature : adaptive Gaussian quadrature
|
||||
odeint : ODE integrator
|
||||
ode : ODE integrator
|
||||
simps : integrator for sampled data
|
||||
romb : integrator for sampled data
|
||||
scipy.special : for coefficients and roots of orthogonal polynomials
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
Compute the double integral of ``x * y**2`` over the box
|
||||
``x`` ranging from 0 to 2 and ``y`` ranging from 0 to 1.
|
||||
|
||||
>>> from scipy import integrate
|
||||
>>> f = lambda y, x: x*y**2
|
||||
>>> integrate.dblquad(f, 0, 2, lambda x: 0, lambda x: 1)
|
||||
(0.6666666666666667, 7.401486830834377e-15)
|
||||
|
||||
"""
|
||||
|
||||
def temp_ranges(*args):
|
||||
return [gfun(args[0]) if callable(gfun) else gfun,
|
||||
hfun(args[0]) if callable(hfun) else hfun]
|
||||
|
||||
return nquad(func, [temp_ranges, [a, b]], args=args,
|
||||
opts={"epsabs": epsabs, "epsrel": epsrel})
|
||||
|
||||
|
||||
def tplquad(func, a, b, gfun, hfun, qfun, rfun, args=(), epsabs=1.49e-8,
|
||||
epsrel=1.49e-8):
|
||||
"""
|
||||
Compute a triple (definite) integral.
|
||||
|
||||
Return the triple integral of ``func(z, y, x)`` from ``x = a..b``,
|
||||
``y = gfun(x)..hfun(x)``, and ``z = qfun(x,y)..rfun(x,y)``.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
func : function
|
||||
A Python function or method of at least three variables in the
|
||||
order (z, y, x).
|
||||
a, b : float
|
||||
The limits of integration in x: `a` < `b`
|
||||
gfun : function or float
|
||||
The lower boundary curve in y which is a function taking a single
|
||||
floating point argument (x) and returning a floating point result
|
||||
or a float indicating a constant boundary curve.
|
||||
hfun : function or float
|
||||
The upper boundary curve in y (same requirements as `gfun`).
|
||||
qfun : function or float
|
||||
The lower boundary surface in z. It must be a function that takes
|
||||
two floats in the order (x, y) and returns a float or a float
|
||||
indicating a constant boundary surface.
|
||||
rfun : function or float
|
||||
The upper boundary surface in z. (Same requirements as `qfun`.)
|
||||
args : tuple, optional
|
||||
Extra arguments to pass to `func`.
|
||||
epsabs : float, optional
|
||||
Absolute tolerance passed directly to the innermost 1-D quadrature
|
||||
integration. Default is 1.49e-8.
|
||||
epsrel : float, optional
|
||||
Relative tolerance of the innermost 1-D integrals. Default is 1.49e-8.
|
||||
|
||||
Returns
|
||||
-------
|
||||
y : float
|
||||
The resultant integral.
|
||||
abserr : float
|
||||
An estimate of the error.
|
||||
|
||||
See Also
|
||||
--------
|
||||
quad: Adaptive quadrature using QUADPACK
|
||||
quadrature: Adaptive Gaussian quadrature
|
||||
fixed_quad: Fixed-order Gaussian quadrature
|
||||
dblquad: Double integrals
|
||||
nquad : N-dimensional integrals
|
||||
romb: Integrators for sampled data
|
||||
simps: Integrators for sampled data
|
||||
ode: ODE integrators
|
||||
odeint: ODE integrators
|
||||
scipy.special: For coefficients and roots of orthogonal polynomials
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
Compute the triple integral of ``x * y * z``, over ``x`` ranging
|
||||
from 1 to 2, ``y`` ranging from 2 to 3, ``z`` ranging from 0 to 1.
|
||||
|
||||
>>> from scipy import integrate
|
||||
>>> f = lambda z, y, x: x*y*z
|
||||
>>> integrate.tplquad(f, 1, 2, lambda x: 2, lambda x: 3,
|
||||
... lambda x, y: 0, lambda x, y: 1)
|
||||
(1.8750000000000002, 3.324644794257407e-14)
|
||||
|
||||
|
||||
"""
|
||||
# f(z, y, x)
|
||||
# qfun/rfun (x, y)
|
||||
# gfun/hfun(x)
|
||||
# nquad will hand (y, x, t0, ...) to ranges0
|
||||
# nquad will hand (x, t0, ...) to ranges1
|
||||
# Stupid different API...
|
||||
|
||||
def ranges0(*args):
|
||||
return [qfun(args[1], args[0]) if callable(qfun) else qfun,
|
||||
rfun(args[1], args[0]) if callable(rfun) else rfun]
|
||||
|
||||
def ranges1(*args):
|
||||
return [gfun(args[0]) if callable(gfun) else gfun,
|
||||
hfun(args[0]) if callable(hfun) else hfun]
|
||||
|
||||
ranges = [ranges0, ranges1, [a, b]]
|
||||
return nquad(func, ranges, args=args,
|
||||
opts={"epsabs": epsabs, "epsrel": epsrel})
|
||||
|
||||
|
||||
def nquad(func, ranges, args=None, opts=None, full_output=False):
|
||||
"""
|
||||
Integration over multiple variables.
|
||||
|
||||
Wraps `quad` to enable integration over multiple variables.
|
||||
Various options allow improved integration of discontinuous functions, as
|
||||
well as the use of weighted integration, and generally finer control of the
|
||||
integration process.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
func : {callable, scipy.LowLevelCallable}
|
||||
The function to be integrated. Has arguments of ``x0, ... xn``,
|
||||
``t0, tm``, where integration is carried out over ``x0, ... xn``, which
|
||||
must be floats. Function signature should be
|
||||
``func(x0, x1, ..., xn, t0, t1, ..., tm)``. Integration is carried out
|
||||
in order. That is, integration over ``x0`` is the innermost integral,
|
||||
and ``xn`` is the outermost.
|
||||
|
||||
If the user desires improved integration performance, then `f` may
|
||||
be a `scipy.LowLevelCallable` with one of the signatures::
|
||||
|
||||
double func(int n, double *xx)
|
||||
double func(int n, double *xx, void *user_data)
|
||||
|
||||
where ``n`` is the number of extra parameters and args is an array
|
||||
of doubles of the additional parameters, the ``xx`` array contains the
|
||||
coordinates. The ``user_data`` is the data contained in the
|
||||
`scipy.LowLevelCallable`.
|
||||
ranges : iterable object
|
||||
Each element of ranges may be either a sequence of 2 numbers, or else
|
||||
a callable that returns such a sequence. ``ranges[0]`` corresponds to
|
||||
integration over x0, and so on. If an element of ranges is a callable,
|
||||
then it will be called with all of the integration arguments available,
|
||||
as well as any parametric arguments. e.g., if
|
||||
``func = f(x0, x1, x2, t0, t1)``, then ``ranges[0]`` may be defined as
|
||||
either ``(a, b)`` or else as ``(a, b) = range0(x1, x2, t0, t1)``.
|
||||
args : iterable object, optional
|
||||
Additional arguments ``t0, ..., tn``, required by `func`, `ranges`, and
|
||||
``opts``.
|
||||
opts : iterable object or dict, optional
|
||||
Options to be passed to `quad`. May be empty, a dict, or
|
||||
a sequence of dicts or functions that return a dict. If empty, the
|
||||
default options from scipy.integrate.quad are used. If a dict, the same
|
||||
options are used for all levels of integraion. If a sequence, then each
|
||||
element of the sequence corresponds to a particular integration. e.g.,
|
||||
opts[0] corresponds to integration over x0, and so on. If a callable,
|
||||
the signature must be the same as for ``ranges``. The available
|
||||
options together with their default values are:
|
||||
|
||||
- epsabs = 1.49e-08
|
||||
- epsrel = 1.49e-08
|
||||
- limit = 50
|
||||
- points = None
|
||||
- weight = None
|
||||
- wvar = None
|
||||
- wopts = None
|
||||
|
||||
For more information on these options, see `quad` and `quad_explain`.
|
||||
|
||||
full_output : bool, optional
|
||||
Partial implementation of ``full_output`` from scipy.integrate.quad.
|
||||
The number of integrand function evaluations ``neval`` can be obtained
|
||||
by setting ``full_output=True`` when calling nquad.
|
||||
|
||||
Returns
|
||||
-------
|
||||
result : float
|
||||
The result of the integration.
|
||||
abserr : float
|
||||
The maximum of the estimates of the absolute error in the various
|
||||
integration results.
|
||||
out_dict : dict, optional
|
||||
A dict containing additional information on the integration.
|
||||
|
||||
See Also
|
||||
--------
|
||||
quad : 1-D numerical integration
|
||||
dblquad, tplquad : double and triple integrals
|
||||
fixed_quad : fixed-order Gaussian quadrature
|
||||
quadrature : adaptive Gaussian quadrature
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from scipy import integrate
|
||||
>>> func = lambda x0,x1,x2,x3 : x0**2 + x1*x2 - x3**3 + np.sin(x0) + (
|
||||
... 1 if (x0-.2*x3-.5-.25*x1>0) else 0)
|
||||
>>> points = [[lambda x1,x2,x3 : 0.2*x3 + 0.5 + 0.25*x1], [], [], []]
|
||||
>>> def opts0(*args, **kwargs):
|
||||
... return {'points':[0.2*args[2] + 0.5 + 0.25*args[0]]}
|
||||
>>> integrate.nquad(func, [[0,1], [-1,1], [.13,.8], [-.15,1]],
|
||||
... opts=[opts0,{},{},{}], full_output=True)
|
||||
(1.5267454070738633, 2.9437360001402324e-14, {'neval': 388962})
|
||||
|
||||
>>> scale = .1
|
||||
>>> def func2(x0, x1, x2, x3, t0, t1):
|
||||
... return x0*x1*x3**2 + np.sin(x2) + 1 + (1 if x0+t1*x1-t0>0 else 0)
|
||||
>>> def lim0(x1, x2, x3, t0, t1):
|
||||
... return [scale * (x1**2 + x2 + np.cos(x3)*t0*t1 + 1) - 1,
|
||||
... scale * (x1**2 + x2 + np.cos(x3)*t0*t1 + 1) + 1]
|
||||
>>> def lim1(x2, x3, t0, t1):
|
||||
... return [scale * (t0*x2 + t1*x3) - 1,
|
||||
... scale * (t0*x2 + t1*x3) + 1]
|
||||
>>> def lim2(x3, t0, t1):
|
||||
... return [scale * (x3 + t0**2*t1**3) - 1,
|
||||
... scale * (x3 + t0**2*t1**3) + 1]
|
||||
>>> def lim3(t0, t1):
|
||||
... return [scale * (t0+t1) - 1, scale * (t0+t1) + 1]
|
||||
>>> def opts0(x1, x2, x3, t0, t1):
|
||||
... return {'points' : [t0 - t1*x1]}
|
||||
>>> def opts1(x2, x3, t0, t1):
|
||||
... return {}
|
||||
>>> def opts2(x3, t0, t1):
|
||||
... return {}
|
||||
>>> def opts3(t0, t1):
|
||||
... return {}
|
||||
>>> integrate.nquad(func2, [lim0, lim1, lim2, lim3], args=(0,0),
|
||||
... opts=[opts0, opts1, opts2, opts3])
|
||||
(25.066666666666666, 2.7829590483937256e-13)
|
||||
|
||||
"""
|
||||
depth = len(ranges)
|
||||
ranges = [rng if callable(rng) else _RangeFunc(rng) for rng in ranges]
|
||||
if args is None:
|
||||
args = ()
|
||||
if opts is None:
|
||||
opts = [dict([])] * depth
|
||||
|
||||
if isinstance(opts, dict):
|
||||
opts = [_OptFunc(opts)] * depth
|
||||
else:
|
||||
opts = [opt if callable(opt) else _OptFunc(opt) for opt in opts]
|
||||
return _NQuad(func, ranges, opts, full_output).integrate(*args)
|
||||
|
||||
|
||||
class _RangeFunc(object):
|
||||
def __init__(self, range_):
|
||||
self.range_ = range_
|
||||
|
||||
def __call__(self, *args):
|
||||
"""Return stored value.
|
||||
|
||||
*args needed because range_ can be float or func, and is called with
|
||||
variable number of parameters.
|
||||
"""
|
||||
return self.range_
|
||||
|
||||
|
||||
class _OptFunc(object):
|
||||
def __init__(self, opt):
|
||||
self.opt = opt
|
||||
|
||||
def __call__(self, *args):
|
||||
"""Return stored dict."""
|
||||
return self.opt
|
||||
|
||||
|
||||
class _NQuad(object):
|
||||
def __init__(self, func, ranges, opts, full_output):
|
||||
self.abserr = 0
|
||||
self.func = func
|
||||
self.ranges = ranges
|
||||
self.opts = opts
|
||||
self.maxdepth = len(ranges)
|
||||
self.full_output = full_output
|
||||
if self.full_output:
|
||||
self.out_dict = {'neval': 0}
|
||||
|
||||
def integrate(self, *args, **kwargs):
|
||||
depth = kwargs.pop('depth', 0)
|
||||
if kwargs:
|
||||
raise ValueError('unexpected kwargs')
|
||||
|
||||
# Get the integration range and options for this depth.
|
||||
ind = -(depth + 1)
|
||||
fn_range = self.ranges[ind]
|
||||
low, high = fn_range(*args)
|
||||
fn_opt = self.opts[ind]
|
||||
opt = dict(fn_opt(*args))
|
||||
|
||||
if 'points' in opt:
|
||||
opt['points'] = [x for x in opt['points'] if low <= x <= high]
|
||||
if depth + 1 == self.maxdepth:
|
||||
f = self.func
|
||||
else:
|
||||
f = partial(self.integrate, depth=depth+1)
|
||||
quad_r = quad(f, low, high, args=args, full_output=self.full_output,
|
||||
**opt)
|
||||
value = quad_r[0]
|
||||
abserr = quad_r[1]
|
||||
if self.full_output:
|
||||
infodict = quad_r[2]
|
||||
# The 'neval' parameter in full_output returns the total
|
||||
# number of times the integrand function was evaluated.
|
||||
# Therefore, only the innermost integration loop counts.
|
||||
if depth + 1 == self.maxdepth:
|
||||
self.out_dict['neval'] += infodict['neval']
|
||||
self.abserr = max(self.abserr, abserr)
|
||||
if depth > 0:
|
||||
return value
|
||||
else:
|
||||
# Final result of N-D integration with error
|
||||
if self.full_output:
|
||||
return value, self.abserr, self.out_dict
|
||||
else:
|
||||
return value, self.abserr
|
113
venv/Lib/site-packages/scipy/integrate/setup.py
Normal file
113
venv/Lib/site-packages/scipy/integrate/setup.py
Normal file
|
@ -0,0 +1,113 @@
|
|||
import os
|
||||
from os.path import join
|
||||
|
||||
from scipy._build_utils import numpy_nodepr_api
|
||||
|
||||
|
||||
def configuration(parent_package='',top_path=None):
|
||||
from numpy.distutils.misc_util import Configuration
|
||||
from scipy._build_utils.system_info import get_info
|
||||
from scipy._build_utils import (uses_blas64, blas_ilp64_pre_build_hook,
|
||||
combine_dict, get_f2py_int64_options)
|
||||
|
||||
config = Configuration('integrate', parent_package, top_path)
|
||||
|
||||
if uses_blas64():
|
||||
lapack_opt = get_info('lapack_ilp64_opt', 2)
|
||||
pre_build_hook = blas_ilp64_pre_build_hook(lapack_opt)
|
||||
f2py_options = get_f2py_int64_options()
|
||||
else:
|
||||
lapack_opt = get_info('lapack_opt')
|
||||
pre_build_hook = None
|
||||
f2py_options = None
|
||||
|
||||
mach_src = [join('mach','*.f')]
|
||||
quadpack_src = [join('quadpack', '*.f')]
|
||||
lsoda_src = [join('odepack', fn) for fn in [
|
||||
'blkdta000.f', 'bnorm.f', 'cfode.f',
|
||||
'ewset.f', 'fnorm.f', 'intdy.f',
|
||||
'lsoda.f', 'prja.f', 'solsy.f', 'srcma.f',
|
||||
'stoda.f', 'vmnorm.f', 'xerrwv.f', 'xsetf.f',
|
||||
'xsetun.f']]
|
||||
vode_src = [join('odepack', 'vode.f'), join('odepack', 'zvode.f')]
|
||||
dop_src = [join('dop','*.f')]
|
||||
quadpack_test_src = [join('tests','_test_multivariate.c')]
|
||||
odeint_banded_test_src = [join('tests', 'banded5x5.f')]
|
||||
|
||||
config.add_library('mach', sources=mach_src, config_fc={'noopt': (__file__, 1)},
|
||||
_pre_build_hook=pre_build_hook)
|
||||
config.add_library('quadpack', sources=quadpack_src, _pre_build_hook=pre_build_hook)
|
||||
config.add_library('lsoda', sources=lsoda_src, _pre_build_hook=pre_build_hook)
|
||||
config.add_library('vode', sources=vode_src, _pre_build_hook=pre_build_hook)
|
||||
config.add_library('dop', sources=dop_src, _pre_build_hook=pre_build_hook)
|
||||
|
||||
# Extensions
|
||||
# quadpack:
|
||||
include_dirs = [join(os.path.dirname(__file__), '..', '_lib', 'src')]
|
||||
cfg = combine_dict(lapack_opt,
|
||||
include_dirs=include_dirs,
|
||||
libraries=['quadpack', 'mach'])
|
||||
config.add_extension('_quadpack',
|
||||
sources=['_quadpackmodule.c'],
|
||||
depends=(['__quadpack.h']
|
||||
+ quadpack_src + mach_src),
|
||||
**cfg)
|
||||
|
||||
# odepack/lsoda-odeint
|
||||
cfg = combine_dict(lapack_opt, numpy_nodepr_api,
|
||||
libraries=['lsoda', 'mach'])
|
||||
config.add_extension('_odepack',
|
||||
sources=['_odepackmodule.c'],
|
||||
depends=(lsoda_src + mach_src),
|
||||
**cfg)
|
||||
|
||||
# vode
|
||||
cfg = combine_dict(lapack_opt,
|
||||
libraries=['vode'])
|
||||
ext = config.add_extension('vode',
|
||||
sources=['vode.pyf'],
|
||||
depends=vode_src,
|
||||
f2py_options=f2py_options,
|
||||
**cfg)
|
||||
ext._pre_build_hook = pre_build_hook
|
||||
|
||||
# lsoda
|
||||
cfg = combine_dict(lapack_opt,
|
||||
libraries=['lsoda', 'mach'])
|
||||
ext = config.add_extension('lsoda',
|
||||
sources=['lsoda.pyf'],
|
||||
depends=(lsoda_src + mach_src),
|
||||
f2py_options=f2py_options,
|
||||
**cfg)
|
||||
ext._pre_build_hook = pre_build_hook
|
||||
|
||||
# dop
|
||||
ext = config.add_extension('_dop',
|
||||
sources=['dop.pyf'],
|
||||
libraries=['dop'],
|
||||
depends=dop_src,
|
||||
f2py_options=f2py_options)
|
||||
ext._pre_build_hook = pre_build_hook
|
||||
|
||||
config.add_extension('_test_multivariate',
|
||||
sources=quadpack_test_src)
|
||||
|
||||
# Fortran+f2py extension module for testing odeint.
|
||||
cfg = combine_dict(lapack_opt,
|
||||
libraries=['lsoda', 'mach'])
|
||||
ext = config.add_extension('_test_odeint_banded',
|
||||
sources=odeint_banded_test_src,
|
||||
depends=(lsoda_src + mach_src),
|
||||
f2py_options=f2py_options,
|
||||
**cfg)
|
||||
ext._pre_build_hook = pre_build_hook
|
||||
|
||||
config.add_subpackage('_ivp')
|
||||
|
||||
config.add_data_dir('tests')
|
||||
return config
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
from numpy.distutils.core import setup
|
||||
setup(**configuration(top_path='').todict())
|
0
venv/Lib/site-packages/scipy/integrate/tests/__init__.py
Normal file
0
venv/Lib/site-packages/scipy/integrate/tests/__init__.py
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,125 @@
|
|||
#include <Python.h>
|
||||
|
||||
#include "math.h"
|
||||
|
||||
const double PI = 3.141592653589793238462643383279502884;
|
||||
|
||||
static double
|
||||
_multivariate_typical(int n, double *args)
|
||||
{
|
||||
return cos(args[1] * args[0] - args[2] * sin(args[0])) / PI;
|
||||
}
|
||||
|
||||
static double
|
||||
_multivariate_indefinite(int n, double *args)
|
||||
{
|
||||
return -exp(-args[0]) * log(args[0]);
|
||||
}
|
||||
|
||||
static double
|
||||
_multivariate_sin(int n, double *args)
|
||||
{
|
||||
return sin(args[0]);
|
||||
}
|
||||
|
||||
static double
|
||||
_sin_0(double x, void *user_data)
|
||||
{
|
||||
return sin(x);
|
||||
}
|
||||
|
||||
static double
|
||||
_sin_1(int ndim, double *x, void *user_data)
|
||||
{
|
||||
return sin(x[0]);
|
||||
}
|
||||
|
||||
static double
|
||||
_sin_2(double x)
|
||||
{
|
||||
return sin(x);
|
||||
}
|
||||
|
||||
static double
|
||||
_sin_3(int ndim, double *x)
|
||||
{
|
||||
return sin(x[0]);
|
||||
}
|
||||
|
||||
|
||||
typedef struct {
|
||||
char *name;
|
||||
void *ptr;
|
||||
} routine_t;
|
||||
|
||||
|
||||
static const routine_t routines[] = {
|
||||
{"_multivariate_typical", &_multivariate_typical},
|
||||
{"_multivariate_indefinite", &_multivariate_indefinite},
|
||||
{"_multivariate_sin", &_multivariate_sin},
|
||||
{"_sin_0", &_sin_0},
|
||||
{"_sin_1", &_sin_1},
|
||||
{"_sin_2", &_sin_2},
|
||||
{"_sin_3", &_sin_3}
|
||||
};
|
||||
|
||||
|
||||
static int create_pointers(PyObject *module)
|
||||
{
|
||||
PyObject *d, *obj = NULL;
|
||||
int i;
|
||||
|
||||
d = PyModule_GetDict(module);
|
||||
if (d == NULL) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
for (i = 0; i < sizeof(routines) / sizeof(routine_t); ++i) {
|
||||
obj = PyLong_FromVoidPtr(routines[i].ptr);
|
||||
if (obj == NULL) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (PyDict_SetItemString(d, routines[i].name, obj)) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
Py_DECREF(obj);
|
||||
obj = NULL;
|
||||
}
|
||||
|
||||
Py_XDECREF(obj);
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
Py_XDECREF(obj);
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
static struct PyModuleDef moduledef = {
|
||||
PyModuleDef_HEAD_INIT,
|
||||
"_test_multivariate",
|
||||
NULL,
|
||||
-1,
|
||||
NULL, /* Empty methods section */
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL
|
||||
};
|
||||
|
||||
PyMODINIT_FUNC
|
||||
PyInit__test_multivariate(void)
|
||||
{
|
||||
PyObject *m;
|
||||
m = PyModule_Create(&moduledef);
|
||||
if (m == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
if (create_pointers(m)) {
|
||||
Py_DECREF(m);
|
||||
return NULL;
|
||||
}
|
||||
return m;
|
||||
}
|
240
venv/Lib/site-packages/scipy/integrate/tests/banded5x5.f
Normal file
240
venv/Lib/site-packages/scipy/integrate/tests/banded5x5.f
Normal file
|
@ -0,0 +1,240 @@
|
|||
c banded5x5.f
|
||||
c
|
||||
c This Fortran library contains implementations of the
|
||||
c differential equation
|
||||
c dy/dt = A*y
|
||||
c where A is a 5x5 banded matrix (see below for the actual
|
||||
c values). These functions will be used to test
|
||||
c scipy.integrate.odeint.
|
||||
c
|
||||
c The idea is to solve the system two ways: pure Fortran, and
|
||||
c using odeint. The "pure Fortran" solver is implemented in
|
||||
c the subroutine banded5x5_solve below. It calls LSODA to
|
||||
c solve the system.
|
||||
c
|
||||
c To solve the same system using odeint, the functions in this
|
||||
c file are given a python wrapper using f2py. Then the code
|
||||
c in test_odeint_jac.py uses the wrapper to implement the
|
||||
c equation and Jacobian functions required by odeint. Because
|
||||
c those functions ultimately call the Fortran routines defined
|
||||
c in this file, the two method (pure Fortran and odeint) should
|
||||
c produce exactly the same results. (That's assuming floating
|
||||
c point calculations are deterministic, which can be an
|
||||
c incorrect assumption.) If we simply re-implemented the
|
||||
c equation and Jacobian functions using just python and numpy,
|
||||
c the floating point calculations would not be performed in
|
||||
c the same sequence as in the Fortran code, and we would obtain
|
||||
c different answers. The answer for either method would be
|
||||
c numerically "correct", but the errors would be different,
|
||||
c and the counts of function and Jacobian evaluations would
|
||||
c likely be different.
|
||||
c
|
||||
block data jacobian
|
||||
implicit none
|
||||
|
||||
double precision bands
|
||||
dimension bands(4,5)
|
||||
common /jac/ bands
|
||||
|
||||
c The data for a banded Jacobian stored in packed banded
|
||||
c format. The full Jacobian is
|
||||
c
|
||||
c -1, 0.25, 0, 0, 0
|
||||
c 0.25, -5, 0.25, 0, 0
|
||||
c 0.10, 0.25, -25, 0.25, 0
|
||||
c 0, 0.10, 0.25, -125, 0.25
|
||||
c 0, 0, 0.10, 0.25, -625
|
||||
c
|
||||
c The columns in the following layout of numbers are
|
||||
c the upper diagonal, main diagonal and two lower diagonals
|
||||
c (i.e. each row in the layout is a column of the packed
|
||||
c banded Jacobian). The values 0.00D0 are in the "don't
|
||||
c care" positions.
|
||||
|
||||
data bands/
|
||||
+ 0.00D0, -1.0D0, 0.25D0, 0.10D0,
|
||||
+ 0.25D0, -5.0D0, 0.25D0, 0.10D0,
|
||||
+ 0.25D0, -25.0D0, 0.25D0, 0.10D0,
|
||||
+ 0.25D0, -125.0D0, 0.25D0, 0.00D0,
|
||||
+ 0.25D0, -625.0D0, 0.00D0, 0.00D0
|
||||
+ /
|
||||
|
||||
end
|
||||
|
||||
subroutine getbands(jac)
|
||||
double precision jac
|
||||
dimension jac(4, 5)
|
||||
cf2py intent(out) jac
|
||||
|
||||
double precision bands
|
||||
dimension bands(4,5)
|
||||
common /jac/ bands
|
||||
|
||||
integer i, j
|
||||
do 5 i = 1, 4
|
||||
do 5 j = 1, 5
|
||||
jac(i, j) = bands(i, j)
|
||||
5 continue
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
c
|
||||
c Differential equations, right-hand-side
|
||||
c
|
||||
subroutine banded5x5(n, t, y, f)
|
||||
implicit none
|
||||
integer n
|
||||
double precision t, y, f
|
||||
dimension y(n), f(n)
|
||||
|
||||
double precision bands
|
||||
dimension bands(4,5)
|
||||
common /jac/ bands
|
||||
|
||||
f(1) = bands(2,1)*y(1) + bands(1,2)*y(2)
|
||||
f(2) = bands(3,1)*y(1) + bands(2,2)*y(2) + bands(1,3)*y(3)
|
||||
f(3) = bands(4,1)*y(1) + bands(3,2)*y(2) + bands(2,3)*y(3)
|
||||
+ + bands(1,4)*y(4)
|
||||
f(4) = bands(4,2)*y(2) + bands(3,3)*y(3) + bands(2,4)*y(4)
|
||||
+ + bands(1,5)*y(5)
|
||||
f(5) = bands(4,3)*y(3) + bands(3,4)*y(4) + bands(2,5)*y(5)
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
c
|
||||
c Jacobian
|
||||
c
|
||||
c The subroutine assumes that the full Jacobian is to be computed.
|
||||
c ml and mu are ignored, and nrowpd is assumed to be n.
|
||||
c
|
||||
subroutine banded5x5_jac(n, t, y, ml, mu, jac, nrowpd)
|
||||
implicit none
|
||||
integer n, ml, mu, nrowpd
|
||||
double precision t, y, jac
|
||||
dimension y(n), jac(nrowpd, n)
|
||||
|
||||
integer i, j
|
||||
|
||||
double precision bands
|
||||
dimension bands(4,5)
|
||||
common /jac/ bands
|
||||
|
||||
do 15 i = 1, 4
|
||||
do 15 j = 1, 5
|
||||
if ((i - j) .gt. 0) then
|
||||
jac(i - j, j) = bands(i, j)
|
||||
end if
|
||||
15 continue
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
c
|
||||
c Banded Jacobian
|
||||
c
|
||||
c ml = 2, mu = 1
|
||||
c
|
||||
subroutine banded5x5_bjac(n, t, y, ml, mu, bjac, nrowpd)
|
||||
implicit none
|
||||
integer n, ml, mu, nrowpd
|
||||
double precision t, y, bjac
|
||||
dimension y(5), bjac(nrowpd, n)
|
||||
|
||||
integer i, j
|
||||
|
||||
double precision bands
|
||||
dimension bands(4,5)
|
||||
common /jac/ bands
|
||||
|
||||
do 20 i = 1, 4
|
||||
do 20 j = 1, 5
|
||||
bjac(i, j) = bands(i, j)
|
||||
20 continue
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
subroutine banded5x5_solve(y, nsteps, dt, jt, nst, nfe, nje)
|
||||
|
||||
c jt is the Jacobian type:
|
||||
c jt = 1 Use the full Jacobian.
|
||||
c jt = 4 Use the banded Jacobian.
|
||||
c nst, nfe and nje are outputs:
|
||||
c nst: Total number of internal steps
|
||||
c nfe: Total number of function (i.e. right-hand-side)
|
||||
c evaluations
|
||||
c nje: Total number of Jacobian evaluations
|
||||
|
||||
implicit none
|
||||
|
||||
external banded5x5
|
||||
external banded5x5_jac
|
||||
external banded5x5_bjac
|
||||
external LSODA
|
||||
|
||||
c Arguments...
|
||||
double precision y, dt
|
||||
integer nsteps, jt, nst, nfe, nje
|
||||
cf2py intent(inout) y
|
||||
cf2py intent(in) nsteps, dt, jt
|
||||
cf2py intent(out) nst, nfe, nje
|
||||
|
||||
c Local variables...
|
||||
double precision atol, rtol, t, tout, rwork
|
||||
integer iwork
|
||||
dimension y(5), rwork(500), iwork(500)
|
||||
integer neq, i
|
||||
integer itol, iopt, itask, istate, lrw, liw
|
||||
|
||||
c Common block...
|
||||
double precision jacband
|
||||
dimension jacband(4,5)
|
||||
common /jac/ jacband
|
||||
|
||||
c --- t range ---
|
||||
t = 0.0D0
|
||||
|
||||
c --- Solver tolerances ---
|
||||
rtol = 1.0D-11
|
||||
atol = 1.0D-13
|
||||
itol = 1
|
||||
|
||||
c --- Other LSODA parameters ---
|
||||
neq = 5
|
||||
itask = 1
|
||||
istate = 1
|
||||
iopt = 0
|
||||
iwork(1) = 2
|
||||
iwork(2) = 1
|
||||
lrw = 500
|
||||
liw = 500
|
||||
|
||||
c --- Call LSODA in a loop to compute the solution ---
|
||||
do 40 i = 1, nsteps
|
||||
tout = i*dt
|
||||
if (jt .eq. 1) then
|
||||
call LSODA(banded5x5, neq, y, t, tout,
|
||||
& itol, rtol, atol, itask, istate, iopt,
|
||||
& rwork, lrw, iwork, liw,
|
||||
& banded5x5_jac, jt)
|
||||
else
|
||||
call LSODA(banded5x5, neq, y, t, tout,
|
||||
& itol, rtol, atol, itask, istate, iopt,
|
||||
& rwork, lrw, iwork, liw,
|
||||
& banded5x5_bjac, jt)
|
||||
end if
|
||||
40 if (istate .lt. 0) goto 80
|
||||
|
||||
nst = iwork(11)
|
||||
nfe = iwork(12)
|
||||
nje = iwork(13)
|
||||
|
||||
return
|
||||
|
||||
80 write (6,89) istate
|
||||
89 format(1X,"Error: istate=",I3)
|
||||
return
|
||||
end
|
176
venv/Lib/site-packages/scipy/integrate/tests/test__quad_vec.py
Normal file
176
venv/Lib/site-packages/scipy/integrate/tests/test__quad_vec.py
Normal file
|
@ -0,0 +1,176 @@
|
|||
import pytest
|
||||
|
||||
import numpy as np
|
||||
from numpy.testing import assert_allclose
|
||||
|
||||
from scipy.integrate import quad_vec
|
||||
|
||||
quadrature_params = pytest.mark.parametrize('quadrature',
|
||||
[None, "gk15", "gk21", "trapz"])
|
||||
|
||||
|
||||
@quadrature_params
|
||||
def test_quad_vec_simple(quadrature):
|
||||
n = np.arange(10)
|
||||
f = lambda x: x**n
|
||||
for epsabs in [0.1, 1e-3, 1e-6]:
|
||||
if quadrature == 'trapz' and epsabs < 1e-4:
|
||||
# slow: skip
|
||||
continue
|
||||
|
||||
kwargs = dict(epsabs=epsabs, quadrature=quadrature)
|
||||
|
||||
exact = 2**(n+1)/(n + 1)
|
||||
|
||||
res, err = quad_vec(f, 0, 2, norm='max', **kwargs)
|
||||
assert_allclose(res, exact, rtol=0, atol=epsabs)
|
||||
|
||||
res, err = quad_vec(f, 0, 2, norm='2', **kwargs)
|
||||
assert np.linalg.norm(res - exact) < epsabs
|
||||
|
||||
res, err = quad_vec(f, 0, 2, norm='max', points=(0.5, 1.0), **kwargs)
|
||||
assert_allclose(res, exact, rtol=0, atol=epsabs)
|
||||
|
||||
res, err, *rest = quad_vec(f, 0, 2, norm='max',
|
||||
epsrel=1e-8,
|
||||
full_output=True,
|
||||
limit=10000,
|
||||
**kwargs)
|
||||
assert_allclose(res, exact, rtol=0, atol=epsabs)
|
||||
|
||||
|
||||
@quadrature_params
|
||||
def test_quad_vec_simple_inf(quadrature):
|
||||
f = lambda x: 1 / (1 + np.float64(x)**2)
|
||||
|
||||
for epsabs in [0.1, 1e-3, 1e-6]:
|
||||
if quadrature == 'trapz' and epsabs < 1e-4:
|
||||
# slow: skip
|
||||
continue
|
||||
|
||||
kwargs = dict(norm='max', epsabs=epsabs, quadrature=quadrature)
|
||||
|
||||
res, err = quad_vec(f, 0, np.inf, **kwargs)
|
||||
assert_allclose(res, np.pi/2, rtol=0, atol=max(epsabs, err))
|
||||
|
||||
res, err = quad_vec(f, 0, -np.inf, **kwargs)
|
||||
assert_allclose(res, -np.pi/2, rtol=0, atol=max(epsabs, err))
|
||||
|
||||
res, err = quad_vec(f, -np.inf, 0, **kwargs)
|
||||
assert_allclose(res, np.pi/2, rtol=0, atol=max(epsabs, err))
|
||||
|
||||
res, err = quad_vec(f, np.inf, 0, **kwargs)
|
||||
assert_allclose(res, -np.pi/2, rtol=0, atol=max(epsabs, err))
|
||||
|
||||
res, err = quad_vec(f, -np.inf, np.inf, **kwargs)
|
||||
assert_allclose(res, np.pi, rtol=0, atol=max(epsabs, err))
|
||||
|
||||
res, err = quad_vec(f, np.inf, -np.inf, **kwargs)
|
||||
assert_allclose(res, -np.pi, rtol=0, atol=max(epsabs, err))
|
||||
|
||||
res, err = quad_vec(f, np.inf, np.inf, **kwargs)
|
||||
assert_allclose(res, 0, rtol=0, atol=max(epsabs, err))
|
||||
|
||||
res, err = quad_vec(f, -np.inf, -np.inf, **kwargs)
|
||||
assert_allclose(res, 0, rtol=0, atol=max(epsabs, err))
|
||||
|
||||
res, err = quad_vec(f, 0, np.inf, points=(1.0, 2.0), **kwargs)
|
||||
assert_allclose(res, np.pi/2, rtol=0, atol=max(epsabs, err))
|
||||
|
||||
f = lambda x: np.sin(x + 2) / (1 + x**2)
|
||||
exact = np.pi / np.e * np.sin(2)
|
||||
epsabs = 1e-5
|
||||
|
||||
res, err, info = quad_vec(f, -np.inf, np.inf, limit=1000, norm='max', epsabs=epsabs,
|
||||
quadrature=quadrature, full_output=True)
|
||||
assert info.status == 1
|
||||
assert_allclose(res, exact, rtol=0, atol=max(epsabs, 1.5 * err))
|
||||
|
||||
|
||||
def _lorenzian(x):
|
||||
return 1 / (1 + x**2)
|
||||
|
||||
|
||||
def test_quad_vec_pool():
|
||||
from multiprocessing.dummy import Pool
|
||||
|
||||
f = _lorenzian
|
||||
res, err = quad_vec(f, -np.inf, np.inf, norm='max', epsabs=1e-4, workers=4)
|
||||
assert_allclose(res, np.pi, rtol=0, atol=1e-4)
|
||||
|
||||
with Pool(10) as pool:
|
||||
f = lambda x: 1 / (1 + x**2)
|
||||
res, err = quad_vec(f, -np.inf, np.inf, norm='max', epsabs=1e-4, workers=pool.map)
|
||||
assert_allclose(res, np.pi, rtol=0, atol=1e-4)
|
||||
|
||||
|
||||
@quadrature_params
|
||||
def test_num_eval(quadrature):
|
||||
def f(x):
|
||||
count[0] += 1
|
||||
return x**5
|
||||
|
||||
count = [0]
|
||||
res = quad_vec(f, 0, 1, norm='max', full_output=True, quadrature=quadrature)
|
||||
assert res[2].neval == count[0]
|
||||
|
||||
|
||||
def test_info():
|
||||
def f(x):
|
||||
return np.ones((3, 2, 1))
|
||||
|
||||
res, err, info = quad_vec(f, 0, 1, norm='max', full_output=True)
|
||||
|
||||
assert info.success == True
|
||||
assert info.status == 0
|
||||
assert info.message == 'Target precision reached.'
|
||||
assert info.neval > 0
|
||||
assert info.intervals.shape[1] == 2
|
||||
assert info.integrals.shape == (info.intervals.shape[0], 3, 2, 1)
|
||||
assert info.errors.shape == (info.intervals.shape[0],)
|
||||
|
||||
|
||||
def test_nan_inf():
|
||||
def f_nan(x):
|
||||
return np.nan
|
||||
|
||||
def f_inf(x):
|
||||
return np.inf if x < 0.1 else 1/x
|
||||
|
||||
res, err, info = quad_vec(f_nan, 0, 1, full_output=True)
|
||||
assert info.status == 3
|
||||
|
||||
res, err, info = quad_vec(f_inf, 0, 1, full_output=True)
|
||||
assert info.status == 3
|
||||
|
||||
|
||||
@pytest.mark.parametrize('a,b', [(0, 1), (0, np.inf), (np.inf, 0),
|
||||
(-np.inf, np.inf), (np.inf, -np.inf)])
|
||||
def test_points(a, b):
|
||||
# Check that initial interval splitting is done according to
|
||||
# `points`, by checking that consecutive sets of 15 point (for
|
||||
# gk15) function evaluations lie between `points`
|
||||
|
||||
points = (0, 0.25, 0.5, 0.75, 1.0)
|
||||
points += tuple(-x for x in points)
|
||||
|
||||
quadrature_points = 15
|
||||
interval_sets = []
|
||||
count = 0
|
||||
|
||||
def f(x):
|
||||
nonlocal count
|
||||
|
||||
if count % quadrature_points == 0:
|
||||
interval_sets.append(set())
|
||||
|
||||
count += 1
|
||||
interval_sets[-1].add(float(x))
|
||||
return 0.0
|
||||
|
||||
quad_vec(f, a, b, points=points, quadrature='gk15', limit=0)
|
||||
|
||||
# Check that all point sets lie in a single `points` interval
|
||||
for p in interval_sets:
|
||||
j = np.searchsorted(sorted(points), tuple(p))
|
||||
assert np.all(j == j[0])
|
|
@ -0,0 +1,218 @@
|
|||
import itertools
|
||||
import numpy as np
|
||||
from numpy.testing import assert_allclose
|
||||
from scipy.integrate import ode
|
||||
|
||||
|
||||
def _band_count(a):
|
||||
"""Returns ml and mu, the lower and upper band sizes of a."""
|
||||
nrows, ncols = a.shape
|
||||
ml = 0
|
||||
for k in range(-nrows+1, 0):
|
||||
if np.diag(a, k).any():
|
||||
ml = -k
|
||||
break
|
||||
mu = 0
|
||||
for k in range(nrows-1, 0, -1):
|
||||
if np.diag(a, k).any():
|
||||
mu = k
|
||||
break
|
||||
return ml, mu
|
||||
|
||||
|
||||
def _linear_func(t, y, a):
|
||||
"""Linear system dy/dt = a * y"""
|
||||
return a.dot(y)
|
||||
|
||||
|
||||
def _linear_jac(t, y, a):
|
||||
"""Jacobian of a * y is a."""
|
||||
return a
|
||||
|
||||
|
||||
def _linear_banded_jac(t, y, a):
|
||||
"""Banded Jacobian."""
|
||||
ml, mu = _band_count(a)
|
||||
bjac = [np.r_[[0] * k, np.diag(a, k)] for k in range(mu, 0, -1)]
|
||||
bjac.append(np.diag(a))
|
||||
for k in range(-1, -ml-1, -1):
|
||||
bjac.append(np.r_[np.diag(a, k), [0] * (-k)])
|
||||
return bjac
|
||||
|
||||
|
||||
def _solve_linear_sys(a, y0, tend=1, dt=0.1,
|
||||
solver=None, method='bdf', use_jac=True,
|
||||
with_jacobian=False, banded=False):
|
||||
"""Use scipy.integrate.ode to solve a linear system of ODEs.
|
||||
|
||||
a : square ndarray
|
||||
Matrix of the linear system to be solved.
|
||||
y0 : ndarray
|
||||
Initial condition
|
||||
tend : float
|
||||
Stop time.
|
||||
dt : float
|
||||
Step size of the output.
|
||||
solver : str
|
||||
If not None, this must be "vode", "lsoda" or "zvode".
|
||||
method : str
|
||||
Either "bdf" or "adams".
|
||||
use_jac : bool
|
||||
Determines if the jacobian function is passed to ode().
|
||||
with_jacobian : bool
|
||||
Passed to ode.set_integrator().
|
||||
banded : bool
|
||||
Determines whether a banded or full jacobian is used.
|
||||
If `banded` is True, `lband` and `uband` are determined by the
|
||||
values in `a`.
|
||||
"""
|
||||
if banded:
|
||||
lband, uband = _band_count(a)
|
||||
else:
|
||||
lband = None
|
||||
uband = None
|
||||
|
||||
if use_jac:
|
||||
if banded:
|
||||
r = ode(_linear_func, _linear_banded_jac)
|
||||
else:
|
||||
r = ode(_linear_func, _linear_jac)
|
||||
else:
|
||||
r = ode(_linear_func)
|
||||
|
||||
if solver is None:
|
||||
if np.iscomplexobj(a):
|
||||
solver = "zvode"
|
||||
else:
|
||||
solver = "vode"
|
||||
|
||||
r.set_integrator(solver,
|
||||
with_jacobian=with_jacobian,
|
||||
method=method,
|
||||
lband=lband, uband=uband,
|
||||
rtol=1e-9, atol=1e-10,
|
||||
)
|
||||
t0 = 0
|
||||
r.set_initial_value(y0, t0)
|
||||
r.set_f_params(a)
|
||||
r.set_jac_params(a)
|
||||
|
||||
t = [t0]
|
||||
y = [y0]
|
||||
while r.successful() and r.t < tend:
|
||||
r.integrate(r.t + dt)
|
||||
t.append(r.t)
|
||||
y.append(r.y)
|
||||
|
||||
t = np.array(t)
|
||||
y = np.array(y)
|
||||
return t, y
|
||||
|
||||
|
||||
def _analytical_solution(a, y0, t):
|
||||
"""
|
||||
Analytical solution to the linear differential equations dy/dt = a*y.
|
||||
|
||||
The solution is only valid if `a` is diagonalizable.
|
||||
|
||||
Returns a 2-D array with shape (len(t), len(y0)).
|
||||
"""
|
||||
lam, v = np.linalg.eig(a)
|
||||
c = np.linalg.solve(v, y0)
|
||||
e = c * np.exp(lam * t.reshape(-1, 1))
|
||||
sol = e.dot(v.T)
|
||||
return sol
|
||||
|
||||
|
||||
def test_banded_ode_solvers():
|
||||
# Test the "lsoda", "vode" and "zvode" solvers of the `ode` class
|
||||
# with a system that has a banded Jacobian matrix.
|
||||
|
||||
t_exact = np.linspace(0, 1.0, 5)
|
||||
|
||||
# --- Real arrays for testing the "lsoda" and "vode" solvers ---
|
||||
|
||||
# lband = 2, uband = 1:
|
||||
a_real = np.array([[-0.6, 0.1, 0.0, 0.0, 0.0],
|
||||
[0.2, -0.5, 0.9, 0.0, 0.0],
|
||||
[0.1, 0.1, -0.4, 0.1, 0.0],
|
||||
[0.0, 0.3, -0.1, -0.9, -0.3],
|
||||
[0.0, 0.0, 0.1, 0.1, -0.7]])
|
||||
|
||||
# lband = 0, uband = 1:
|
||||
a_real_upper = np.triu(a_real)
|
||||
|
||||
# lband = 2, uband = 0:
|
||||
a_real_lower = np.tril(a_real)
|
||||
|
||||
# lband = 0, uband = 0:
|
||||
a_real_diag = np.triu(a_real_lower)
|
||||
|
||||
real_matrices = [a_real, a_real_upper, a_real_lower, a_real_diag]
|
||||
real_solutions = []
|
||||
|
||||
for a in real_matrices:
|
||||
y0 = np.arange(1, a.shape[0] + 1)
|
||||
y_exact = _analytical_solution(a, y0, t_exact)
|
||||
real_solutions.append((y0, t_exact, y_exact))
|
||||
|
||||
def check_real(idx, solver, meth, use_jac, with_jac, banded):
|
||||
a = real_matrices[idx]
|
||||
y0, t_exact, y_exact = real_solutions[idx]
|
||||
t, y = _solve_linear_sys(a, y0,
|
||||
tend=t_exact[-1],
|
||||
dt=t_exact[1] - t_exact[0],
|
||||
solver=solver,
|
||||
method=meth,
|
||||
use_jac=use_jac,
|
||||
with_jacobian=with_jac,
|
||||
banded=banded)
|
||||
assert_allclose(t, t_exact)
|
||||
assert_allclose(y, y_exact)
|
||||
|
||||
for idx in range(len(real_matrices)):
|
||||
p = [['vode', 'lsoda'], # solver
|
||||
['bdf', 'adams'], # method
|
||||
[False, True], # use_jac
|
||||
[False, True], # with_jacobian
|
||||
[False, True]] # banded
|
||||
for solver, meth, use_jac, with_jac, banded in itertools.product(*p):
|
||||
check_real(idx, solver, meth, use_jac, with_jac, banded)
|
||||
|
||||
# --- Complex arrays for testing the "zvode" solver ---
|
||||
|
||||
# complex, lband = 2, uband = 1:
|
||||
a_complex = a_real - 0.5j * a_real
|
||||
|
||||
# complex, lband = 0, uband = 0:
|
||||
a_complex_diag = np.diag(np.diag(a_complex))
|
||||
|
||||
complex_matrices = [a_complex, a_complex_diag]
|
||||
complex_solutions = []
|
||||
|
||||
for a in complex_matrices:
|
||||
y0 = np.arange(1, a.shape[0] + 1) + 1j
|
||||
y_exact = _analytical_solution(a, y0, t_exact)
|
||||
complex_solutions.append((y0, t_exact, y_exact))
|
||||
|
||||
def check_complex(idx, solver, meth, use_jac, with_jac, banded):
|
||||
a = complex_matrices[idx]
|
||||
y0, t_exact, y_exact = complex_solutions[idx]
|
||||
t, y = _solve_linear_sys(a, y0,
|
||||
tend=t_exact[-1],
|
||||
dt=t_exact[1] - t_exact[0],
|
||||
solver=solver,
|
||||
method=meth,
|
||||
use_jac=use_jac,
|
||||
with_jacobian=with_jac,
|
||||
banded=banded)
|
||||
assert_allclose(t, t_exact)
|
||||
assert_allclose(y, y_exact)
|
||||
|
||||
for idx in range(len(complex_matrices)):
|
||||
p = [['bdf', 'adams'], # method
|
||||
[False, True], # use_jac
|
||||
[False, True], # with_jacobian
|
||||
[False, True]] # banded
|
||||
for meth, use_jac, with_jac, banded in itertools.product(*p):
|
||||
check_complex(idx, "zvode", meth, use_jac, with_jac, banded)
|
602
venv/Lib/site-packages/scipy/integrate/tests/test_bvp.py
Normal file
602
venv/Lib/site-packages/scipy/integrate/tests/test_bvp.py
Normal file
|
@ -0,0 +1,602 @@
|
|||
import sys
|
||||
|
||||
try:
|
||||
from StringIO import StringIO
|
||||
except ImportError:
|
||||
from io import StringIO
|
||||
|
||||
import numpy as np
|
||||
from numpy.testing import (assert_, assert_array_equal, assert_allclose,
|
||||
assert_equal)
|
||||
from pytest import raises as assert_raises
|
||||
|
||||
from scipy.sparse import coo_matrix
|
||||
from scipy.special import erf
|
||||
from scipy.integrate._bvp import (modify_mesh, estimate_fun_jac,
|
||||
estimate_bc_jac, compute_jac_indices,
|
||||
construct_global_jac, solve_bvp)
|
||||
|
||||
|
||||
def exp_fun(x, y):
|
||||
return np.vstack((y[1], y[0]))
|
||||
|
||||
|
||||
def exp_fun_jac(x, y):
|
||||
df_dy = np.empty((2, 2, x.shape[0]))
|
||||
df_dy[0, 0] = 0
|
||||
df_dy[0, 1] = 1
|
||||
df_dy[1, 0] = 1
|
||||
df_dy[1, 1] = 0
|
||||
return df_dy
|
||||
|
||||
|
||||
def exp_bc(ya, yb):
|
||||
return np.hstack((ya[0] - 1, yb[0]))
|
||||
|
||||
|
||||
def exp_bc_complex(ya, yb):
|
||||
return np.hstack((ya[0] - 1 - 1j, yb[0]))
|
||||
|
||||
|
||||
def exp_bc_jac(ya, yb):
|
||||
dbc_dya = np.array([
|
||||
[1, 0],
|
||||
[0, 0]
|
||||
])
|
||||
dbc_dyb = np.array([
|
||||
[0, 0],
|
||||
[1, 0]
|
||||
])
|
||||
return dbc_dya, dbc_dyb
|
||||
|
||||
|
||||
def exp_sol(x):
|
||||
return (np.exp(-x) - np.exp(x - 2)) / (1 - np.exp(-2))
|
||||
|
||||
|
||||
def sl_fun(x, y, p):
|
||||
return np.vstack((y[1], -p[0]**2 * y[0]))
|
||||
|
||||
|
||||
def sl_fun_jac(x, y, p):
|
||||
n, m = y.shape
|
||||
df_dy = np.empty((n, 2, m))
|
||||
df_dy[0, 0] = 0
|
||||
df_dy[0, 1] = 1
|
||||
df_dy[1, 0] = -p[0]**2
|
||||
df_dy[1, 1] = 0
|
||||
|
||||
df_dp = np.empty((n, 1, m))
|
||||
df_dp[0, 0] = 0
|
||||
df_dp[1, 0] = -2 * p[0] * y[0]
|
||||
|
||||
return df_dy, df_dp
|
||||
|
||||
|
||||
def sl_bc(ya, yb, p):
|
||||
return np.hstack((ya[0], yb[0], ya[1] - p[0]))
|
||||
|
||||
|
||||
def sl_bc_jac(ya, yb, p):
|
||||
dbc_dya = np.zeros((3, 2))
|
||||
dbc_dya[0, 0] = 1
|
||||
dbc_dya[2, 1] = 1
|
||||
|
||||
dbc_dyb = np.zeros((3, 2))
|
||||
dbc_dyb[1, 0] = 1
|
||||
|
||||
dbc_dp = np.zeros((3, 1))
|
||||
dbc_dp[2, 0] = -1
|
||||
|
||||
return dbc_dya, dbc_dyb, dbc_dp
|
||||
|
||||
|
||||
def sl_sol(x, p):
|
||||
return np.sin(p[0] * x)
|
||||
|
||||
|
||||
def emden_fun(x, y):
|
||||
return np.vstack((y[1], -y[0]**5))
|
||||
|
||||
|
||||
def emden_fun_jac(x, y):
|
||||
df_dy = np.empty((2, 2, x.shape[0]))
|
||||
df_dy[0, 0] = 0
|
||||
df_dy[0, 1] = 1
|
||||
df_dy[1, 0] = -5 * y[0]**4
|
||||
df_dy[1, 1] = 0
|
||||
return df_dy
|
||||
|
||||
|
||||
def emden_bc(ya, yb):
|
||||
return np.array([ya[1], yb[0] - (3/4)**0.5])
|
||||
|
||||
|
||||
def emden_bc_jac(ya, yb):
|
||||
dbc_dya = np.array([
|
||||
[0, 1],
|
||||
[0, 0]
|
||||
])
|
||||
dbc_dyb = np.array([
|
||||
[0, 0],
|
||||
[1, 0]
|
||||
])
|
||||
return dbc_dya, dbc_dyb
|
||||
|
||||
|
||||
def emden_sol(x):
|
||||
return (1 + x**2/3)**-0.5
|
||||
|
||||
|
||||
def undefined_fun(x, y):
|
||||
return np.zeros_like(y)
|
||||
|
||||
|
||||
def undefined_bc(ya, yb):
|
||||
return np.array([ya[0], yb[0] - 1])
|
||||
|
||||
|
||||
def big_fun(x, y):
|
||||
f = np.zeros_like(y)
|
||||
f[::2] = y[1::2]
|
||||
return f
|
||||
|
||||
|
||||
def big_bc(ya, yb):
|
||||
return np.hstack((ya[::2], yb[::2] - 1))
|
||||
|
||||
|
||||
def big_sol(x, n):
|
||||
y = np.ones((2 * n, x.size))
|
||||
y[::2] = x
|
||||
return x
|
||||
|
||||
|
||||
def shock_fun(x, y):
|
||||
eps = 1e-3
|
||||
return np.vstack((
|
||||
y[1],
|
||||
-(x * y[1] + eps * np.pi**2 * np.cos(np.pi * x) +
|
||||
np.pi * x * np.sin(np.pi * x)) / eps
|
||||
))
|
||||
|
||||
|
||||
def shock_bc(ya, yb):
|
||||
return np.array([ya[0] + 2, yb[0]])
|
||||
|
||||
|
||||
def shock_sol(x):
|
||||
eps = 1e-3
|
||||
k = np.sqrt(2 * eps)
|
||||
return np.cos(np.pi * x) + erf(x / k) / erf(1 / k)
|
||||
|
||||
|
||||
def nonlin_bc_fun(x, y):
|
||||
# laplace eq.
|
||||
return np.stack([y[1], np.zeros_like(x)])
|
||||
|
||||
|
||||
def nonlin_bc_bc(ya, yb):
|
||||
phiA, phipA = ya
|
||||
phiC, phipC = yb
|
||||
|
||||
kappa, ioA, ioC, V, f = 1.64, 0.01, 1.0e-4, 0.5, 38.9
|
||||
|
||||
# Butler-Volmer Kinetics at Anode
|
||||
hA = 0.0-phiA-0.0
|
||||
iA = ioA * (np.exp(f*hA) - np.exp(-f*hA))
|
||||
res0 = iA + kappa * phipA
|
||||
|
||||
# Butler-Volmer Kinetics at Cathode
|
||||
hC = V - phiC - 1.0
|
||||
iC = ioC * (np.exp(f*hC) - np.exp(-f*hC))
|
||||
res1 = iC - kappa*phipC
|
||||
|
||||
return np.array([res0, res1])
|
||||
|
||||
|
||||
def nonlin_bc_sol(x):
|
||||
return -0.13426436116763119 - 1.1308709 * x
|
||||
|
||||
|
||||
def test_modify_mesh():
|
||||
x = np.array([0, 1, 3, 9], dtype=float)
|
||||
x_new = modify_mesh(x, np.array([0]), np.array([2]))
|
||||
assert_array_equal(x_new, np.array([0, 0.5, 1, 3, 5, 7, 9]))
|
||||
|
||||
x = np.array([-6, -3, 0, 3, 6], dtype=float)
|
||||
x_new = modify_mesh(x, np.array([1], dtype=int), np.array([0, 2, 3]))
|
||||
assert_array_equal(x_new, [-6, -5, -4, -3, -1.5, 0, 1, 2, 3, 4, 5, 6])
|
||||
|
||||
|
||||
def test_compute_fun_jac():
|
||||
x = np.linspace(0, 1, 5)
|
||||
y = np.empty((2, x.shape[0]))
|
||||
y[0] = 0.01
|
||||
y[1] = 0.02
|
||||
p = np.array([])
|
||||
df_dy, df_dp = estimate_fun_jac(lambda x, y, p: exp_fun(x, y), x, y, p)
|
||||
df_dy_an = exp_fun_jac(x, y)
|
||||
assert_allclose(df_dy, df_dy_an)
|
||||
assert_(df_dp is None)
|
||||
|
||||
x = np.linspace(0, np.pi, 5)
|
||||
y = np.empty((2, x.shape[0]))
|
||||
y[0] = np.sin(x)
|
||||
y[1] = np.cos(x)
|
||||
p = np.array([1.0])
|
||||
df_dy, df_dp = estimate_fun_jac(sl_fun, x, y, p)
|
||||
df_dy_an, df_dp_an = sl_fun_jac(x, y, p)
|
||||
assert_allclose(df_dy, df_dy_an)
|
||||
assert_allclose(df_dp, df_dp_an)
|
||||
|
||||
x = np.linspace(0, 1, 10)
|
||||
y = np.empty((2, x.shape[0]))
|
||||
y[0] = (3/4)**0.5
|
||||
y[1] = 1e-4
|
||||
p = np.array([])
|
||||
df_dy, df_dp = estimate_fun_jac(lambda x, y, p: emden_fun(x, y), x, y, p)
|
||||
df_dy_an = emden_fun_jac(x, y)
|
||||
assert_allclose(df_dy, df_dy_an)
|
||||
assert_(df_dp is None)
|
||||
|
||||
|
||||
def test_compute_bc_jac():
|
||||
ya = np.array([-1.0, 2])
|
||||
yb = np.array([0.5, 3])
|
||||
p = np.array([])
|
||||
dbc_dya, dbc_dyb, dbc_dp = estimate_bc_jac(
|
||||
lambda ya, yb, p: exp_bc(ya, yb), ya, yb, p)
|
||||
dbc_dya_an, dbc_dyb_an = exp_bc_jac(ya, yb)
|
||||
assert_allclose(dbc_dya, dbc_dya_an)
|
||||
assert_allclose(dbc_dyb, dbc_dyb_an)
|
||||
assert_(dbc_dp is None)
|
||||
|
||||
ya = np.array([0.0, 1])
|
||||
yb = np.array([0.0, -1])
|
||||
p = np.array([0.5])
|
||||
dbc_dya, dbc_dyb, dbc_dp = estimate_bc_jac(sl_bc, ya, yb, p)
|
||||
dbc_dya_an, dbc_dyb_an, dbc_dp_an = sl_bc_jac(ya, yb, p)
|
||||
assert_allclose(dbc_dya, dbc_dya_an)
|
||||
assert_allclose(dbc_dyb, dbc_dyb_an)
|
||||
assert_allclose(dbc_dp, dbc_dp_an)
|
||||
|
||||
ya = np.array([0.5, 100])
|
||||
yb = np.array([-1000, 10.5])
|
||||
p = np.array([])
|
||||
dbc_dya, dbc_dyb, dbc_dp = estimate_bc_jac(
|
||||
lambda ya, yb, p: emden_bc(ya, yb), ya, yb, p)
|
||||
dbc_dya_an, dbc_dyb_an = emden_bc_jac(ya, yb)
|
||||
assert_allclose(dbc_dya, dbc_dya_an)
|
||||
assert_allclose(dbc_dyb, dbc_dyb_an)
|
||||
assert_(dbc_dp is None)
|
||||
|
||||
|
||||
def test_compute_jac_indices():
|
||||
n = 2
|
||||
m = 4
|
||||
k = 2
|
||||
i, j = compute_jac_indices(n, m, k)
|
||||
s = coo_matrix((np.ones_like(i), (i, j))).toarray()
|
||||
s_true = np.array([
|
||||
[1, 1, 1, 1, 0, 0, 0, 0, 1, 1],
|
||||
[1, 1, 1, 1, 0, 0, 0, 0, 1, 1],
|
||||
[0, 0, 1, 1, 1, 1, 0, 0, 1, 1],
|
||||
[0, 0, 1, 1, 1, 1, 0, 0, 1, 1],
|
||||
[0, 0, 0, 0, 1, 1, 1, 1, 1, 1],
|
||||
[0, 0, 0, 0, 1, 1, 1, 1, 1, 1],
|
||||
[1, 1, 0, 0, 0, 0, 1, 1, 1, 1],
|
||||
[1, 1, 0, 0, 0, 0, 1, 1, 1, 1],
|
||||
[1, 1, 0, 0, 0, 0, 1, 1, 1, 1],
|
||||
[1, 1, 0, 0, 0, 0, 1, 1, 1, 1],
|
||||
])
|
||||
assert_array_equal(s, s_true)
|
||||
|
||||
|
||||
def test_compute_global_jac():
|
||||
n = 2
|
||||
m = 5
|
||||
k = 1
|
||||
i_jac, j_jac = compute_jac_indices(2, 5, 1)
|
||||
x = np.linspace(0, 1, 5)
|
||||
h = np.diff(x)
|
||||
y = np.vstack((np.sin(np.pi * x), np.pi * np.cos(np.pi * x)))
|
||||
p = np.array([3.0])
|
||||
|
||||
f = sl_fun(x, y, p)
|
||||
|
||||
x_middle = x[:-1] + 0.5 * h
|
||||
y_middle = 0.5 * (y[:, :-1] + y[:, 1:]) - h/8 * (f[:, 1:] - f[:, :-1])
|
||||
|
||||
df_dy, df_dp = sl_fun_jac(x, y, p)
|
||||
df_dy_middle, df_dp_middle = sl_fun_jac(x_middle, y_middle, p)
|
||||
dbc_dya, dbc_dyb, dbc_dp = sl_bc_jac(y[:, 0], y[:, -1], p)
|
||||
|
||||
J = construct_global_jac(n, m, k, i_jac, j_jac, h, df_dy, df_dy_middle,
|
||||
df_dp, df_dp_middle, dbc_dya, dbc_dyb, dbc_dp)
|
||||
J = J.toarray()
|
||||
|
||||
def J_block(h, p):
|
||||
return np.array([
|
||||
[h**2*p**2/12 - 1, -0.5*h, -h**2*p**2/12 + 1, -0.5*h],
|
||||
[0.5*h*p**2, h**2*p**2/12 - 1, 0.5*h*p**2, 1 - h**2*p**2/12]
|
||||
])
|
||||
|
||||
J_true = np.zeros((m * n + k, m * n + k))
|
||||
for i in range(m - 1):
|
||||
J_true[i * n: (i + 1) * n, i * n: (i + 2) * n] = J_block(h[i], p[0])
|
||||
|
||||
J_true[:(m - 1) * n:2, -1] = p * h**2/6 * (y[0, :-1] - y[0, 1:])
|
||||
J_true[1:(m - 1) * n:2, -1] = p * (h * (y[0, :-1] + y[0, 1:]) +
|
||||
h**2/6 * (y[1, :-1] - y[1, 1:]))
|
||||
|
||||
J_true[8, 0] = 1
|
||||
J_true[9, 8] = 1
|
||||
J_true[10, 1] = 1
|
||||
J_true[10, 10] = -1
|
||||
|
||||
assert_allclose(J, J_true, rtol=1e-10)
|
||||
|
||||
df_dy, df_dp = estimate_fun_jac(sl_fun, x, y, p)
|
||||
df_dy_middle, df_dp_middle = estimate_fun_jac(sl_fun, x_middle, y_middle, p)
|
||||
dbc_dya, dbc_dyb, dbc_dp = estimate_bc_jac(sl_bc, y[:, 0], y[:, -1], p)
|
||||
J = construct_global_jac(n, m, k, i_jac, j_jac, h, df_dy, df_dy_middle,
|
||||
df_dp, df_dp_middle, dbc_dya, dbc_dyb, dbc_dp)
|
||||
J = J.toarray()
|
||||
assert_allclose(J, J_true, rtol=1e-8, atol=1e-9)
|
||||
|
||||
|
||||
def test_parameter_validation():
|
||||
x = [0, 1, 0.5]
|
||||
y = np.zeros((2, 3))
|
||||
assert_raises(ValueError, solve_bvp, exp_fun, exp_bc, x, y)
|
||||
|
||||
x = np.linspace(0, 1, 5)
|
||||
y = np.zeros((2, 4))
|
||||
assert_raises(ValueError, solve_bvp, exp_fun, exp_bc, x, y)
|
||||
|
||||
fun = lambda x, y, p: exp_fun(x, y)
|
||||
bc = lambda ya, yb, p: exp_bc(ya, yb)
|
||||
|
||||
y = np.zeros((2, x.shape[0]))
|
||||
assert_raises(ValueError, solve_bvp, fun, bc, x, y, p=[1])
|
||||
|
||||
def wrong_shape_fun(x, y):
|
||||
return np.zeros(3)
|
||||
|
||||
assert_raises(ValueError, solve_bvp, wrong_shape_fun, bc, x, y)
|
||||
|
||||
S = np.array([[0, 0]])
|
||||
assert_raises(ValueError, solve_bvp, exp_fun, exp_bc, x, y, S=S)
|
||||
|
||||
|
||||
def test_no_params():
|
||||
x = np.linspace(0, 1, 5)
|
||||
x_test = np.linspace(0, 1, 100)
|
||||
y = np.zeros((2, x.shape[0]))
|
||||
for fun_jac in [None, exp_fun_jac]:
|
||||
for bc_jac in [None, exp_bc_jac]:
|
||||
sol = solve_bvp(exp_fun, exp_bc, x, y, fun_jac=fun_jac,
|
||||
bc_jac=bc_jac)
|
||||
|
||||
assert_equal(sol.status, 0)
|
||||
assert_(sol.success)
|
||||
|
||||
assert_equal(sol.x.size, 5)
|
||||
|
||||
sol_test = sol.sol(x_test)
|
||||
|
||||
assert_allclose(sol_test[0], exp_sol(x_test), atol=1e-5)
|
||||
|
||||
f_test = exp_fun(x_test, sol_test)
|
||||
r = sol.sol(x_test, 1) - f_test
|
||||
rel_res = r / (1 + np.abs(f_test))
|
||||
norm_res = np.sum(rel_res**2, axis=0)**0.5
|
||||
assert_(np.all(norm_res < 1e-3))
|
||||
|
||||
assert_(np.all(sol.rms_residuals < 1e-3))
|
||||
assert_allclose(sol.sol(sol.x), sol.y, rtol=1e-10, atol=1e-10)
|
||||
assert_allclose(sol.sol(sol.x, 1), sol.yp, rtol=1e-10, atol=1e-10)
|
||||
|
||||
|
||||
def test_with_params():
|
||||
x = np.linspace(0, np.pi, 5)
|
||||
x_test = np.linspace(0, np.pi, 100)
|
||||
y = np.ones((2, x.shape[0]))
|
||||
|
||||
for fun_jac in [None, sl_fun_jac]:
|
||||
for bc_jac in [None, sl_bc_jac]:
|
||||
sol = solve_bvp(sl_fun, sl_bc, x, y, p=[0.5], fun_jac=fun_jac,
|
||||
bc_jac=bc_jac)
|
||||
|
||||
assert_equal(sol.status, 0)
|
||||
assert_(sol.success)
|
||||
|
||||
assert_(sol.x.size < 10)
|
||||
|
||||
assert_allclose(sol.p, [1], rtol=1e-4)
|
||||
|
||||
sol_test = sol.sol(x_test)
|
||||
|
||||
assert_allclose(sol_test[0], sl_sol(x_test, [1]),
|
||||
rtol=1e-4, atol=1e-4)
|
||||
|
||||
f_test = sl_fun(x_test, sol_test, [1])
|
||||
r = sol.sol(x_test, 1) - f_test
|
||||
rel_res = r / (1 + np.abs(f_test))
|
||||
norm_res = np.sum(rel_res ** 2, axis=0) ** 0.5
|
||||
assert_(np.all(norm_res < 1e-3))
|
||||
|
||||
assert_(np.all(sol.rms_residuals < 1e-3))
|
||||
assert_allclose(sol.sol(sol.x), sol.y, rtol=1e-10, atol=1e-10)
|
||||
assert_allclose(sol.sol(sol.x, 1), sol.yp, rtol=1e-10, atol=1e-10)
|
||||
|
||||
|
||||
def test_singular_term():
|
||||
x = np.linspace(0, 1, 10)
|
||||
x_test = np.linspace(0.05, 1, 100)
|
||||
y = np.empty((2, 10))
|
||||
y[0] = (3/4)**0.5
|
||||
y[1] = 1e-4
|
||||
S = np.array([[0, 0], [0, -2]])
|
||||
|
||||
for fun_jac in [None, emden_fun_jac]:
|
||||
for bc_jac in [None, emden_bc_jac]:
|
||||
sol = solve_bvp(emden_fun, emden_bc, x, y, S=S, fun_jac=fun_jac,
|
||||
bc_jac=bc_jac)
|
||||
|
||||
assert_equal(sol.status, 0)
|
||||
assert_(sol.success)
|
||||
|
||||
assert_equal(sol.x.size, 10)
|
||||
|
||||
sol_test = sol.sol(x_test)
|
||||
assert_allclose(sol_test[0], emden_sol(x_test), atol=1e-5)
|
||||
|
||||
f_test = emden_fun(x_test, sol_test) + S.dot(sol_test) / x_test
|
||||
r = sol.sol(x_test, 1) - f_test
|
||||
rel_res = r / (1 + np.abs(f_test))
|
||||
norm_res = np.sum(rel_res ** 2, axis=0) ** 0.5
|
||||
|
||||
assert_(np.all(norm_res < 1e-3))
|
||||
assert_allclose(sol.sol(sol.x), sol.y, rtol=1e-10, atol=1e-10)
|
||||
assert_allclose(sol.sol(sol.x, 1), sol.yp, rtol=1e-10, atol=1e-10)
|
||||
|
||||
|
||||
def test_complex():
|
||||
# The test is essentially the same as test_no_params, but boundary
|
||||
# conditions are turned into complex.
|
||||
x = np.linspace(0, 1, 5)
|
||||
x_test = np.linspace(0, 1, 100)
|
||||
y = np.zeros((2, x.shape[0]), dtype=complex)
|
||||
for fun_jac in [None, exp_fun_jac]:
|
||||
for bc_jac in [None, exp_bc_jac]:
|
||||
sol = solve_bvp(exp_fun, exp_bc_complex, x, y, fun_jac=fun_jac,
|
||||
bc_jac=bc_jac)
|
||||
|
||||
assert_equal(sol.status, 0)
|
||||
assert_(sol.success)
|
||||
|
||||
sol_test = sol.sol(x_test)
|
||||
|
||||
assert_allclose(sol_test[0].real, exp_sol(x_test), atol=1e-5)
|
||||
assert_allclose(sol_test[0].imag, exp_sol(x_test), atol=1e-5)
|
||||
|
||||
f_test = exp_fun(x_test, sol_test)
|
||||
r = sol.sol(x_test, 1) - f_test
|
||||
rel_res = r / (1 + np.abs(f_test))
|
||||
norm_res = np.sum(np.real(rel_res * np.conj(rel_res)),
|
||||
axis=0) ** 0.5
|
||||
assert_(np.all(norm_res < 1e-3))
|
||||
|
||||
assert_(np.all(sol.rms_residuals < 1e-3))
|
||||
assert_allclose(sol.sol(sol.x), sol.y, rtol=1e-10, atol=1e-10)
|
||||
assert_allclose(sol.sol(sol.x, 1), sol.yp, rtol=1e-10, atol=1e-10)
|
||||
|
||||
|
||||
def test_failures():
|
||||
x = np.linspace(0, 1, 2)
|
||||
y = np.zeros((2, x.size))
|
||||
res = solve_bvp(exp_fun, exp_bc, x, y, tol=1e-5, max_nodes=5)
|
||||
assert_equal(res.status, 1)
|
||||
assert_(not res.success)
|
||||
|
||||
x = np.linspace(0, 1, 5)
|
||||
y = np.zeros((2, x.size))
|
||||
res = solve_bvp(undefined_fun, undefined_bc, x, y)
|
||||
assert_equal(res.status, 2)
|
||||
assert_(not res.success)
|
||||
|
||||
|
||||
def test_big_problem():
|
||||
n = 30
|
||||
x = np.linspace(0, 1, 5)
|
||||
y = np.zeros((2 * n, x.size))
|
||||
sol = solve_bvp(big_fun, big_bc, x, y)
|
||||
|
||||
assert_equal(sol.status, 0)
|
||||
assert_(sol.success)
|
||||
|
||||
sol_test = sol.sol(x)
|
||||
|
||||
assert_allclose(sol_test[0], big_sol(x, n))
|
||||
|
||||
f_test = big_fun(x, sol_test)
|
||||
r = sol.sol(x, 1) - f_test
|
||||
rel_res = r / (1 + np.abs(f_test))
|
||||
norm_res = np.sum(np.real(rel_res * np.conj(rel_res)), axis=0) ** 0.5
|
||||
assert_(np.all(norm_res < 1e-3))
|
||||
|
||||
assert_(np.all(sol.rms_residuals < 1e-3))
|
||||
assert_allclose(sol.sol(sol.x), sol.y, rtol=1e-10, atol=1e-10)
|
||||
assert_allclose(sol.sol(sol.x, 1), sol.yp, rtol=1e-10, atol=1e-10)
|
||||
|
||||
|
||||
def test_shock_layer():
|
||||
x = np.linspace(-1, 1, 5)
|
||||
x_test = np.linspace(-1, 1, 100)
|
||||
y = np.zeros((2, x.size))
|
||||
sol = solve_bvp(shock_fun, shock_bc, x, y)
|
||||
|
||||
assert_equal(sol.status, 0)
|
||||
assert_(sol.success)
|
||||
|
||||
assert_(sol.x.size < 110)
|
||||
|
||||
sol_test = sol.sol(x_test)
|
||||
assert_allclose(sol_test[0], shock_sol(x_test), rtol=1e-5, atol=1e-5)
|
||||
|
||||
f_test = shock_fun(x_test, sol_test)
|
||||
r = sol.sol(x_test, 1) - f_test
|
||||
rel_res = r / (1 + np.abs(f_test))
|
||||
norm_res = np.sum(rel_res ** 2, axis=0) ** 0.5
|
||||
|
||||
assert_(np.all(norm_res < 1e-3))
|
||||
assert_allclose(sol.sol(sol.x), sol.y, rtol=1e-10, atol=1e-10)
|
||||
assert_allclose(sol.sol(sol.x, 1), sol.yp, rtol=1e-10, atol=1e-10)
|
||||
|
||||
|
||||
def test_nonlin_bc():
|
||||
x = np.linspace(0, 0.1, 5)
|
||||
x_test = x
|
||||
y = np.zeros([2, x.size])
|
||||
sol = solve_bvp(nonlin_bc_fun, nonlin_bc_bc, x, y)
|
||||
|
||||
assert_equal(sol.status, 0)
|
||||
assert_(sol.success)
|
||||
|
||||
assert_(sol.x.size < 8)
|
||||
|
||||
sol_test = sol.sol(x_test)
|
||||
assert_allclose(sol_test[0], nonlin_bc_sol(x_test), rtol=1e-5, atol=1e-5)
|
||||
|
||||
f_test = nonlin_bc_fun(x_test, sol_test)
|
||||
r = sol.sol(x_test, 1) - f_test
|
||||
rel_res = r / (1 + np.abs(f_test))
|
||||
norm_res = np.sum(rel_res ** 2, axis=0) ** 0.5
|
||||
|
||||
assert_(np.all(norm_res < 1e-3))
|
||||
assert_allclose(sol.sol(sol.x), sol.y, rtol=1e-10, atol=1e-10)
|
||||
assert_allclose(sol.sol(sol.x, 1), sol.yp, rtol=1e-10, atol=1e-10)
|
||||
|
||||
|
||||
def test_verbose():
|
||||
# Smoke test that checks the printing does something and does not crash
|
||||
x = np.linspace(0, 1, 5)
|
||||
y = np.zeros((2, x.shape[0]))
|
||||
for verbose in [0, 1, 2]:
|
||||
old_stdout = sys.stdout
|
||||
sys.stdout = StringIO()
|
||||
try:
|
||||
sol = solve_bvp(exp_fun, exp_bc, x, y, verbose=verbose)
|
||||
text = sys.stdout.getvalue()
|
||||
finally:
|
||||
sys.stdout = old_stdout
|
||||
|
||||
assert_(sol.success)
|
||||
if verbose == 0:
|
||||
assert_(not text, text)
|
||||
if verbose >= 1:
|
||||
assert_("Solved in" in text, text)
|
||||
if verbose >= 2:
|
||||
assert_("Max residual" in text, text)
|
830
venv/Lib/site-packages/scipy/integrate/tests/test_integrate.py
Normal file
830
venv/Lib/site-packages/scipy/integrate/tests/test_integrate.py
Normal file
|
@ -0,0 +1,830 @@
|
|||
# Authors: Nils Wagner, Ed Schofield, Pauli Virtanen, John Travers
|
||||
"""
|
||||
Tests for numerical integration.
|
||||
"""
|
||||
import numpy as np
|
||||
from numpy import (arange, zeros, array, dot, sqrt, cos, sin, eye, pi, exp,
|
||||
allclose)
|
||||
|
||||
from numpy.testing import (
|
||||
assert_, assert_array_almost_equal,
|
||||
assert_allclose, assert_array_equal, assert_equal, assert_warns)
|
||||
from pytest import raises as assert_raises
|
||||
from scipy.integrate import odeint, ode, complex_ode
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Test ODE integrators
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestOdeint(object):
|
||||
# Check integrate.odeint
|
||||
|
||||
def _do_problem(self, problem):
|
||||
t = arange(0.0, problem.stop_t, 0.05)
|
||||
|
||||
# Basic case
|
||||
z, infodict = odeint(problem.f, problem.z0, t, full_output=True)
|
||||
assert_(problem.verify(z, t))
|
||||
|
||||
# Use tfirst=True
|
||||
z, infodict = odeint(lambda t, y: problem.f(y, t), problem.z0, t,
|
||||
full_output=True, tfirst=True)
|
||||
assert_(problem.verify(z, t))
|
||||
|
||||
if hasattr(problem, 'jac'):
|
||||
# Use Dfun
|
||||
z, infodict = odeint(problem.f, problem.z0, t, Dfun=problem.jac,
|
||||
full_output=True)
|
||||
assert_(problem.verify(z, t))
|
||||
|
||||
# Use Dfun and tfirst=True
|
||||
z, infodict = odeint(lambda t, y: problem.f(y, t), problem.z0, t,
|
||||
Dfun=lambda t, y: problem.jac(y, t),
|
||||
full_output=True, tfirst=True)
|
||||
assert_(problem.verify(z, t))
|
||||
|
||||
def test_odeint(self):
|
||||
for problem_cls in PROBLEMS:
|
||||
problem = problem_cls()
|
||||
if problem.cmplx:
|
||||
continue
|
||||
self._do_problem(problem)
|
||||
|
||||
|
||||
class TestODEClass(object):
|
||||
|
||||
ode_class = None # Set in subclass.
|
||||
|
||||
def _do_problem(self, problem, integrator, method='adams'):
|
||||
|
||||
# ode has callback arguments in different order than odeint
|
||||
f = lambda t, z: problem.f(z, t)
|
||||
jac = None
|
||||
if hasattr(problem, 'jac'):
|
||||
jac = lambda t, z: problem.jac(z, t)
|
||||
|
||||
integrator_params = {}
|
||||
if problem.lband is not None or problem.uband is not None:
|
||||
integrator_params['uband'] = problem.uband
|
||||
integrator_params['lband'] = problem.lband
|
||||
|
||||
ig = self.ode_class(f, jac)
|
||||
ig.set_integrator(integrator,
|
||||
atol=problem.atol/10,
|
||||
rtol=problem.rtol/10,
|
||||
method=method,
|
||||
**integrator_params)
|
||||
|
||||
ig.set_initial_value(problem.z0, t=0.0)
|
||||
z = ig.integrate(problem.stop_t)
|
||||
|
||||
assert_array_equal(z, ig.y)
|
||||
assert_(ig.successful(), (problem, method))
|
||||
assert_(ig.get_return_code() > 0, (problem, method))
|
||||
assert_(problem.verify(array([z]), problem.stop_t), (problem, method))
|
||||
|
||||
|
||||
class TestOde(TestODEClass):
|
||||
|
||||
ode_class = ode
|
||||
|
||||
def test_vode(self):
|
||||
# Check the vode solver
|
||||
for problem_cls in PROBLEMS:
|
||||
problem = problem_cls()
|
||||
if problem.cmplx:
|
||||
continue
|
||||
if not problem.stiff:
|
||||
self._do_problem(problem, 'vode', 'adams')
|
||||
self._do_problem(problem, 'vode', 'bdf')
|
||||
|
||||
def test_zvode(self):
|
||||
# Check the zvode solver
|
||||
for problem_cls in PROBLEMS:
|
||||
problem = problem_cls()
|
||||
if not problem.stiff:
|
||||
self._do_problem(problem, 'zvode', 'adams')
|
||||
self._do_problem(problem, 'zvode', 'bdf')
|
||||
|
||||
def test_lsoda(self):
|
||||
# Check the lsoda solver
|
||||
for problem_cls in PROBLEMS:
|
||||
problem = problem_cls()
|
||||
if problem.cmplx:
|
||||
continue
|
||||
self._do_problem(problem, 'lsoda')
|
||||
|
||||
def test_dopri5(self):
|
||||
# Check the dopri5 solver
|
||||
for problem_cls in PROBLEMS:
|
||||
problem = problem_cls()
|
||||
if problem.cmplx:
|
||||
continue
|
||||
if problem.stiff:
|
||||
continue
|
||||
if hasattr(problem, 'jac'):
|
||||
continue
|
||||
self._do_problem(problem, 'dopri5')
|
||||
|
||||
def test_dop853(self):
|
||||
# Check the dop853 solver
|
||||
for problem_cls in PROBLEMS:
|
||||
problem = problem_cls()
|
||||
if problem.cmplx:
|
||||
continue
|
||||
if problem.stiff:
|
||||
continue
|
||||
if hasattr(problem, 'jac'):
|
||||
continue
|
||||
self._do_problem(problem, 'dop853')
|
||||
|
||||
def test_concurrent_fail(self):
|
||||
for sol in ('vode', 'zvode', 'lsoda'):
|
||||
f = lambda t, y: 1.0
|
||||
|
||||
r = ode(f).set_integrator(sol)
|
||||
r.set_initial_value(0, 0)
|
||||
|
||||
r2 = ode(f).set_integrator(sol)
|
||||
r2.set_initial_value(0, 0)
|
||||
|
||||
r.integrate(r.t + 0.1)
|
||||
r2.integrate(r2.t + 0.1)
|
||||
|
||||
assert_raises(RuntimeError, r.integrate, r.t + 0.1)
|
||||
|
||||
def test_concurrent_ok(self):
|
||||
f = lambda t, y: 1.0
|
||||
|
||||
for k in range(3):
|
||||
for sol in ('vode', 'zvode', 'lsoda', 'dopri5', 'dop853'):
|
||||
r = ode(f).set_integrator(sol)
|
||||
r.set_initial_value(0, 0)
|
||||
|
||||
r2 = ode(f).set_integrator(sol)
|
||||
r2.set_initial_value(0, 0)
|
||||
|
||||
r.integrate(r.t + 0.1)
|
||||
r2.integrate(r2.t + 0.1)
|
||||
r2.integrate(r2.t + 0.1)
|
||||
|
||||
assert_allclose(r.y, 0.1)
|
||||
assert_allclose(r2.y, 0.2)
|
||||
|
||||
for sol in ('dopri5', 'dop853'):
|
||||
r = ode(f).set_integrator(sol)
|
||||
r.set_initial_value(0, 0)
|
||||
|
||||
r2 = ode(f).set_integrator(sol)
|
||||
r2.set_initial_value(0, 0)
|
||||
|
||||
r.integrate(r.t + 0.1)
|
||||
r.integrate(r.t + 0.1)
|
||||
r2.integrate(r2.t + 0.1)
|
||||
r.integrate(r.t + 0.1)
|
||||
r2.integrate(r2.t + 0.1)
|
||||
|
||||
assert_allclose(r.y, 0.3)
|
||||
assert_allclose(r2.y, 0.2)
|
||||
|
||||
|
||||
class TestComplexOde(TestODEClass):
|
||||
|
||||
ode_class = complex_ode
|
||||
|
||||
def test_vode(self):
|
||||
# Check the vode solver
|
||||
for problem_cls in PROBLEMS:
|
||||
problem = problem_cls()
|
||||
if not problem.stiff:
|
||||
self._do_problem(problem, 'vode', 'adams')
|
||||
else:
|
||||
self._do_problem(problem, 'vode', 'bdf')
|
||||
|
||||
def test_lsoda(self):
|
||||
# Check the lsoda solver
|
||||
for problem_cls in PROBLEMS:
|
||||
problem = problem_cls()
|
||||
self._do_problem(problem, 'lsoda')
|
||||
|
||||
def test_dopri5(self):
|
||||
# Check the dopri5 solver
|
||||
for problem_cls in PROBLEMS:
|
||||
problem = problem_cls()
|
||||
if problem.stiff:
|
||||
continue
|
||||
if hasattr(problem, 'jac'):
|
||||
continue
|
||||
self._do_problem(problem, 'dopri5')
|
||||
|
||||
def test_dop853(self):
|
||||
# Check the dop853 solver
|
||||
for problem_cls in PROBLEMS:
|
||||
problem = problem_cls()
|
||||
if problem.stiff:
|
||||
continue
|
||||
if hasattr(problem, 'jac'):
|
||||
continue
|
||||
self._do_problem(problem, 'dop853')
|
||||
|
||||
|
||||
class TestSolout(object):
|
||||
# Check integrate.ode correctly handles solout for dopri5 and dop853
|
||||
def _run_solout_test(self, integrator):
|
||||
# Check correct usage of solout
|
||||
ts = []
|
||||
ys = []
|
||||
t0 = 0.0
|
||||
tend = 10.0
|
||||
y0 = [1.0, 2.0]
|
||||
|
||||
def solout(t, y):
|
||||
ts.append(t)
|
||||
ys.append(y.copy())
|
||||
|
||||
def rhs(t, y):
|
||||
return [y[0] + y[1], -y[1]**2]
|
||||
|
||||
ig = ode(rhs).set_integrator(integrator)
|
||||
ig.set_solout(solout)
|
||||
ig.set_initial_value(y0, t0)
|
||||
ret = ig.integrate(tend)
|
||||
assert_array_equal(ys[0], y0)
|
||||
assert_array_equal(ys[-1], ret)
|
||||
assert_equal(ts[0], t0)
|
||||
assert_equal(ts[-1], tend)
|
||||
|
||||
def test_solout(self):
|
||||
for integrator in ('dopri5', 'dop853'):
|
||||
self._run_solout_test(integrator)
|
||||
|
||||
def _run_solout_after_initial_test(self, integrator):
|
||||
# Check if solout works even if it is set after the initial value.
|
||||
ts = []
|
||||
ys = []
|
||||
t0 = 0.0
|
||||
tend = 10.0
|
||||
y0 = [1.0, 2.0]
|
||||
|
||||
def solout(t, y):
|
||||
ts.append(t)
|
||||
ys.append(y.copy())
|
||||
|
||||
def rhs(t, y):
|
||||
return [y[0] + y[1], -y[1]**2]
|
||||
|
||||
ig = ode(rhs).set_integrator(integrator)
|
||||
ig.set_initial_value(y0, t0)
|
||||
ig.set_solout(solout)
|
||||
ret = ig.integrate(tend)
|
||||
assert_array_equal(ys[0], y0)
|
||||
assert_array_equal(ys[-1], ret)
|
||||
assert_equal(ts[0], t0)
|
||||
assert_equal(ts[-1], tend)
|
||||
|
||||
def test_solout_after_initial(self):
|
||||
for integrator in ('dopri5', 'dop853'):
|
||||
self._run_solout_after_initial_test(integrator)
|
||||
|
||||
def _run_solout_break_test(self, integrator):
|
||||
# Check correct usage of stopping via solout
|
||||
ts = []
|
||||
ys = []
|
||||
t0 = 0.0
|
||||
tend = 10.0
|
||||
y0 = [1.0, 2.0]
|
||||
|
||||
def solout(t, y):
|
||||
ts.append(t)
|
||||
ys.append(y.copy())
|
||||
if t > tend/2.0:
|
||||
return -1
|
||||
|
||||
def rhs(t, y):
|
||||
return [y[0] + y[1], -y[1]**2]
|
||||
|
||||
ig = ode(rhs).set_integrator(integrator)
|
||||
ig.set_solout(solout)
|
||||
ig.set_initial_value(y0, t0)
|
||||
ret = ig.integrate(tend)
|
||||
assert_array_equal(ys[0], y0)
|
||||
assert_array_equal(ys[-1], ret)
|
||||
assert_equal(ts[0], t0)
|
||||
assert_(ts[-1] > tend/2.0)
|
||||
assert_(ts[-1] < tend)
|
||||
|
||||
def test_solout_break(self):
|
||||
for integrator in ('dopri5', 'dop853'):
|
||||
self._run_solout_break_test(integrator)
|
||||
|
||||
|
||||
class TestComplexSolout(object):
|
||||
# Check integrate.ode correctly handles solout for dopri5 and dop853
|
||||
def _run_solout_test(self, integrator):
|
||||
# Check correct usage of solout
|
||||
ts = []
|
||||
ys = []
|
||||
t0 = 0.0
|
||||
tend = 20.0
|
||||
y0 = [0.0]
|
||||
|
||||
def solout(t, y):
|
||||
ts.append(t)
|
||||
ys.append(y.copy())
|
||||
|
||||
def rhs(t, y):
|
||||
return [1.0/(t - 10.0 - 1j)]
|
||||
|
||||
ig = complex_ode(rhs).set_integrator(integrator)
|
||||
ig.set_solout(solout)
|
||||
ig.set_initial_value(y0, t0)
|
||||
ret = ig.integrate(tend)
|
||||
assert_array_equal(ys[0], y0)
|
||||
assert_array_equal(ys[-1], ret)
|
||||
assert_equal(ts[0], t0)
|
||||
assert_equal(ts[-1], tend)
|
||||
|
||||
def test_solout(self):
|
||||
for integrator in ('dopri5', 'dop853'):
|
||||
self._run_solout_test(integrator)
|
||||
|
||||
def _run_solout_break_test(self, integrator):
|
||||
# Check correct usage of stopping via solout
|
||||
ts = []
|
||||
ys = []
|
||||
t0 = 0.0
|
||||
tend = 20.0
|
||||
y0 = [0.0]
|
||||
|
||||
def solout(t, y):
|
||||
ts.append(t)
|
||||
ys.append(y.copy())
|
||||
if t > tend/2.0:
|
||||
return -1
|
||||
|
||||
def rhs(t, y):
|
||||
return [1.0/(t - 10.0 - 1j)]
|
||||
|
||||
ig = complex_ode(rhs).set_integrator(integrator)
|
||||
ig.set_solout(solout)
|
||||
ig.set_initial_value(y0, t0)
|
||||
ret = ig.integrate(tend)
|
||||
assert_array_equal(ys[0], y0)
|
||||
assert_array_equal(ys[-1], ret)
|
||||
assert_equal(ts[0], t0)
|
||||
assert_(ts[-1] > tend/2.0)
|
||||
assert_(ts[-1] < tend)
|
||||
|
||||
def test_solout_break(self):
|
||||
for integrator in ('dopri5', 'dop853'):
|
||||
self._run_solout_break_test(integrator)
|
||||
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Test problems
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
|
||||
class ODE:
|
||||
"""
|
||||
ODE problem
|
||||
"""
|
||||
stiff = False
|
||||
cmplx = False
|
||||
stop_t = 1
|
||||
z0 = []
|
||||
|
||||
lband = None
|
||||
uband = None
|
||||
|
||||
atol = 1e-6
|
||||
rtol = 1e-5
|
||||
|
||||
|
||||
class SimpleOscillator(ODE):
|
||||
r"""
|
||||
Free vibration of a simple oscillator::
|
||||
m \ddot{u} + k u = 0, u(0) = u_0 \dot{u}(0) \dot{u}_0
|
||||
Solution::
|
||||
u(t) = u_0*cos(sqrt(k/m)*t)+\dot{u}_0*sin(sqrt(k/m)*t)/sqrt(k/m)
|
||||
"""
|
||||
stop_t = 1 + 0.09
|
||||
z0 = array([1.0, 0.1], float)
|
||||
|
||||
k = 4.0
|
||||
m = 1.0
|
||||
|
||||
def f(self, z, t):
|
||||
tmp = zeros((2, 2), float)
|
||||
tmp[0, 1] = 1.0
|
||||
tmp[1, 0] = -self.k / self.m
|
||||
return dot(tmp, z)
|
||||
|
||||
def verify(self, zs, t):
|
||||
omega = sqrt(self.k / self.m)
|
||||
u = self.z0[0]*cos(omega*t) + self.z0[1]*sin(omega*t)/omega
|
||||
return allclose(u, zs[:, 0], atol=self.atol, rtol=self.rtol)
|
||||
|
||||
|
||||
class ComplexExp(ODE):
|
||||
r"""The equation :lm:`\dot u = i u`"""
|
||||
stop_t = 1.23*pi
|
||||
z0 = exp([1j, 2j, 3j, 4j, 5j])
|
||||
cmplx = True
|
||||
|
||||
def f(self, z, t):
|
||||
return 1j*z
|
||||
|
||||
def jac(self, z, t):
|
||||
return 1j*eye(5)
|
||||
|
||||
def verify(self, zs, t):
|
||||
u = self.z0 * exp(1j*t)
|
||||
return allclose(u, zs, atol=self.atol, rtol=self.rtol)
|
||||
|
||||
|
||||
class Pi(ODE):
|
||||
r"""Integrate 1/(t + 1j) from t=-10 to t=10"""
|
||||
stop_t = 20
|
||||
z0 = [0]
|
||||
cmplx = True
|
||||
|
||||
def f(self, z, t):
|
||||
return array([1./(t - 10 + 1j)])
|
||||
|
||||
def verify(self, zs, t):
|
||||
u = -2j * np.arctan(10)
|
||||
return allclose(u, zs[-1, :], atol=self.atol, rtol=self.rtol)
|
||||
|
||||
|
||||
class CoupledDecay(ODE):
|
||||
r"""
|
||||
3 coupled decays suited for banded treatment
|
||||
(banded mode makes it necessary when N>>3)
|
||||
"""
|
||||
|
||||
stiff = True
|
||||
stop_t = 0.5
|
||||
z0 = [5.0, 7.0, 13.0]
|
||||
lband = 1
|
||||
uband = 0
|
||||
|
||||
lmbd = [0.17, 0.23, 0.29] # fictitious decay constants
|
||||
|
||||
def f(self, z, t):
|
||||
lmbd = self.lmbd
|
||||
return np.array([-lmbd[0]*z[0],
|
||||
-lmbd[1]*z[1] + lmbd[0]*z[0],
|
||||
-lmbd[2]*z[2] + lmbd[1]*z[1]])
|
||||
|
||||
def jac(self, z, t):
|
||||
# The full Jacobian is
|
||||
#
|
||||
# [-lmbd[0] 0 0 ]
|
||||
# [ lmbd[0] -lmbd[1] 0 ]
|
||||
# [ 0 lmbd[1] -lmbd[2]]
|
||||
#
|
||||
# The lower and upper bandwidths are lband=1 and uband=0, resp.
|
||||
# The representation of this array in packed format is
|
||||
#
|
||||
# [-lmbd[0] -lmbd[1] -lmbd[2]]
|
||||
# [ lmbd[0] lmbd[1] 0 ]
|
||||
|
||||
lmbd = self.lmbd
|
||||
j = np.zeros((self.lband + self.uband + 1, 3), order='F')
|
||||
|
||||
def set_j(ri, ci, val):
|
||||
j[self.uband + ri - ci, ci] = val
|
||||
set_j(0, 0, -lmbd[0])
|
||||
set_j(1, 0, lmbd[0])
|
||||
set_j(1, 1, -lmbd[1])
|
||||
set_j(2, 1, lmbd[1])
|
||||
set_j(2, 2, -lmbd[2])
|
||||
return j
|
||||
|
||||
def verify(self, zs, t):
|
||||
# Formulae derived by hand
|
||||
lmbd = np.array(self.lmbd)
|
||||
d10 = lmbd[1] - lmbd[0]
|
||||
d21 = lmbd[2] - lmbd[1]
|
||||
d20 = lmbd[2] - lmbd[0]
|
||||
e0 = np.exp(-lmbd[0] * t)
|
||||
e1 = np.exp(-lmbd[1] * t)
|
||||
e2 = np.exp(-lmbd[2] * t)
|
||||
u = np.vstack((
|
||||
self.z0[0] * e0,
|
||||
self.z0[1] * e1 + self.z0[0] * lmbd[0] / d10 * (e0 - e1),
|
||||
self.z0[2] * e2 + self.z0[1] * lmbd[1] / d21 * (e1 - e2) +
|
||||
lmbd[1] * lmbd[0] * self.z0[0] / d10 *
|
||||
(1 / d20 * (e0 - e2) - 1 / d21 * (e1 - e2)))).transpose()
|
||||
return allclose(u, zs, atol=self.atol, rtol=self.rtol)
|
||||
|
||||
|
||||
PROBLEMS = [SimpleOscillator, ComplexExp, Pi, CoupledDecay]
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
|
||||
def f(t, x):
|
||||
dxdt = [x[1], -x[0]]
|
||||
return dxdt
|
||||
|
||||
|
||||
def jac(t, x):
|
||||
j = array([[0.0, 1.0],
|
||||
[-1.0, 0.0]])
|
||||
return j
|
||||
|
||||
|
||||
def f1(t, x, omega):
|
||||
dxdt = [omega*x[1], -omega*x[0]]
|
||||
return dxdt
|
||||
|
||||
|
||||
def jac1(t, x, omega):
|
||||
j = array([[0.0, omega],
|
||||
[-omega, 0.0]])
|
||||
return j
|
||||
|
||||
|
||||
def f2(t, x, omega1, omega2):
|
||||
dxdt = [omega1*x[1], -omega2*x[0]]
|
||||
return dxdt
|
||||
|
||||
|
||||
def jac2(t, x, omega1, omega2):
|
||||
j = array([[0.0, omega1],
|
||||
[-omega2, 0.0]])
|
||||
return j
|
||||
|
||||
|
||||
def fv(t, x, omega):
|
||||
dxdt = [omega[0]*x[1], -omega[1]*x[0]]
|
||||
return dxdt
|
||||
|
||||
|
||||
def jacv(t, x, omega):
|
||||
j = array([[0.0, omega[0]],
|
||||
[-omega[1], 0.0]])
|
||||
return j
|
||||
|
||||
|
||||
class ODECheckParameterUse(object):
|
||||
"""Call an ode-class solver with several cases of parameter use."""
|
||||
|
||||
# solver_name must be set before tests can be run with this class.
|
||||
|
||||
# Set these in subclasses.
|
||||
solver_name = ''
|
||||
solver_uses_jac = False
|
||||
|
||||
def _get_solver(self, f, jac):
|
||||
solver = ode(f, jac)
|
||||
if self.solver_uses_jac:
|
||||
solver.set_integrator(self.solver_name, atol=1e-9, rtol=1e-7,
|
||||
with_jacobian=self.solver_uses_jac)
|
||||
else:
|
||||
# XXX Shouldn't set_integrator *always* accept the keyword arg
|
||||
# 'with_jacobian', and perhaps raise an exception if it is set
|
||||
# to True if the solver can't actually use it?
|
||||
solver.set_integrator(self.solver_name, atol=1e-9, rtol=1e-7)
|
||||
return solver
|
||||
|
||||
def _check_solver(self, solver):
|
||||
ic = [1.0, 0.0]
|
||||
solver.set_initial_value(ic, 0.0)
|
||||
solver.integrate(pi)
|
||||
assert_array_almost_equal(solver.y, [-1.0, 0.0])
|
||||
|
||||
def test_no_params(self):
|
||||
solver = self._get_solver(f, jac)
|
||||
self._check_solver(solver)
|
||||
|
||||
def test_one_scalar_param(self):
|
||||
solver = self._get_solver(f1, jac1)
|
||||
omega = 1.0
|
||||
solver.set_f_params(omega)
|
||||
if self.solver_uses_jac:
|
||||
solver.set_jac_params(omega)
|
||||
self._check_solver(solver)
|
||||
|
||||
def test_two_scalar_params(self):
|
||||
solver = self._get_solver(f2, jac2)
|
||||
omega1 = 1.0
|
||||
omega2 = 1.0
|
||||
solver.set_f_params(omega1, omega2)
|
||||
if self.solver_uses_jac:
|
||||
solver.set_jac_params(omega1, omega2)
|
||||
self._check_solver(solver)
|
||||
|
||||
def test_vector_param(self):
|
||||
solver = self._get_solver(fv, jacv)
|
||||
omega = [1.0, 1.0]
|
||||
solver.set_f_params(omega)
|
||||
if self.solver_uses_jac:
|
||||
solver.set_jac_params(omega)
|
||||
self._check_solver(solver)
|
||||
|
||||
def test_warns_on_failure(self):
|
||||
# Set nsteps small to ensure failure
|
||||
solver = self._get_solver(f, jac)
|
||||
solver.set_integrator(self.solver_name, nsteps=1)
|
||||
ic = [1.0, 0.0]
|
||||
solver.set_initial_value(ic, 0.0)
|
||||
assert_warns(UserWarning, solver.integrate, pi)
|
||||
|
||||
|
||||
class TestDOPRI5CheckParameterUse(ODECheckParameterUse):
|
||||
solver_name = 'dopri5'
|
||||
solver_uses_jac = False
|
||||
|
||||
|
||||
class TestDOP853CheckParameterUse(ODECheckParameterUse):
|
||||
solver_name = 'dop853'
|
||||
solver_uses_jac = False
|
||||
|
||||
|
||||
class TestVODECheckParameterUse(ODECheckParameterUse):
|
||||
solver_name = 'vode'
|
||||
solver_uses_jac = True
|
||||
|
||||
|
||||
class TestZVODECheckParameterUse(ODECheckParameterUse):
|
||||
solver_name = 'zvode'
|
||||
solver_uses_jac = True
|
||||
|
||||
|
||||
class TestLSODACheckParameterUse(ODECheckParameterUse):
|
||||
solver_name = 'lsoda'
|
||||
solver_uses_jac = True
|
||||
|
||||
|
||||
def test_odeint_trivial_time():
|
||||
# Test that odeint succeeds when given a single time point
|
||||
# and full_output=True. This is a regression test for gh-4282.
|
||||
y0 = 1
|
||||
t = [0]
|
||||
y, info = odeint(lambda y, t: -y, y0, t, full_output=True)
|
||||
assert_array_equal(y, np.array([[y0]]))
|
||||
|
||||
|
||||
def test_odeint_banded_jacobian():
|
||||
# Test the use of the `Dfun`, `ml` and `mu` options of odeint.
|
||||
|
||||
def func(y, t, c):
|
||||
return c.dot(y)
|
||||
|
||||
def jac(y, t, c):
|
||||
return c
|
||||
|
||||
def jac_transpose(y, t, c):
|
||||
return c.T.copy(order='C')
|
||||
|
||||
def bjac_rows(y, t, c):
|
||||
jac = np.row_stack((np.r_[0, np.diag(c, 1)],
|
||||
np.diag(c),
|
||||
np.r_[np.diag(c, -1), 0],
|
||||
np.r_[np.diag(c, -2), 0, 0]))
|
||||
return jac
|
||||
|
||||
def bjac_cols(y, t, c):
|
||||
return bjac_rows(y, t, c).T.copy(order='C')
|
||||
|
||||
c = array([[-205, 0.01, 0.00, 0.0],
|
||||
[0.1, -2.50, 0.02, 0.0],
|
||||
[1e-3, 0.01, -2.0, 0.01],
|
||||
[0.00, 0.00, 0.1, -1.0]])
|
||||
|
||||
y0 = np.ones(4)
|
||||
t = np.array([0, 5, 10, 100])
|
||||
|
||||
# Use the full Jacobian.
|
||||
sol1, info1 = odeint(func, y0, t, args=(c,), full_output=True,
|
||||
atol=1e-13, rtol=1e-11, mxstep=10000,
|
||||
Dfun=jac)
|
||||
|
||||
# Use the transposed full Jacobian, with col_deriv=True.
|
||||
sol2, info2 = odeint(func, y0, t, args=(c,), full_output=True,
|
||||
atol=1e-13, rtol=1e-11, mxstep=10000,
|
||||
Dfun=jac_transpose, col_deriv=True)
|
||||
|
||||
# Use the banded Jacobian.
|
||||
sol3, info3 = odeint(func, y0, t, args=(c,), full_output=True,
|
||||
atol=1e-13, rtol=1e-11, mxstep=10000,
|
||||
Dfun=bjac_rows, ml=2, mu=1)
|
||||
|
||||
# Use the transposed banded Jacobian, with col_deriv=True.
|
||||
sol4, info4 = odeint(func, y0, t, args=(c,), full_output=True,
|
||||
atol=1e-13, rtol=1e-11, mxstep=10000,
|
||||
Dfun=bjac_cols, ml=2, mu=1, col_deriv=True)
|
||||
|
||||
assert_allclose(sol1, sol2, err_msg="sol1 != sol2")
|
||||
assert_allclose(sol1, sol3, atol=1e-12, err_msg="sol1 != sol3")
|
||||
assert_allclose(sol3, sol4, err_msg="sol3 != sol4")
|
||||
|
||||
# Verify that the number of jacobian evaluations was the same for the
|
||||
# calls of odeint with a full jacobian and with a banded jacobian. This is
|
||||
# a regression test--there was a bug in the handling of banded jacobians
|
||||
# that resulted in an incorrect jacobian matrix being passed to the LSODA
|
||||
# code. That would cause errors or excessive jacobian evaluations.
|
||||
assert_array_equal(info1['nje'], info2['nje'])
|
||||
assert_array_equal(info3['nje'], info4['nje'])
|
||||
|
||||
# Test the use of tfirst
|
||||
sol1ty, info1ty = odeint(lambda t, y, c: func(y, t, c), y0, t, args=(c,),
|
||||
full_output=True, atol=1e-13, rtol=1e-11,
|
||||
mxstep=10000,
|
||||
Dfun=lambda t, y, c: jac(y, t, c), tfirst=True)
|
||||
# The code should execute the exact same sequence of floating point
|
||||
# calculations, so these should be exactly equal. We'll be safe and use
|
||||
# a small tolerance.
|
||||
assert_allclose(sol1, sol1ty, rtol=1e-12, err_msg="sol1 != sol1ty")
|
||||
|
||||
|
||||
def test_odeint_errors():
|
||||
def sys1d(x, t):
|
||||
return -100*x
|
||||
|
||||
def bad1(x, t):
|
||||
return 1.0/0
|
||||
|
||||
def bad2(x, t):
|
||||
return "foo"
|
||||
|
||||
def bad_jac1(x, t):
|
||||
return 1.0/0
|
||||
|
||||
def bad_jac2(x, t):
|
||||
return [["foo"]]
|
||||
|
||||
def sys2d(x, t):
|
||||
return [-100*x[0], -0.1*x[1]]
|
||||
|
||||
def sys2d_bad_jac(x, t):
|
||||
return [[1.0/0, 0], [0, -0.1]]
|
||||
|
||||
assert_raises(ZeroDivisionError, odeint, bad1, 1.0, [0, 1])
|
||||
assert_raises(ValueError, odeint, bad2, 1.0, [0, 1])
|
||||
|
||||
assert_raises(ZeroDivisionError, odeint, sys1d, 1.0, [0, 1], Dfun=bad_jac1)
|
||||
assert_raises(ValueError, odeint, sys1d, 1.0, [0, 1], Dfun=bad_jac2)
|
||||
|
||||
assert_raises(ZeroDivisionError, odeint, sys2d, [1.0, 1.0], [0, 1],
|
||||
Dfun=sys2d_bad_jac)
|
||||
|
||||
|
||||
def test_odeint_bad_shapes():
|
||||
# Tests of some errors that can occur with odeint.
|
||||
|
||||
def badrhs(x, t):
|
||||
return [1, -1]
|
||||
|
||||
def sys1(x, t):
|
||||
return -100*x
|
||||
|
||||
def badjac(x, t):
|
||||
return [[0, 0, 0]]
|
||||
|
||||
# y0 must be at most 1-d.
|
||||
bad_y0 = [[0, 0], [0, 0]]
|
||||
assert_raises(ValueError, odeint, sys1, bad_y0, [0, 1])
|
||||
|
||||
# t must be at most 1-d.
|
||||
bad_t = [[0, 1], [2, 3]]
|
||||
assert_raises(ValueError, odeint, sys1, [10.0], bad_t)
|
||||
|
||||
# y0 is 10, but badrhs(x, t) returns [1, -1].
|
||||
assert_raises(RuntimeError, odeint, badrhs, 10, [0, 1])
|
||||
|
||||
# shape of array returned by badjac(x, t) is not correct.
|
||||
assert_raises(RuntimeError, odeint, sys1, [10, 10], [0, 1], Dfun=badjac)
|
||||
|
||||
|
||||
def test_repeated_t_values():
|
||||
"""Regression test for gh-8217."""
|
||||
|
||||
def func(x, t):
|
||||
return -0.25*x
|
||||
|
||||
t = np.zeros(10)
|
||||
sol = odeint(func, [1.], t)
|
||||
assert_array_equal(sol, np.ones((len(t), 1)))
|
||||
|
||||
tau = 4*np.log(2)
|
||||
t = [0]*9 + [tau, 2*tau, 2*tau, 3*tau]
|
||||
sol = odeint(func, [1, 2], t, rtol=1e-12, atol=1e-12)
|
||||
expected_sol = np.array([[1.0, 2.0]]*9 +
|
||||
[[0.5, 1.0],
|
||||
[0.25, 0.5],
|
||||
[0.25, 0.5],
|
||||
[0.125, 0.25]])
|
||||
assert_allclose(sol, expected_sol)
|
||||
|
||||
# Edge case: empty t sequence.
|
||||
sol = odeint(func, [1.], [])
|
||||
assert_array_equal(sol, np.array([], dtype=np.float64).reshape((0, 1)))
|
||||
|
||||
# t values are not monotonic.
|
||||
assert_raises(ValueError, odeint, func, [1.], [0, 1, 0.5, 0])
|
||||
assert_raises(ValueError, odeint, func, [1, 2, 3], [0, -1, -2, 3])
|
|
@ -0,0 +1,75 @@
|
|||
|
||||
import numpy as np
|
||||
from numpy.testing import assert_equal, assert_allclose
|
||||
from scipy.integrate import odeint
|
||||
import scipy.integrate._test_odeint_banded as banded5x5
|
||||
|
||||
|
||||
def rhs(y, t):
|
||||
dydt = np.zeros_like(y)
|
||||
banded5x5.banded5x5(t, y, dydt)
|
||||
return dydt
|
||||
|
||||
|
||||
def jac(y, t):
|
||||
n = len(y)
|
||||
jac = np.zeros((n, n), order='F')
|
||||
banded5x5.banded5x5_jac(t, y, 1, 1, jac)
|
||||
return jac
|
||||
|
||||
|
||||
def bjac(y, t):
|
||||
n = len(y)
|
||||
bjac = np.zeros((4, n), order='F')
|
||||
banded5x5.banded5x5_bjac(t, y, 1, 1, bjac)
|
||||
return bjac
|
||||
|
||||
|
||||
JACTYPE_FULL = 1
|
||||
JACTYPE_BANDED = 4
|
||||
|
||||
|
||||
def check_odeint(jactype):
|
||||
if jactype == JACTYPE_FULL:
|
||||
ml = None
|
||||
mu = None
|
||||
jacobian = jac
|
||||
elif jactype == JACTYPE_BANDED:
|
||||
ml = 2
|
||||
mu = 1
|
||||
jacobian = bjac
|
||||
else:
|
||||
raise ValueError("invalid jactype: %r" % (jactype,))
|
||||
|
||||
y0 = np.arange(1.0, 6.0)
|
||||
# These tolerances must match the tolerances used in banded5x5.f.
|
||||
rtol = 1e-11
|
||||
atol = 1e-13
|
||||
dt = 0.125
|
||||
nsteps = 64
|
||||
t = dt * np.arange(nsteps+1)
|
||||
|
||||
sol, info = odeint(rhs, y0, t,
|
||||
Dfun=jacobian, ml=ml, mu=mu,
|
||||
atol=atol, rtol=rtol, full_output=True)
|
||||
yfinal = sol[-1]
|
||||
odeint_nst = info['nst'][-1]
|
||||
odeint_nfe = info['nfe'][-1]
|
||||
odeint_nje = info['nje'][-1]
|
||||
|
||||
y1 = y0.copy()
|
||||
# Pure Fortran solution. y1 is modified in-place.
|
||||
nst, nfe, nje = banded5x5.banded5x5_solve(y1, nsteps, dt, jactype)
|
||||
|
||||
# It is likely that yfinal and y1 are *exactly* the same, but
|
||||
# we'll be cautious and use assert_allclose.
|
||||
assert_allclose(yfinal, y1, rtol=1e-12)
|
||||
assert_equal((odeint_nst, odeint_nfe, odeint_nje), (nst, nfe, nje))
|
||||
|
||||
|
||||
def test_odeint_full_jac():
|
||||
check_odeint(JACTYPE_FULL)
|
||||
|
||||
|
||||
def test_odeint_banded_jac():
|
||||
check_odeint(JACTYPE_BANDED)
|
411
venv/Lib/site-packages/scipy/integrate/tests/test_quadpack.py
Normal file
411
venv/Lib/site-packages/scipy/integrate/tests/test_quadpack.py
Normal file
|
@ -0,0 +1,411 @@
|
|||
import sys
|
||||
import math
|
||||
import numpy as np
|
||||
from numpy import sqrt, cos, sin, arctan, exp, log, pi, Inf
|
||||
from numpy.testing import (assert_,
|
||||
assert_allclose, assert_array_less, assert_almost_equal)
|
||||
import pytest
|
||||
|
||||
from scipy.integrate import quad, dblquad, tplquad, nquad
|
||||
from scipy._lib._ccallback import LowLevelCallable
|
||||
|
||||
import ctypes
|
||||
import ctypes.util
|
||||
from scipy._lib._ccallback_c import sine_ctypes
|
||||
|
||||
import scipy.integrate._test_multivariate as clib_test
|
||||
|
||||
|
||||
def assert_quad(value_and_err, tabled_value, errTol=1.5e-8):
|
||||
value, err = value_and_err
|
||||
assert_allclose(value, tabled_value, atol=err, rtol=0)
|
||||
if errTol is not None:
|
||||
assert_array_less(err, errTol)
|
||||
|
||||
|
||||
def get_clib_test_routine(name, restype, *argtypes):
|
||||
ptr = getattr(clib_test, name)
|
||||
return ctypes.cast(ptr, ctypes.CFUNCTYPE(restype, *argtypes))
|
||||
|
||||
|
||||
class TestCtypesQuad(object):
|
||||
def setup_method(self):
|
||||
if sys.platform == 'win32':
|
||||
files = ['api-ms-win-crt-math-l1-1-0.dll']
|
||||
elif sys.platform == 'darwin':
|
||||
files = ['libm.dylib']
|
||||
else:
|
||||
files = ['libm.so', 'libm.so.6']
|
||||
|
||||
for file in files:
|
||||
try:
|
||||
self.lib = ctypes.CDLL(file)
|
||||
break
|
||||
except OSError:
|
||||
pass
|
||||
else:
|
||||
# This test doesn't work on some Linux platforms (Fedora for
|
||||
# example) that put an ld script in libm.so - see gh-5370
|
||||
pytest.skip("Ctypes can't import libm.so")
|
||||
|
||||
restype = ctypes.c_double
|
||||
argtypes = (ctypes.c_double,)
|
||||
for name in ['sin', 'cos', 'tan']:
|
||||
func = getattr(self.lib, name)
|
||||
func.restype = restype
|
||||
func.argtypes = argtypes
|
||||
|
||||
def test_typical(self):
|
||||
assert_quad(quad(self.lib.sin, 0, 5), quad(math.sin, 0, 5)[0])
|
||||
assert_quad(quad(self.lib.cos, 0, 5), quad(math.cos, 0, 5)[0])
|
||||
assert_quad(quad(self.lib.tan, 0, 1), quad(math.tan, 0, 1)[0])
|
||||
|
||||
def test_ctypes_sine(self):
|
||||
quad(LowLevelCallable(sine_ctypes), 0, 1)
|
||||
|
||||
def test_ctypes_variants(self):
|
||||
sin_0 = get_clib_test_routine('_sin_0', ctypes.c_double,
|
||||
ctypes.c_double, ctypes.c_void_p)
|
||||
|
||||
sin_1 = get_clib_test_routine('_sin_1', ctypes.c_double,
|
||||
ctypes.c_int, ctypes.POINTER(ctypes.c_double),
|
||||
ctypes.c_void_p)
|
||||
|
||||
sin_2 = get_clib_test_routine('_sin_2', ctypes.c_double,
|
||||
ctypes.c_double)
|
||||
|
||||
sin_3 = get_clib_test_routine('_sin_3', ctypes.c_double,
|
||||
ctypes.c_int, ctypes.POINTER(ctypes.c_double))
|
||||
|
||||
sin_4 = get_clib_test_routine('_sin_3', ctypes.c_double,
|
||||
ctypes.c_int, ctypes.c_double)
|
||||
|
||||
all_sigs = [sin_0, sin_1, sin_2, sin_3, sin_4]
|
||||
legacy_sigs = [sin_2, sin_4]
|
||||
legacy_only_sigs = [sin_4]
|
||||
|
||||
# LowLevelCallables work for new signatures
|
||||
for j, func in enumerate(all_sigs):
|
||||
callback = LowLevelCallable(func)
|
||||
if func in legacy_only_sigs:
|
||||
pytest.raises(ValueError, quad, callback, 0, pi)
|
||||
else:
|
||||
assert_allclose(quad(callback, 0, pi)[0], 2.0)
|
||||
|
||||
# Plain ctypes items work only for legacy signatures
|
||||
for j, func in enumerate(legacy_sigs):
|
||||
if func in legacy_sigs:
|
||||
assert_allclose(quad(func, 0, pi)[0], 2.0)
|
||||
else:
|
||||
pytest.raises(ValueError, quad, func, 0, pi)
|
||||
|
||||
|
||||
class TestMultivariateCtypesQuad(object):
|
||||
def setup_method(self):
|
||||
restype = ctypes.c_double
|
||||
argtypes = (ctypes.c_int, ctypes.c_double)
|
||||
for name in ['_multivariate_typical', '_multivariate_indefinite',
|
||||
'_multivariate_sin']:
|
||||
func = get_clib_test_routine(name, restype, *argtypes)
|
||||
setattr(self, name, func)
|
||||
|
||||
def test_typical(self):
|
||||
# 1) Typical function with two extra arguments:
|
||||
assert_quad(quad(self._multivariate_typical, 0, pi, (2, 1.8)),
|
||||
0.30614353532540296487)
|
||||
|
||||
def test_indefinite(self):
|
||||
# 2) Infinite integration limits --- Euler's constant
|
||||
assert_quad(quad(self._multivariate_indefinite, 0, Inf),
|
||||
0.577215664901532860606512)
|
||||
|
||||
def test_threadsafety(self):
|
||||
# Ensure multivariate ctypes are threadsafe
|
||||
def threadsafety(y):
|
||||
return y + quad(self._multivariate_sin, 0, 1)[0]
|
||||
assert_quad(quad(threadsafety, 0, 1), 0.9596976941318602)
|
||||
|
||||
|
||||
class TestQuad(object):
|
||||
def test_typical(self):
|
||||
# 1) Typical function with two extra arguments:
|
||||
def myfunc(x, n, z): # Bessel function integrand
|
||||
return cos(n*x-z*sin(x))/pi
|
||||
assert_quad(quad(myfunc, 0, pi, (2, 1.8)), 0.30614353532540296487)
|
||||
|
||||
def test_indefinite(self):
|
||||
# 2) Infinite integration limits --- Euler's constant
|
||||
def myfunc(x): # Euler's constant integrand
|
||||
return -exp(-x)*log(x)
|
||||
assert_quad(quad(myfunc, 0, Inf), 0.577215664901532860606512)
|
||||
|
||||
def test_singular(self):
|
||||
# 3) Singular points in region of integration.
|
||||
def myfunc(x):
|
||||
if 0 < x < 2.5:
|
||||
return sin(x)
|
||||
elif 2.5 <= x <= 5.0:
|
||||
return exp(-x)
|
||||
else:
|
||||
return 0.0
|
||||
|
||||
assert_quad(quad(myfunc, 0, 10, points=[2.5, 5.0]),
|
||||
1 - cos(2.5) + exp(-2.5) - exp(-5.0))
|
||||
|
||||
def test_sine_weighted_finite(self):
|
||||
# 4) Sine weighted integral (finite limits)
|
||||
def myfunc(x, a):
|
||||
return exp(a*(x-1))
|
||||
|
||||
ome = 2.0**3.4
|
||||
assert_quad(quad(myfunc, 0, 1, args=20, weight='sin', wvar=ome),
|
||||
(20*sin(ome)-ome*cos(ome)+ome*exp(-20))/(20**2 + ome**2))
|
||||
|
||||
def test_sine_weighted_infinite(self):
|
||||
# 5) Sine weighted integral (infinite limits)
|
||||
def myfunc(x, a):
|
||||
return exp(-x*a)
|
||||
|
||||
a = 4.0
|
||||
ome = 3.0
|
||||
assert_quad(quad(myfunc, 0, Inf, args=a, weight='sin', wvar=ome),
|
||||
ome/(a**2 + ome**2))
|
||||
|
||||
def test_cosine_weighted_infinite(self):
|
||||
# 6) Cosine weighted integral (negative infinite limits)
|
||||
def myfunc(x, a):
|
||||
return exp(x*a)
|
||||
|
||||
a = 2.5
|
||||
ome = 2.3
|
||||
assert_quad(quad(myfunc, -Inf, 0, args=a, weight='cos', wvar=ome),
|
||||
a/(a**2 + ome**2))
|
||||
|
||||
def test_algebraic_log_weight(self):
|
||||
# 6) Algebraic-logarithmic weight.
|
||||
def myfunc(x, a):
|
||||
return 1/(1+x+2**(-a))
|
||||
|
||||
a = 1.5
|
||||
assert_quad(quad(myfunc, -1, 1, args=a, weight='alg',
|
||||
wvar=(-0.5, -0.5)),
|
||||
pi/sqrt((1+2**(-a))**2 - 1))
|
||||
|
||||
def test_cauchypv_weight(self):
|
||||
# 7) Cauchy prinicpal value weighting w(x) = 1/(x-c)
|
||||
def myfunc(x, a):
|
||||
return 2.0**(-a)/((x-1)**2+4.0**(-a))
|
||||
|
||||
a = 0.4
|
||||
tabledValue = ((2.0**(-0.4)*log(1.5) -
|
||||
2.0**(-1.4)*log((4.0**(-a)+16) / (4.0**(-a)+1)) -
|
||||
arctan(2.0**(a+2)) -
|
||||
arctan(2.0**a)) /
|
||||
(4.0**(-a) + 1))
|
||||
assert_quad(quad(myfunc, 0, 5, args=0.4, weight='cauchy', wvar=2.0),
|
||||
tabledValue, errTol=1.9e-8)
|
||||
|
||||
def test_b_less_than_a(self):
|
||||
def f(x, p, q):
|
||||
return p * np.exp(-q*x)
|
||||
|
||||
val_1, err_1 = quad(f, 0, np.inf, args=(2, 3))
|
||||
val_2, err_2 = quad(f, np.inf, 0, args=(2, 3))
|
||||
assert_allclose(val_1, -val_2, atol=max(err_1, err_2))
|
||||
|
||||
def test_b_less_than_a_2(self):
|
||||
def f(x, s):
|
||||
return np.exp(-x**2 / 2 / s) / np.sqrt(2.*s)
|
||||
|
||||
val_1, err_1 = quad(f, -np.inf, np.inf, args=(2,))
|
||||
val_2, err_2 = quad(f, np.inf, -np.inf, args=(2,))
|
||||
assert_allclose(val_1, -val_2, atol=max(err_1, err_2))
|
||||
|
||||
def test_b_less_than_a_3(self):
|
||||
def f(x):
|
||||
return 1.0
|
||||
|
||||
val_1, err_1 = quad(f, 0, 1, weight='alg', wvar=(0, 0))
|
||||
val_2, err_2 = quad(f, 1, 0, weight='alg', wvar=(0, 0))
|
||||
assert_allclose(val_1, -val_2, atol=max(err_1, err_2))
|
||||
|
||||
def test_b_less_than_a_full_output(self):
|
||||
def f(x):
|
||||
return 1.0
|
||||
|
||||
res_1 = quad(f, 0, 1, weight='alg', wvar=(0, 0), full_output=True)
|
||||
res_2 = quad(f, 1, 0, weight='alg', wvar=(0, 0), full_output=True)
|
||||
err = max(res_1[1], res_2[1])
|
||||
assert_allclose(res_1[0], -res_2[0], atol=err)
|
||||
|
||||
def test_double_integral(self):
|
||||
# 8) Double Integral test
|
||||
def simpfunc(y, x): # Note order of arguments.
|
||||
return x+y
|
||||
|
||||
a, b = 1.0, 2.0
|
||||
assert_quad(dblquad(simpfunc, a, b, lambda x: x, lambda x: 2*x),
|
||||
5/6.0 * (b**3.0-a**3.0))
|
||||
|
||||
def test_double_integral2(self):
|
||||
def func(x0, x1, t0, t1):
|
||||
return x0 + x1 + t0 + t1
|
||||
g = lambda x: x
|
||||
h = lambda x: 2 * x
|
||||
args = 1, 2
|
||||
assert_quad(dblquad(func, 1, 2, g, h, args=args),35./6 + 9*.5)
|
||||
|
||||
def test_double_integral3(self):
|
||||
def func(x0, x1):
|
||||
return x0 + x1 + 1 + 2
|
||||
assert_quad(dblquad(func, 1, 2, 1, 2),6.)
|
||||
|
||||
def test_triple_integral(self):
|
||||
# 9) Triple Integral test
|
||||
def simpfunc(z, y, x, t): # Note order of arguments.
|
||||
return (x+y+z)*t
|
||||
|
||||
a, b = 1.0, 2.0
|
||||
assert_quad(tplquad(simpfunc, a, b,
|
||||
lambda x: x, lambda x: 2*x,
|
||||
lambda x, y: x - y, lambda x, y: x + y,
|
||||
(2.,)),
|
||||
2*8/3.0 * (b**4.0 - a**4.0))
|
||||
|
||||
|
||||
class TestNQuad(object):
|
||||
def test_fixed_limits(self):
|
||||
def func1(x0, x1, x2, x3):
|
||||
val = (x0**2 + x1*x2 - x3**3 + np.sin(x0) +
|
||||
(1 if (x0 - 0.2*x3 - 0.5 - 0.25*x1 > 0) else 0))
|
||||
return val
|
||||
|
||||
def opts_basic(*args):
|
||||
return {'points': [0.2*args[2] + 0.5 + 0.25*args[0]]}
|
||||
|
||||
res = nquad(func1, [[0, 1], [-1, 1], [.13, .8], [-.15, 1]],
|
||||
opts=[opts_basic, {}, {}, {}], full_output=True)
|
||||
assert_quad(res[:-1], 1.5267454070738635)
|
||||
assert_(res[-1]['neval'] > 0 and res[-1]['neval'] < 4e5)
|
||||
|
||||
def test_variable_limits(self):
|
||||
scale = .1
|
||||
|
||||
def func2(x0, x1, x2, x3, t0, t1):
|
||||
val = (x0*x1*x3**2 + np.sin(x2) + 1 +
|
||||
(1 if x0 + t1*x1 - t0 > 0 else 0))
|
||||
return val
|
||||
|
||||
def lim0(x1, x2, x3, t0, t1):
|
||||
return [scale * (x1**2 + x2 + np.cos(x3)*t0*t1 + 1) - 1,
|
||||
scale * (x1**2 + x2 + np.cos(x3)*t0*t1 + 1) + 1]
|
||||
|
||||
def lim1(x2, x3, t0, t1):
|
||||
return [scale * (t0*x2 + t1*x3) - 1,
|
||||
scale * (t0*x2 + t1*x3) + 1]
|
||||
|
||||
def lim2(x3, t0, t1):
|
||||
return [scale * (x3 + t0**2*t1**3) - 1,
|
||||
scale * (x3 + t0**2*t1**3) + 1]
|
||||
|
||||
def lim3(t0, t1):
|
||||
return [scale * (t0 + t1) - 1, scale * (t0 + t1) + 1]
|
||||
|
||||
def opts0(x1, x2, x3, t0, t1):
|
||||
return {'points': [t0 - t1*x1]}
|
||||
|
||||
def opts1(x2, x3, t0, t1):
|
||||
return {}
|
||||
|
||||
def opts2(x3, t0, t1):
|
||||
return {}
|
||||
|
||||
def opts3(t0, t1):
|
||||
return {}
|
||||
|
||||
res = nquad(func2, [lim0, lim1, lim2, lim3], args=(0, 0),
|
||||
opts=[opts0, opts1, opts2, opts3])
|
||||
assert_quad(res, 25.066666666666663)
|
||||
|
||||
def test_square_separate_ranges_and_opts(self):
|
||||
def f(y, x):
|
||||
return 1.0
|
||||
|
||||
assert_quad(nquad(f, [[-1, 1], [-1, 1]], opts=[{}, {}]), 4.0)
|
||||
|
||||
def test_square_aliased_ranges_and_opts(self):
|
||||
def f(y, x):
|
||||
return 1.0
|
||||
|
||||
r = [-1, 1]
|
||||
opt = {}
|
||||
assert_quad(nquad(f, [r, r], opts=[opt, opt]), 4.0)
|
||||
|
||||
def test_square_separate_fn_ranges_and_opts(self):
|
||||
def f(y, x):
|
||||
return 1.0
|
||||
|
||||
def fn_range0(*args):
|
||||
return (-1, 1)
|
||||
|
||||
def fn_range1(*args):
|
||||
return (-1, 1)
|
||||
|
||||
def fn_opt0(*args):
|
||||
return {}
|
||||
|
||||
def fn_opt1(*args):
|
||||
return {}
|
||||
|
||||
ranges = [fn_range0, fn_range1]
|
||||
opts = [fn_opt0, fn_opt1]
|
||||
assert_quad(nquad(f, ranges, opts=opts), 4.0)
|
||||
|
||||
def test_square_aliased_fn_ranges_and_opts(self):
|
||||
def f(y, x):
|
||||
return 1.0
|
||||
|
||||
def fn_range(*args):
|
||||
return (-1, 1)
|
||||
|
||||
def fn_opt(*args):
|
||||
return {}
|
||||
|
||||
ranges = [fn_range, fn_range]
|
||||
opts = [fn_opt, fn_opt]
|
||||
assert_quad(nquad(f, ranges, opts=opts), 4.0)
|
||||
|
||||
def test_matching_quad(self):
|
||||
def func(x):
|
||||
return x**2 + 1
|
||||
|
||||
res, reserr = quad(func, 0, 4)
|
||||
res2, reserr2 = nquad(func, ranges=[[0, 4]])
|
||||
assert_almost_equal(res, res2)
|
||||
assert_almost_equal(reserr, reserr2)
|
||||
|
||||
def test_matching_dblquad(self):
|
||||
def func2d(x0, x1):
|
||||
return x0**2 + x1**3 - x0 * x1 + 1
|
||||
|
||||
res, reserr = dblquad(func2d, -2, 2, lambda x: -3, lambda x: 3)
|
||||
res2, reserr2 = nquad(func2d, [[-3, 3], (-2, 2)])
|
||||
assert_almost_equal(res, res2)
|
||||
assert_almost_equal(reserr, reserr2)
|
||||
|
||||
def test_matching_tplquad(self):
|
||||
def func3d(x0, x1, x2, c0, c1):
|
||||
return x0**2 + c0 * x1**3 - x0 * x1 + 1 + c1 * np.sin(x2)
|
||||
|
||||
res = tplquad(func3d, -1, 2, lambda x: -2, lambda x: 2,
|
||||
lambda x, y: -np.pi, lambda x, y: np.pi,
|
||||
args=(2, 3))
|
||||
res2 = nquad(func3d, [[-np.pi, np.pi], [-2, 2], (-1, 2)], args=(2, 3))
|
||||
assert_almost_equal(res, res2)
|
||||
|
||||
def test_dict_as_opts(self):
|
||||
try:
|
||||
nquad(lambda x, y: x * y, [[0, 1], [0, 1]], opts={'epsrel': 0.0001})
|
||||
except(TypeError):
|
||||
assert False
|
||||
|
229
venv/Lib/site-packages/scipy/integrate/tests/test_quadrature.py
Normal file
229
venv/Lib/site-packages/scipy/integrate/tests/test_quadrature.py
Normal file
|
@ -0,0 +1,229 @@
|
|||
import numpy as np
|
||||
from numpy import cos, sin, pi
|
||||
from numpy.testing import (assert_equal, assert_almost_equal, assert_allclose,
|
||||
assert_, suppress_warnings)
|
||||
|
||||
from scipy.integrate import (quadrature, romberg, romb, newton_cotes,
|
||||
cumtrapz, quad, simps, fixed_quad,
|
||||
AccuracyWarning)
|
||||
|
||||
|
||||
class TestFixedQuad(object):
|
||||
def test_scalar(self):
|
||||
n = 4
|
||||
func = lambda x: x**(2*n - 1)
|
||||
expected = 1/(2*n)
|
||||
got, _ = fixed_quad(func, 0, 1, n=n)
|
||||
# quadrature exact for this input
|
||||
assert_allclose(got, expected, rtol=1e-12)
|
||||
|
||||
def test_vector(self):
|
||||
n = 4
|
||||
p = np.arange(1, 2*n)
|
||||
func = lambda x: x**p[:,None]
|
||||
expected = 1/(p + 1)
|
||||
got, _ = fixed_quad(func, 0, 1, n=n)
|
||||
assert_allclose(got, expected, rtol=1e-12)
|
||||
|
||||
|
||||
class TestQuadrature(object):
|
||||
def quad(self, x, a, b, args):
|
||||
raise NotImplementedError
|
||||
|
||||
def test_quadrature(self):
|
||||
# Typical function with two extra arguments:
|
||||
def myfunc(x, n, z): # Bessel function integrand
|
||||
return cos(n*x-z*sin(x))/pi
|
||||
val, err = quadrature(myfunc, 0, pi, (2, 1.8))
|
||||
table_val = 0.30614353532540296487
|
||||
assert_almost_equal(val, table_val, decimal=7)
|
||||
|
||||
def test_quadrature_rtol(self):
|
||||
def myfunc(x, n, z): # Bessel function integrand
|
||||
return 1e90 * cos(n*x-z*sin(x))/pi
|
||||
val, err = quadrature(myfunc, 0, pi, (2, 1.8), rtol=1e-10)
|
||||
table_val = 1e90 * 0.30614353532540296487
|
||||
assert_allclose(val, table_val, rtol=1e-10)
|
||||
|
||||
def test_quadrature_miniter(self):
|
||||
# Typical function with two extra arguments:
|
||||
def myfunc(x, n, z): # Bessel function integrand
|
||||
return cos(n*x-z*sin(x))/pi
|
||||
table_val = 0.30614353532540296487
|
||||
for miniter in [5, 52]:
|
||||
val, err = quadrature(myfunc, 0, pi, (2, 1.8), miniter=miniter)
|
||||
assert_almost_equal(val, table_val, decimal=7)
|
||||
assert_(err < 1.0)
|
||||
|
||||
def test_quadrature_single_args(self):
|
||||
def myfunc(x, n):
|
||||
return 1e90 * cos(n*x-1.8*sin(x))/pi
|
||||
val, err = quadrature(myfunc, 0, pi, args=2, rtol=1e-10)
|
||||
table_val = 1e90 * 0.30614353532540296487
|
||||
assert_allclose(val, table_val, rtol=1e-10)
|
||||
|
||||
def test_romberg(self):
|
||||
# Typical function with two extra arguments:
|
||||
def myfunc(x, n, z): # Bessel function integrand
|
||||
return cos(n*x-z*sin(x))/pi
|
||||
val = romberg(myfunc, 0, pi, args=(2, 1.8))
|
||||
table_val = 0.30614353532540296487
|
||||
assert_almost_equal(val, table_val, decimal=7)
|
||||
|
||||
def test_romberg_rtol(self):
|
||||
# Typical function with two extra arguments:
|
||||
def myfunc(x, n, z): # Bessel function integrand
|
||||
return 1e19*cos(n*x-z*sin(x))/pi
|
||||
val = romberg(myfunc, 0, pi, args=(2, 1.8), rtol=1e-10)
|
||||
table_val = 1e19*0.30614353532540296487
|
||||
assert_allclose(val, table_val, rtol=1e-10)
|
||||
|
||||
def test_romb(self):
|
||||
assert_equal(romb(np.arange(17)), 128)
|
||||
|
||||
def test_romb_gh_3731(self):
|
||||
# Check that romb makes maximal use of data points
|
||||
x = np.arange(2**4+1)
|
||||
y = np.cos(0.2*x)
|
||||
val = romb(y)
|
||||
val2, err = quad(lambda x: np.cos(0.2*x), x.min(), x.max())
|
||||
assert_allclose(val, val2, rtol=1e-8, atol=0)
|
||||
|
||||
# should be equal to romb with 2**k+1 samples
|
||||
with suppress_warnings() as sup:
|
||||
sup.filter(AccuracyWarning, "divmax .4. exceeded")
|
||||
val3 = romberg(lambda x: np.cos(0.2*x), x.min(), x.max(), divmax=4)
|
||||
assert_allclose(val, val3, rtol=1e-12, atol=0)
|
||||
|
||||
def test_non_dtype(self):
|
||||
# Check that we work fine with functions returning float
|
||||
import math
|
||||
valmath = romberg(math.sin, 0, 1)
|
||||
expected_val = 0.45969769413185085
|
||||
assert_almost_equal(valmath, expected_val, decimal=7)
|
||||
|
||||
def test_newton_cotes(self):
|
||||
"""Test the first few degrees, for evenly spaced points."""
|
||||
n = 1
|
||||
wts, errcoff = newton_cotes(n, 1)
|
||||
assert_equal(wts, n*np.array([0.5, 0.5]))
|
||||
assert_almost_equal(errcoff, -n**3/12.0)
|
||||
|
||||
n = 2
|
||||
wts, errcoff = newton_cotes(n, 1)
|
||||
assert_almost_equal(wts, n*np.array([1.0, 4.0, 1.0])/6.0)
|
||||
assert_almost_equal(errcoff, -n**5/2880.0)
|
||||
|
||||
n = 3
|
||||
wts, errcoff = newton_cotes(n, 1)
|
||||
assert_almost_equal(wts, n*np.array([1.0, 3.0, 3.0, 1.0])/8.0)
|
||||
assert_almost_equal(errcoff, -n**5/6480.0)
|
||||
|
||||
n = 4
|
||||
wts, errcoff = newton_cotes(n, 1)
|
||||
assert_almost_equal(wts, n*np.array([7.0, 32.0, 12.0, 32.0, 7.0])/90.0)
|
||||
assert_almost_equal(errcoff, -n**7/1935360.0)
|
||||
|
||||
def test_newton_cotes2(self):
|
||||
"""Test newton_cotes with points that are not evenly spaced."""
|
||||
|
||||
x = np.array([0.0, 1.5, 2.0])
|
||||
y = x**2
|
||||
wts, errcoff = newton_cotes(x)
|
||||
exact_integral = 8.0/3
|
||||
numeric_integral = np.dot(wts, y)
|
||||
assert_almost_equal(numeric_integral, exact_integral)
|
||||
|
||||
x = np.array([0.0, 1.4, 2.1, 3.0])
|
||||
y = x**2
|
||||
wts, errcoff = newton_cotes(x)
|
||||
exact_integral = 9.0
|
||||
numeric_integral = np.dot(wts, y)
|
||||
assert_almost_equal(numeric_integral, exact_integral)
|
||||
|
||||
def test_simps(self):
|
||||
y = np.arange(17)
|
||||
assert_equal(simps(y), 128)
|
||||
assert_equal(simps(y, dx=0.5), 64)
|
||||
assert_equal(simps(y, x=np.linspace(0, 4, 17)), 32)
|
||||
|
||||
y = np.arange(4)
|
||||
x = 2**y
|
||||
assert_equal(simps(y, x=x, even='avg'), 13.875)
|
||||
assert_equal(simps(y, x=x, even='first'), 13.75)
|
||||
assert_equal(simps(y, x=x, even='last'), 14)
|
||||
|
||||
|
||||
class TestCumtrapz(object):
|
||||
def test_1d(self):
|
||||
x = np.linspace(-2, 2, num=5)
|
||||
y = x
|
||||
y_int = cumtrapz(y, x, initial=0)
|
||||
y_expected = [0., -1.5, -2., -1.5, 0.]
|
||||
assert_allclose(y_int, y_expected)
|
||||
|
||||
y_int = cumtrapz(y, x, initial=None)
|
||||
assert_allclose(y_int, y_expected[1:])
|
||||
|
||||
def test_y_nd_x_nd(self):
|
||||
x = np.arange(3 * 2 * 4).reshape(3, 2, 4)
|
||||
y = x
|
||||
y_int = cumtrapz(y, x, initial=0)
|
||||
y_expected = np.array([[[0., 0.5, 2., 4.5],
|
||||
[0., 4.5, 10., 16.5]],
|
||||
[[0., 8.5, 18., 28.5],
|
||||
[0., 12.5, 26., 40.5]],
|
||||
[[0., 16.5, 34., 52.5],
|
||||
[0., 20.5, 42., 64.5]]])
|
||||
|
||||
assert_allclose(y_int, y_expected)
|
||||
|
||||
# Try with all axes
|
||||
shapes = [(2, 2, 4), (3, 1, 4), (3, 2, 3)]
|
||||
for axis, shape in zip([0, 1, 2], shapes):
|
||||
y_int = cumtrapz(y, x, initial=3.45, axis=axis)
|
||||
assert_equal(y_int.shape, (3, 2, 4))
|
||||
y_int = cumtrapz(y, x, initial=None, axis=axis)
|
||||
assert_equal(y_int.shape, shape)
|
||||
|
||||
def test_y_nd_x_1d(self):
|
||||
y = np.arange(3 * 2 * 4).reshape(3, 2, 4)
|
||||
x = np.arange(4)**2
|
||||
# Try with all axes
|
||||
ys_expected = (
|
||||
np.array([[[4., 5., 6., 7.],
|
||||
[8., 9., 10., 11.]],
|
||||
[[40., 44., 48., 52.],
|
||||
[56., 60., 64., 68.]]]),
|
||||
np.array([[[2., 3., 4., 5.]],
|
||||
[[10., 11., 12., 13.]],
|
||||
[[18., 19., 20., 21.]]]),
|
||||
np.array([[[0.5, 5., 17.5],
|
||||
[4.5, 21., 53.5]],
|
||||
[[8.5, 37., 89.5],
|
||||
[12.5, 53., 125.5]],
|
||||
[[16.5, 69., 161.5],
|
||||
[20.5, 85., 197.5]]]))
|
||||
|
||||
for axis, y_expected in zip([0, 1, 2], ys_expected):
|
||||
y_int = cumtrapz(y, x=x[:y.shape[axis]], axis=axis, initial=None)
|
||||
assert_allclose(y_int, y_expected)
|
||||
|
||||
def test_x_none(self):
|
||||
y = np.linspace(-2, 2, num=5)
|
||||
|
||||
y_int = cumtrapz(y)
|
||||
y_expected = [-1.5, -2., -1.5, 0.]
|
||||
assert_allclose(y_int, y_expected)
|
||||
|
||||
y_int = cumtrapz(y, initial=1.23)
|
||||
y_expected = [1.23, -1.5, -2., -1.5, 0.]
|
||||
assert_allclose(y_int, y_expected)
|
||||
|
||||
y_int = cumtrapz(y, dx=3)
|
||||
y_expected = [-4.5, -6., -4.5, 0.]
|
||||
assert_allclose(y_int, y_expected)
|
||||
|
||||
y_int = cumtrapz(y, dx=3, initial=1.23)
|
||||
y_expected = [1.23, -4.5, -6., -4.5, 0.]
|
||||
assert_allclose(y_int, y_expected)
|
BIN
venv/Lib/site-packages/scipy/integrate/vode.cp36-win32.pyd
Normal file
BIN
venv/Lib/site-packages/scipy/integrate/vode.cp36-win32.pyd
Normal file
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue