```python
%matplotlib inline
```

```python
import numpy as np
import matplotlib.pyplot as plt
```

# SciPy

SciPy is a collection of numerical algorithms with python interfaces.  In many cases, these interfaces are wrappers around standard numerical libraries that have been developed in the community and are used with other languages.  Usually detailed references are available to explain the implementation.

There are many subpackages generally, you load the subpackages separately, e.g.

```
from scipy import linalg, optimize
```
then you have access to the methods in those namespaces

# Numerical Methods

One thing to keep in mind -- all numerical methods have strengths and weaknesses, and make assumptions.  You should always do some research into the method to understand what it is doing.

It is also always a good idea to run a new method on some test where you know the answer, to make sure it is behaving as expected.

# Integration

we'll do some integrals of the form

$$I = \int_a^b f(x) dx$$

We can imagine two situations:
* our function $f(x)$ is given by an analytic expression.  This gives us the freedom to pick our integration points, and in general can allow us to optimize our result and get high accuracy
* our function $f(x)$ is defined on at a set of (possibly regular spaced) points.

In numerical analysis, the term _quadrature_ is used to describe any integration method that represents the integral as the weighted sum of a discrete number of points.

```python
from scipy import integrate
help(integrate)
```

    Help on package scipy.integrate in scipy:

    NAME
        scipy.integrate

    DESCRIPTION
        =============================================
        Integration and ODEs (:mod:`scipy.integrate`)
        =============================================

        .. currentmodule:: scipy.integrate

        Integrating functions, given function object
        ============================================

        .. autosummary::
           :toctree: generated/

           quad          -- General purpose integration
           dblquad       -- General purpose double integration
           tplquad       -- General purpose triple integration
           nquad         -- General purpose n-dimensional 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

        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).
           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.

    PACKAGE CONTENTS
        _bvp
        _dop
        _ivp (package)
        _ode
        _odepack
        _quadpack
        _test_multivariate
        _test_odeint_banded
        lsoda
        odepack
        quadpack
        quadrature
        setup
        tests (package)
        vode

    CLASSES
        builtins.UserWarning(builtins.Warning)
            scipy.integrate.quadpack.IntegrationWarning
        builtins.object
            scipy.integrate._ivp.base.DenseOutput
            scipy.integrate._ivp.base.OdeSolver
                scipy.integrate._ivp.bdf.BDF
                scipy.integrate._ivp.lsoda.LSODA
                scipy.integrate._ivp.radau.Radau
            scipy.integrate._ivp.common.OdeSolution
            scipy.integrate._ode.ode
                scipy.integrate._ode.complex_ode
        scipy.integrate._ivp.rk.RungeKutta(scipy.integrate._ivp.base.OdeSolver)
            scipy.integrate._ivp.rk.RK23
            scipy.integrate._ivp.rk.RK45

        class BDF(scipy.integrate._ivp.base.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 approach 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 a 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). The
         |      vectorized implementation allows faster approximation of the Jacobian
         |      by finite differences.
         |  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.
         |  max_step : float, optional
         |      Maximum allowed step size. Default is np.inf, i.e. the step 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` then 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 only by 'Radau' and 'BDF' methods. The Jacobian matrix
         |      has shape (n, n) and its element (i, j) is equal to ``d f_i / d y_j``.
         |      There are 3 ways to define the Jacobian:
         |
         |          * If array_like or sparse_matrix, then the Jacobian is assumed to
         |            be constant.
         |          * If callable, then the Jacobian is assumed to depend on both
         |            t and y, and will be called as ``jac(t, y)`` as necessary. The
         |            return value might be a sparse matrix.
         |          * If None (default), then 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). 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 identically
         |      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 the system's rhs evaluations.
         |  njev : int
         |      Number of the Jacobian evaluations.
         |  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.
         |
         |  Method resolution order:
         |      BDF
         |      scipy.integrate._ivp.base.OdeSolver
         |      builtins.object
         |
         |  Methods defined here:
         |
         |  __init__(self, fun, t0, y0, t_bound, max_step=inf, rtol=0.001, atol=1e-06, jac=None, jac_sparsity=None, vectorized=False, **extraneous)
         |      Initialize self.  See help(type(self)) for accurate signature.
         |
         |  ----------------------------------------------------------------------
         |  Methods inherited from scipy.integrate._ivp.base.OdeSolver:
         |
         |  dense_output(self)
         |      Compute a local interpolant over the last successful step.
         |
         |      Returns
         |      -------
         |      sol : `DenseOutput`
         |          Local interpolant over the last successful step.
         |
         |  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.
         |
         |  ----------------------------------------------------------------------
         |  Data descriptors inherited from scipy.integrate._ivp.base.OdeSolver:
         |
         |  __dict__
         |      dictionary for instance variables (if defined)
         |
         |  __weakref__
         |      list of weak references to the object (if defined)
         |
         |  step_size
         |
         |  ----------------------------------------------------------------------
         |  Data and other attributes inherited from scipy.integrate._ivp.base.OdeSolver:
         |
         |  TOO_SMALL_STEP = 'Required step size is less than spacing between numb...

        class DenseOutput(builtins.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.
         |
         |  Methods defined here:
         |
         |  __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.
         |
         |  __init__(self, t_old, t)
         |      Initialize self.  See help(type(self)) for accurate signature.
         |
         |  ----------------------------------------------------------------------
         |  Data descriptors defined here:
         |
         |  __dict__
         |      dictionary for instance variables (if defined)
         |
         |  __weakref__
         |      list of weak references to the object (if defined)

        class IntegrationWarning(builtins.UserWarning)
         |  Warning on issues during integration.
         |
         |  Method resolution order:
         |      IntegrationWarning
         |      builtins.UserWarning
         |      builtins.Warning
         |      builtins.Exception
         |      builtins.BaseException
         |      builtins.object
         |
         |  Data descriptors defined here:
         |
         |  __weakref__
         |      list of weak references to the object (if defined)
         |
         |  ----------------------------------------------------------------------
         |  Methods inherited from builtins.UserWarning:
         |
         |  __init__(self, /, *args, **kwargs)
         |      Initialize self.  See help(type(self)) for accurate signature.
         |
         |  __new__(*args, **kwargs) from builtins.type
         |      Create and return a new object.  See help(type) for accurate signature.
         |
         |  ----------------------------------------------------------------------
         |  Methods inherited from builtins.BaseException:
         |
         |  __delattr__(self, name, /)
         |      Implement delattr(self, name).
         |
         |  __getattribute__(self, name, /)
         |      Return getattr(self, name).
         |
         |  __reduce__(...)
         |      helper for pickle
         |
         |  __repr__(self, /)
         |      Return repr(self).
         |
         |  __setattr__(self, name, value, /)
         |      Implement setattr(self, name, value).
         |
         |  __setstate__(...)
         |
         |  __str__(self, /)
         |      Return str(self).
         |
         |  with_traceback(...)
         |      Exception.with_traceback(tb) --
         |      set self.__traceback__ to tb and return self.
         |
         |  ----------------------------------------------------------------------
         |  Data descriptors inherited from builtins.BaseException:
         |
         |  __cause__
         |      exception cause
         |
         |  __context__
         |      exception context
         |
         |  __dict__
         |
         |  __suppress_context__
         |
         |  __traceback__
         |
         |  args

        class LSODA(scipy.integrate._ivp.base.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 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). The
         |      vectorized implementation allows faster approximation of the Jacobian
         |      by finite differences.
         |  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 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 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` then 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), then 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, optional
         |      Jacobian band width:
         |      ``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 the system's rhs evaluations.
         |  njev : int
         |      Number of the Jacobian evaluations.
         |
         |  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.
         |
         |  Method resolution order:
         |      LSODA
         |      scipy.integrate._ivp.base.OdeSolver
         |      builtins.object
         |
         |  Methods defined here:
         |
         |  __init__(self, fun, t0, y0, t_bound, first_step=None, min_step=0.0, max_step=inf, rtol=0.001, atol=1e-06, jac=None, lband=None, uband=None, vectorized=False, **extraneous)
         |      Initialize self.  See help(type(self)) for accurate signature.
         |
         |  ----------------------------------------------------------------------
         |  Methods inherited from scipy.integrate._ivp.base.OdeSolver:
         |
         |  dense_output(self)
         |      Compute a local interpolant over the last successful step.
         |
         |      Returns
         |      -------
         |      sol : `DenseOutput`
         |          Local interpolant over the last successful step.
         |
         |  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.
         |
         |  ----------------------------------------------------------------------
         |  Data descriptors inherited from scipy.integrate._ivp.base.OdeSolver:
         |
         |  __dict__
         |      dictionary for instance variables (if defined)
         |
         |  __weakref__
         |      list of weak references to the object (if defined)
         |
         |  step_size
         |
         |  ----------------------------------------------------------------------
         |  Data and other attributes inherited from scipy.integrate._ivp.base.OdeSolver:
         |
         |  TOO_SMALL_STEP = 'Required step size is less than spacing between numb...

        class OdeSolution(builtins.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.
         |
         |  Methods defined here:
         |
         |  __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.
         |
         |  __init__(self, ts, interpolants)
         |      Initialize self.  See help(type(self)) for accurate signature.
         |
         |  ----------------------------------------------------------------------
         |  Data descriptors defined here:
         |
         |  __dict__
         |      dictionary for instance variables (if defined)
         |
         |  __weakref__
         |      list of weak references to the object (if defined)

        class OdeSolver(builtins.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.
         |
         |  Methods defined here:
         |
         |  __init__(self, fun, t0, y0, t_bound, vectorized, support_complex=False)
         |      Initialize self.  See help(type(self)) for accurate signature.
         |
         |  dense_output(self)
         |      Compute a local interpolant over the last successful step.
         |
         |      Returns
         |      -------
         |      sol : `DenseOutput`
         |          Local interpolant over the last successful step.
         |
         |  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.
         |
         |  ----------------------------------------------------------------------
         |  Data descriptors defined here:
         |
         |  __dict__
         |      dictionary for instance variables (if defined)
         |
         |  __weakref__
         |      list of weak references to the object (if defined)
         |
         |  step_size
         |
         |  ----------------------------------------------------------------------
         |  Data and other attributes defined here:
         |
         |  TOO_SMALL_STEP = 'Required step size is less than spacing between numb...

        class RK23(RungeKutta)
         |  Explicit Runge-Kutta method of order 3(2).
         |
         |  The Bogacki-Shamping pair of formulas is used [1]_. The error is controlled
         |  assuming 2nd order accuracy, but steps are taken using a 3rd oder accurate
         |  formula (local extrapolation is done). A cubic Hermit polynomial is used
         |  for the dense output.
         |
         |  Can be applied in a 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). The
         |      vectorized implementation allows faster approximation of the Jacobian
         |      by finite differences.
         |  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.
         |  max_step : float, optional
         |      Maximum allowed step size. Default is np.inf, i.e. the step 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` then 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 of the system's rhs evaluations.
         |  njev : int
         |      Number of the Jacobian evaluations.
         |  nlu : int
         |      Number of LU decompositions.
         |
         |  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.
         |
         |  Method resolution order:
         |      RK23
         |      RungeKutta
         |      scipy.integrate._ivp.base.OdeSolver
         |      builtins.object
         |
         |  Data and other attributes defined here:
         |
         |  A = [array([ 0.5]), array([ 0.  ,  0.75])]
         |
         |  B = array([ 0.22222222,  0.33333333,  0.44444444])
         |
         |  C = array([ 0.5 ,  0.75])
         |
         |  E = array([ 0.06944444, -0.08333333, -0.11111111,  0.125     ])
         |
         |  P = array([[ 1.        , -1.33333333,  0.55555556],
         |  ...
         |         [ 0.   ...
         |
         |  n_stages = 3
         |
         |  order = 2
         |
         |  ----------------------------------------------------------------------
         |  Methods inherited from RungeKutta:
         |
         |  __init__(self, fun, t0, y0, t_bound, max_step=inf, rtol=0.001, atol=1e-06, vectorized=False, **extraneous)
         |      Initialize self.  See help(type(self)) for accurate signature.
         |
         |  ----------------------------------------------------------------------
         |  Methods inherited from scipy.integrate._ivp.base.OdeSolver:
         |
         |  dense_output(self)
         |      Compute a local interpolant over the last successful step.
         |
         |      Returns
         |      -------
         |      sol : `DenseOutput`
         |          Local interpolant over the last successful step.
         |
         |  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.
         |
         |  ----------------------------------------------------------------------
         |  Data descriptors inherited from scipy.integrate._ivp.base.OdeSolver:
         |
         |  __dict__
         |      dictionary for instance variables (if defined)
         |
         |  __weakref__
         |      list of weak references to the object (if defined)
         |
         |  step_size
         |
         |  ----------------------------------------------------------------------
         |  Data and other attributes inherited from scipy.integrate._ivp.base.OdeSolver:
         |
         |  TOO_SMALL_STEP = 'Required step size is less than spacing between numb...

        class RK45(RungeKutta)
         |  Explicit Runge-Kutta method of order 5(4).
         |
         |  The Dormand-Prince pair of formulas is used [1]_. The error is controlled
         |  assuming 4th order accuracy, but steps are taken using a 5th
         |  oder accurate formula (local extrapolation is done). A quartic
         |  interpolation polynomial is used for the dense output [2]_.
         |
         |  Can be applied in a 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). The
         |      vectorized implementation allows faster approximation of the Jacobian
         |      by finite differences.
         |  t0 : float
         |      Initial value of the independent variable.
         |  y0 : array_like, shape (n,)
         |      Initial values of the dependent variable.
         |  t_bound : float
         |      Boundary time --- the integration won't continue beyond it. It also
         |      determines the direction of the integration.
         |  max_step : float, optional
         |      Maximum allowed step size. Default is np.inf, i.e. the step 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` then 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 of the system's rhs evaluations.
         |  njev : int
         |      Number of the Jacobian evaluations.
         |  nlu : int
         |      Number of LU decompositions.
         |
         |  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.
         |
         |  Method resolution order:
         |      RK45
         |      RungeKutta
         |      scipy.integrate._ivp.base.OdeSolver
         |      builtins.object
         |
         |  Data and other attributes defined here:
         |
         |  A = [array([ 0.2]), array([ 0.075,  0.225]), array([ 0.97777778, -3.73...
         |
         |  B = array([ 0.09114583,  0.        ,  0.4492363 ,  0.65104167, -0.3223...
         |
         |  C = array([ 0.2       ,  0.3       ,  0.8       ,  0.88888889,  1.    ...
         |
         |  E = array([-0.00123264,  0.        ,  0.00425277, -0...7,  0.0508638 ,...
         |
         |  P = array([[  1.        ,  -2.85358007,   3.07174346...    ,   1.38246...
         |
         |  n_stages = 6
         |
         |  order = 4
         |
         |  ----------------------------------------------------------------------
         |  Methods inherited from RungeKutta:
         |
         |  __init__(self, fun, t0, y0, t_bound, max_step=inf, rtol=0.001, atol=1e-06, vectorized=False, **extraneous)
         |      Initialize self.  See help(type(self)) for accurate signature.
         |
         |  ----------------------------------------------------------------------
         |  Methods inherited from scipy.integrate._ivp.base.OdeSolver:
         |
         |  dense_output(self)
         |      Compute a local interpolant over the last successful step.
         |
         |      Returns
         |      -------
         |      sol : `DenseOutput`
         |          Local interpolant over the last successful step.
         |
         |  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.
         |
         |  ----------------------------------------------------------------------
         |  Data descriptors inherited from scipy.integrate._ivp.base.OdeSolver:
         |
         |  __dict__
         |      dictionary for instance variables (if defined)
         |
         |  __weakref__
         |      list of weak references to the object (if defined)
         |
         |  step_size
         |
         |  ----------------------------------------------------------------------
         |  Data and other attributes inherited from scipy.integrate._ivp.base.OdeSolver:
         |
         |  TOO_SMALL_STEP = 'Required step size is less than spacing between numb...

        class Radau(scipy.integrate._ivp.base.OdeSolver)
         |  Implicit Runge-Kutta method of Radau IIA family of order 5.
         |
         |  Implementation follows [1]_. The error is controlled for a 3rd 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 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). The
         |      vectorized implementation allows faster approximation of the Jacobian
         |      by finite differences.
         |  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.
         |  max_step : float, optional
         |      Maximum allowed step size. Default is np.inf, i.e. the step 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` then 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 only by 'Radau' and 'BDF' methods. The Jacobian matrix
         |      has shape (n, n) and its element (i, j) is equal to ``d f_i / d y_j``.
         |      There are 3 ways to define the Jacobian:
         |
         |          * If array_like or sparse_matrix, then the Jacobian is assumed to
         |            be constant.
         |          * If callable, then the Jacobian is assumed to depend on both
         |            t and y, and will be called as ``jac(t, y)`` as necessary. The
         |            return value might be a sparse matrix.
         |          * If None (default), then 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). 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 identically
         |      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 the system's rhs evaluations.
         |  njev : int
         |      Number of the Jacobian evaluations.
         |  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.
         |
         |  Method resolution order:
         |      Radau
         |      scipy.integrate._ivp.base.OdeSolver
         |      builtins.object
         |
         |  Methods defined here:
         |
         |  __init__(self, fun, t0, y0, t_bound, max_step=inf, rtol=0.001, atol=1e-06, jac=None, jac_sparsity=None, vectorized=False, **extraneous)
         |      Initialize self.  See help(type(self)) for accurate signature.
         |
         |  ----------------------------------------------------------------------
         |  Methods inherited from scipy.integrate._ivp.base.OdeSolver:
         |
         |  dense_output(self)
         |      Compute a local interpolant over the last successful step.
         |
         |      Returns
         |      -------
         |      sol : `DenseOutput`
         |          Local interpolant over the last successful step.
         |
         |  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.
         |
         |  ----------------------------------------------------------------------
         |  Data descriptors inherited from scipy.integrate._ivp.base.OdeSolver:
         |
         |  __dict__
         |      dictionary for instance variables (if defined)
         |
         |  __weakref__
         |      list of weak references to the object (if defined)
         |
         |  step_size
         |
         |  ----------------------------------------------------------------------
         |  Data and other attributes inherited from scipy.integrate._ivp.base.OdeSolver:
         |
         |  TOO_SMALL_STEP = 'Required step size is less than spacing between numb...

        class complex_ode(ode)
         |  A wrapper of ode for complex systems.
         |
         |  This functions similarly as `ode`, but re-maps a complex-valued
         |  equation system to a real-valued one before using the integrators.
         |
         |  Parameters
         |  ----------
         |  f : callable ``f(t, y, *f_args)``
         |      Rhs of the equation. t is a scalar, ``y.shape == (n,)``.
         |      ``f_args`` is set by calling ``set_f_params(*args)``.
         |  jac : callable ``jac(t, y, *jac_args)``
         |      Jacobian of the rhs, ``jac[i,j] = d f[i] / d y[j]``.
         |      ``jac_args`` is set by calling ``set_f_params(*args)``.
         |
         |  Attributes
         |  ----------
         |  t : float
         |      Current time.
         |  y : ndarray
         |      Current variable values.
         |
         |  Examples
         |  --------
         |  For usage examples, see `ode`.
         |
         |  Method resolution order:
         |      complex_ode
         |      ode
         |      builtins.object
         |
         |  Methods defined here:
         |
         |  __init__(self, f, jac=None)
         |      Initialize self.  See help(type(self)) for accurate signature.
         |
         |  integrate(self, t, step=False, relax=False)
         |      Find y=y(t), set y as an initial condition, and return y.
         |
         |      Parameters
         |      ----------
         |      t : float
         |          The endpoint of the integration step.
         |      step : bool
         |          If True, and if the integrator supports the step method,
         |          then perform a single integration step and return.
         |          This parameter is provided in order to expose internals of
         |          the implementation, and should not be changed from its default
         |          value in most cases.
         |      relax : bool
         |          If True and if the integrator supports the run_relax method,
         |          then integrate until t_1 >= t and return. ``relax`` is not
         |          referenced if ``step=True``.
         |          This parameter is provided in order to expose internals of
         |          the implementation, and should not be changed from its default
         |          value in most cases.
         |
         |      Returns
         |      -------
         |      y : float
         |          The integrated value at t
         |
         |  set_initial_value(self, y, t=0.0)
         |      Set initial conditions y(t) = y.
         |
         |  set_integrator(self, name, **integrator_params)
         |      Set integrator by name.
         |
         |      Parameters
         |      ----------
         |      name : str
         |          Name of the integrator
         |      integrator_params
         |          Additional parameters for the integrator.
         |
         |  set_solout(self, solout)
         |      Set callable to be called at every successful integration step.
         |
         |      Parameters
         |      ----------
         |      solout : callable
         |          ``solout(t, y)`` is called at each internal integrator step,
         |          t is a scalar providing the current independent position
         |          y is the current soloution ``y.shape == (n,)``
         |          solout should return -1 to stop integration
         |          otherwise it should return None or 0
         |
         |  ----------------------------------------------------------------------
         |  Data descriptors defined here:
         |
         |  y
         |
         |  ----------------------------------------------------------------------
         |  Methods inherited from ode:
         |
         |  get_return_code(self)
         |      Extracts the return code for the integration to enable better control
         |      if the integration fails.
         |
         |  set_f_params(self, *args)
         |      Set extra parameters for user-supplied function f.
         |
         |  set_jac_params(self, *args)
         |      Set extra parameters for user-supplied function jac.
         |
         |  successful(self)
         |      Check if integration was successful.
         |
         |  ----------------------------------------------------------------------
         |  Data descriptors inherited from ode:
         |
         |  __dict__
         |      dictionary for instance variables (if defined)
         |
         |  __weakref__
         |      list of weak references to the object (if defined)

        class ode(builtins.object)
         |  A generic interface class to numeric integrators.
         |
         |  Solve an equation system :math:`y'(t) = f(t,y)` with (optional) ``jac = df/dy``.
         |
         |  *Note*: The first two arguments of ``f(t, y, ...)`` are in the
         |  opposite order of the arguments in the system definition function used
         |  by `scipy.integrate.odeint`.
         |
         |  Parameters
         |  ----------
         |  f : callable ``f(t, y, *f_args)``
         |      Right-hand side of the differential equation. t is a scalar,
         |      ``y.shape == (n,)``.
         |      ``f_args`` is set by calling ``set_f_params(*args)``.
         |      `f` should return a scalar, array or list (not a tuple).
         |  jac : callable ``jac(t, y, *jac_args)``, optional
         |      Jacobian of the right-hand side, ``jac[i,j] = d f[i] / d y[j]``.
         |      ``jac_args`` is set by calling ``set_jac_params(*args)``.
         |
         |  Attributes
         |  ----------
         |  t : float
         |      Current time.
         |  y : ndarray
         |      Current variable values.
         |
         |  See also
         |  --------
         |  odeint : an integrator with a simpler interface based on lsoda from ODEPACK
         |  quad : for finding the area under a curve
         |
         |  Notes
         |  -----
         |  Available integrators are listed below. They can be selected using
         |  the `set_integrator` method.
         |
         |  "vode"
         |
         |      Real-valued Variable-coefficient Ordinary Differential Equation
         |      solver, with fixed-leading-coefficient implementation. It provides
         |      implicit Adams method (for non-stiff problems) and a method based on
         |      backward differentiation formulas (BDF) (for stiff problems).
         |
         |      Source: http://www.netlib.org/ode/vode.f
         |
         |      .. warning::
         |
         |         This integrator is not re-entrant. You cannot have two `ode`
         |         instances using the "vode" integrator at the same time.
         |
         |      This integrator accepts the following parameters in `set_integrator`
         |      method of the `ode` class:
         |
         |      - atol : float or sequence
         |        absolute tolerance for solution
         |      - rtol : float or sequence
         |        relative tolerance for solution
         |      - lband : None or int
         |      - uband : None or int
         |        Jacobian band width, jac[i,j] != 0 for i-lband <= j <= i+uband.
         |        Setting these requires your jac routine to return the jacobian
         |        in packed format, jac_packed[i-j+uband, j] = jac[i,j]. The
         |        dimension of the matrix must be (lband+uband+1, len(y)).
         |      - method: 'adams' or 'bdf'
         |        Which solver to use, Adams (non-stiff) or BDF (stiff)
         |      - with_jacobian : bool
         |        This option is only considered when the user has not supplied a
         |        Jacobian function and has not indicated (by setting either band)
         |        that the Jacobian is banded.  In this case, `with_jacobian` specifies
         |        whether the iteration method of the ODE solver's correction step is
         |        chord iteration with an internally generated full Jacobian or
         |        functional iteration with no Jacobian.
         |      - nsteps : int
         |        Maximum number of (internally defined) steps allowed during one
         |        call to the solver.
         |      - first_step : float
         |      - min_step : float
         |      - max_step : float
         |        Limits for the step sizes used by the integrator.
         |      - order : int
         |        Maximum order used by the integrator,
         |        order <= 12 for Adams, <= 5 for BDF.
         |
         |  "zvode"
         |
         |      Complex-valued Variable-coefficient Ordinary Differential Equation
         |      solver, with fixed-leading-coefficient implementation.  It provides
         |      implicit Adams method (for non-stiff problems) and a method based on
         |      backward differentiation formulas (BDF) (for stiff problems).
         |
         |      Source: http://www.netlib.org/ode/zvode.f
         |
         |      .. warning::
         |
         |         This integrator is not re-entrant. You cannot have two `ode`
         |         instances using the "zvode" integrator at the same time.
         |
         |      This integrator accepts the same parameters in `set_integrator`
         |      as the "vode" solver.
         |
         |      .. note::
         |
         |          When using ZVODE for a stiff system, it should only be used for
         |          the case in which the function f is analytic, that is, when each f(i)
         |          is an analytic function of each y(j).  Analyticity means that the
         |          partial derivative df(i)/dy(j) is a unique complex number, and this
         |          fact is critical in the way ZVODE solves the dense or banded linear
         |          systems that arise in the stiff case.  For a complex stiff ODE system
         |          in which f is not analytic, ZVODE is likely to have convergence
         |          failures, and for this problem one should instead use DVODE on the
         |          equivalent real system (in the real and imaginary parts of y).
         |
         |  "lsoda"
         |
         |      Real-valued Variable-coefficient Ordinary Differential Equation
         |      solver, with fixed-leading-coefficient implementation. It provides
         |      automatic method switching between implicit Adams method (for non-stiff
         |      problems) and a method based on backward differentiation formulas (BDF)
         |      (for stiff problems).
         |
         |      Source: http://www.netlib.org/odepack
         |
         |      .. warning::
         |
         |         This integrator is not re-entrant. You cannot have two `ode`
         |         instances using the "lsoda" integrator at the same time.
         |
         |      This integrator accepts the following parameters in `set_integrator`
         |      method of the `ode` class:
         |
         |      - atol : float or sequence
         |        absolute tolerance for solution
         |      - rtol : float or sequence
         |        relative tolerance for solution
         |      - lband : None or int
         |      - uband : None or int
         |        Jacobian band width, jac[i,j] != 0 for i-lband <= j <= i+uband.
         |        Setting these requires your jac routine to return the jacobian
         |        in packed format, jac_packed[i-j+uband, j] = jac[i,j].
         |      - with_jacobian : bool
         |        *Not used.*
         |      - nsteps : int
         |        Maximum number of (internally defined) steps allowed during one
         |        call to the solver.
         |      - first_step : float
         |      - min_step : float
         |      - max_step : float
         |        Limits for the step sizes used by the integrator.
         |      - max_order_ns : int
         |        Maximum order used in the nonstiff case (default 12).
         |      - max_order_s : int
         |        Maximum order used in the stiff case (default 5).
         |      - max_hnil : int
         |        Maximum number of messages reporting too small step size (t + h = t)
         |        (default 0)
         |      - ixpr : int
         |        Whether to generate extra printing at method switches (default False).
         |
         |  "dopri5"
         |
         |      This is an explicit runge-kutta method of order (4)5 due to Dormand &
         |      Prince (with stepsize control and dense output).
         |
         |      Authors:
         |
         |          E. Hairer and G. Wanner
         |          Universite de Geneve, Dept. de Mathematiques
         |          CH-1211 Geneve 24, Switzerland
         |          e-mail:  ernst.hairer@math.unige.ch, gerhard.wanner@math.unige.ch
         |
         |      This code is described in [HNW93]_.
         |
         |      This integrator accepts the following parameters in set_integrator()
         |      method of the ode class:
         |
         |      - atol : float or sequence
         |        absolute tolerance for solution
         |      - rtol : float or sequence
         |        relative tolerance for solution
         |      - nsteps : int
         |        Maximum number of (internally defined) steps allowed during one
         |        call to the solver.
         |      - first_step : float
         |      - max_step : float
         |      - safety : float
         |        Safety factor on new step selection (default 0.9)
         |      - ifactor : float
         |      - dfactor : float
         |        Maximum factor to increase/decrease step size by in one step
         |      - beta : float
         |        Beta parameter for stabilised step size control.
         |      - verbosity : int
         |        Switch for printing messages (< 0 for no messages).
         |
         |  "dop853"
         |
         |      This is an explicit runge-kutta method of order 8(5,3) due to Dormand
         |      & Prince (with stepsize control and dense output).
         |
         |      Options and references the same as "dopri5".
         |
         |  Examples
         |  --------
         |
         |  A problem to integrate and the corresponding jacobian:
         |
         |  >>> from scipy.integrate import ode
         |  >>>
         |  >>> y0, t0 = [1.0j, 2.0], 0
         |  >>>
         |  >>> def f(t, y, arg1):
         |  ...     return [1j*arg1*y[0] + y[1], -arg1*y[1]**2]
         |  >>> def jac(t, y, arg1):
         |  ...     return [[1j*arg1, 1], [0, -arg1*2*y[1]]]
         |
         |  The integration:
         |
         |  >>> r = ode(f, jac).set_integrator('zvode', method='bdf')
         |  >>> r.set_initial_value(y0, t0).set_f_params(2.0).set_jac_params(2.0)
         |  >>> t1 = 10
         |  >>> dt = 1
         |  >>> while r.successful() and r.t < t1:
         |  ...     print(r.t+dt, r.integrate(r.t+dt))
         |  1 [-0.71038232+0.23749653j  0.40000271+0.j        ]
         |  2.0 [ 0.19098503-0.52359246j  0.22222356+0.j        ]
         |  3.0 [ 0.47153208+0.52701229j  0.15384681+0.j        ]
         |  4.0 [-0.61905937+0.30726255j  0.11764744+0.j        ]
         |  5.0 [ 0.02340997-0.61418799j  0.09523835+0.j        ]
         |  6.0 [ 0.58643071+0.339819j  0.08000018+0.j      ]
         |  7.0 [-0.52070105+0.44525141j  0.06896565+0.j        ]
         |  8.0 [-0.15986733-0.61234476j  0.06060616+0.j        ]
         |  9.0 [ 0.64850462+0.15048982j  0.05405414+0.j        ]
         |  10.0 [-0.38404699+0.56382299j  0.04878055+0.j        ]
         |
         |  References
         |  ----------
         |  .. [HNW93] E. Hairer, S.P. Norsett and G. Wanner, Solving Ordinary
         |      Differential Equations i. Nonstiff Problems. 2nd edition.
         |      Springer Series in Computational Mathematics,
         |      Springer-Verlag (1993)
         |
         |  Methods defined here:
         |
         |  __init__(self, f, jac=None)
         |      Initialize self.  See help(type(self)) for accurate signature.
         |
         |  get_return_code(self)
         |      Extracts the return code for the integration to enable better control
         |      if the integration fails.
         |
         |  integrate(self, t, step=False, relax=False)
         |      Find y=y(t), set y as an initial condition, and return y.
         |
         |      Parameters
         |      ----------
         |      t : float
         |          The endpoint of the integration step.
         |      step : bool
         |          If True, and if the integrator supports the step method,
         |          then perform a single integration step and return.
         |          This parameter is provided in order to expose internals of
         |          the implementation, and should not be changed from its default
         |          value in most cases.
         |      relax : bool
         |          If True and if the integrator supports the run_relax method,
         |          then integrate until t_1 >= t and return. ``relax`` is not
         |          referenced if ``step=True``.
         |          This parameter is provided in order to expose internals of
         |          the implementation, and should not be changed from its default
         |          value in most cases.
         |
         |      Returns
         |      -------
         |      y : float
         |          The integrated value at t
         |
         |  set_f_params(self, *args)
         |      Set extra parameters for user-supplied function f.
         |
         |  set_initial_value(self, y, t=0.0)
         |      Set initial conditions y(t) = y.
         |
         |  set_integrator(self, name, **integrator_params)
         |      Set integrator by name.
         |
         |      Parameters
         |      ----------
         |      name : str
         |          Name of the integrator.
         |      integrator_params
         |          Additional parameters for the integrator.
         |
         |  set_jac_params(self, *args)
         |      Set extra parameters for user-supplied function jac.
         |
         |  set_solout(self, solout)
         |      Set callable to be called at every successful integration step.
         |
         |      Parameters
         |      ----------
         |      solout : callable
         |          ``solout(t, y)`` is called at each internal integrator step,
         |          t is a scalar providing the current independent position
         |          y is the current soloution ``y.shape == (n,)``
         |          solout should return -1 to stop integration
         |          otherwise it should return None or 0
         |
         |  successful(self)
         |      Check if integration was successful.
         |
         |  ----------------------------------------------------------------------
         |  Data descriptors defined here:
         |
         |  __dict__
         |      dictionary for instance variables (if defined)
         |
         |  __weakref__
         |      list of weak references to the object (if defined)
         |
         |  y

    FUNCTIONS
        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, uses this value as the first value in 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()

        dblquad(func, a, b, gfun, hfun, args=(), epsabs=1.49e-08, epsrel=1.49e-08)
            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
                The lower boundary curve in y which is a function taking a single
                floating point argument (x) and returning a floating point result: a
                lambda function can be useful here.
            hfun : callable
                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.
            epsrel : float, optional
                Relative tolerance of the inner 1-D integrals. Default is 1.49e-8.

            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

        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

        newton_cotes(rn, equal=0)
            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.

            Notes
            -----
            Normally, the Newton-Cotes rules are used on smaller integration
            regions and a composite rule is used to return the total integral.

        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-dimensional 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)

        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)
            Integrate a system of ordinary differential equations.

            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, t0, ...)

            where y can be a vector.

            *Note*: The first two arguments of ``func(y, t0, ...)`` are in the
            opposite order of the arguments in the system definition function used
            by the `scipy.integrate.ode` class.

            Parameters
            ----------
            func : callable(y, t0, ...)
                Computes the derivative of y at t0.
            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.
            args : tuple, optional
                Extra arguments to pass to function.
            Dfun : callable(y, t0, ...)
                Gradient (Jacobian) of `func`.
            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

            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
            --------
            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 it initially at rest, so
            `omega(0)` = 0.  Then the vector of initial conditions is

            >>> y0 = [np.pi - 0.1, 0.0]

            We generate a solution 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()

        quad(func, a, b, args=(), full_output=0, epsabs=1.49e-08, epsrel=1.49e-08, 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.
            epsrel : float or int, optional
                Relative error tolerance.
            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.
            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.  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

        quad_explain(output=<ipykernel.iostream.OutStream object at 0x7f91caf55470>)
            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

        quadrature(func, a, b, args=(), tol=1.49e-08, rtol=1.49e-08, 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

        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

        romberg(function, a, b, args=(), tol=1.48e-08, rtol=1.48e-08, 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' http://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

        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 `y`. 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.

        solve_bvp(fun, bc, x, y, p=None, S=None, fun_jac=None, bc_jac=None, tol=0.001, max_nodes=1000, verbose=0)
            Solve a boundary-value problem for a system of ODEs.

            This function numerically solves a first order system of ODEs subject to
            two-point boundary conditions::

                dy / dx = f(x, y, p) + S * y / (x - a), a <= x <= b
                bc(y(a), y(b), p) = 0

            Here x is a 1-dimensional independent variable, y(x) is a n-dimensional
            vector-valued function and p is a k-dimensional vector of unknown
            parameters which is to be found along with y(x). For the problem to be
            determined there must be n + k boundary conditions, i.e. bc must be
            (n + k)-dimensional function.

            The last singular term in the right-hand side of the system is optional.
            It is defined by an n-by-n matrix S, such that the solution must satisfy
            S y(a) = 0. This condition will be forced during iterations, so it must not
            contradict boundary conditions. See [2]_ for the explanation how this term
            is handled when solving BVPs numerically.

            Problems in a complex domain can be solved as well. In this case y and p
            are considered to be complex, and f and bc are assumed to be complex-valued
            functions, but x stays real. Note that f and bc must be complex
            differentiable (satisfy Cauchy-Riemann equations [4]_), otherwise you
            should rewrite your problem for real and imaginary parts separately. To
            solve a problem in a complex domain, pass an initial guess for y with a
            complex data type (see below).

            Parameters
            ----------
            fun : callable
                Right-hand side of the system. The calling signature is ``fun(x, y)``,
                or ``fun(x, y, p)`` if parameters are present. All arguments are
                ndarray: ``x`` with shape (m,), ``y`` with shape (n, m), meaning that
                ``y[:, i]`` corresponds to ``x[i]``, and ``p`` with shape (k,). The
                return value must be an array with shape (n, m) and with the same
                layout as ``y``.
            bc : callable
                Function evaluating residuals of the boundary conditions. The calling
                signature is ``bc(ya, yb)``, or ``bc(ya, yb, p)`` if parameters are
                present. All arguments are ndarray: ``ya`` and ``yb`` with shape (n,),
                and ``p`` with shape (k,). The return value must be an array with
                shape (n + k,).
            x : array_like, shape (m,)
                Initial mesh. Must be a strictly increasing sequence of real numbers
                with ``x[0]=a`` and ``x[-1]=b``.
            y : array_like, shape (n, m)
                Initial guess for the function values at the mesh nodes, i-th column
                corresponds to ``x[i]``. For problems in a complex domain pass `y`
                with a complex data type (even if the initial guess is purely real).
            p : array_like with shape (k,) or None, optional
                Initial guess for the unknown parameters. If None (default), it is
                assumed that the problem doesn't depend on any parameters.
            S : array_like with shape (n, n) or None
                Matrix defining the singular term. If None (default), the problem is
                solved without the singular term.
            fun_jac : callable or None, optional
                Function computing derivatives of f with respect to y and p. The
                calling signature is ``fun_jac(x, y)``, or ``fun_jac(x, y, p)`` if
                parameters are present. The return must contain 1 or 2 elements in the
                following order:

                    * df_dy : array_like with shape (n, n, m) where an element
                      (i, j, q) equals to d f_i(x_q, y_q, p) / d (y_q)_j.
                    * df_dp : array_like with shape (n, k, m) where an element
                      (i, j, q) equals to d f_i(x_q, y_q, p) / d p_j.

                Here q numbers nodes at which x and y are defined, whereas i and j
                number vector components. If the problem is solved without unknown
                parameters df_dp should not be returned.

                If `fun_jac` is None (default), the derivatives will be estimated
                by the forward finite differences.
            bc_jac : callable or None, optional
                Function computing derivatives of bc with respect to ya, yb and p.
                The calling signature is ``bc_jac(ya, yb)``, or ``bc_jac(ya, yb, p)``
                if parameters are present. The return must contain 2 or 3 elements in
                the following order:

                    * dbc_dya : array_like with shape (n, n) where an element (i, j)
                      equals to d bc_i(ya, yb, p) / d ya_j.
                    * dbc_dyb : array_like with shape (n, n) where an element (i, j)
                      equals to d bc_i(ya, yb, p) / d yb_j.
                    * dbc_dp : array_like with shape (n, k) where an element (i, j)
                      equals to d bc_i(ya, yb, p) / d p_j.

                If the problem is solved without unknown parameters dbc_dp should not
                be returned.

                If `bc_jac` is None (default), the derivatives will be estimated by
                the forward finite differences.
            tol : float, optional
                Desired tolerance of the solution. If we define ``r = y' - f(x, y)``
                where y is the found solution, then the solver tries to achieve on each
                mesh interval ``norm(r / (1 + abs(f)) < tol``, where ``norm`` is
                estimated in a root mean squared sense (using a numerical quadrature
                formula). Default is 1e-3.
            max_nodes : int, optional
                Maximum allowed number of the mesh nodes. If exceeded, the algorithm
                terminates. Default is 1000.
            verbose : {0, 1, 2}, optional
                Level of algorithm's verbosity:

                    * 0 (default) : work silently.
                    * 1 : display a termination report.
                    * 2 : display progress during iterations.

            Returns
            -------
            Bunch object with the following fields defined:
            sol : PPoly
                Found solution for y as `scipy.interpolate.PPoly` instance, a C1
                continuous cubic spline.
            p : ndarray or None, shape (k,)
                Found parameters. None, if the parameters were not present in the
                problem.
            x : ndarray, shape (m,)
                Nodes of the final mesh.
            y : ndarray, shape (n, m)
                Solution values at the mesh nodes.
            yp : ndarray, shape (n, m)
                Solution derivatives at the mesh nodes.
            rms_residuals : ndarray, shape (m - 1,)
                RMS values of the relative residuals over each mesh interval (see the
                description of `tol` parameter).
            niter : int
                Number of completed iterations.
            status : int
                Reason for algorithm termination:

                    * 0: The algorithm converged to the desired accuracy.
                    * 1: The maximum number of mesh nodes is exceeded.
                    * 2: A singular Jacobian encountered when solving the collocation
                      system.

            message : string
                Verbal description of the termination reason.
            success : bool
                True if the algorithm converged to the desired accuracy (``status=0``).

            Notes
            -----
            This function implements a 4-th order collocation algorithm with the
            control of residuals similar to [1]_. A collocation system is solved
            by a damped Newton method with an affine-invariant criterion function as
            described in [3]_.

            Note that in [1]_  integral residuals are defined without normalization
            by interval lengths. So their definition is different by a multiplier of
            h**0.5 (h is an interval length) from the definition used here.

            .. versionadded:: 0.18.0

            References
            ----------
            .. [1] J. Kierzenka, L. F. Shampine, "A BVP Solver Based on Residual
                   Control and the Maltab PSE", ACM Trans. Math. Softw., Vol. 27,
                   Number 3, pp. 299-316, 2001.
            .. [2] L.F. Shampine, P. H. Muir and H. Xu, "A User-Friendly Fortran BVP
                   Solver".
            .. [3] U. Ascher, R. Mattheij and R. Russell "Numerical Solution of
                   Boundary Value Problems for Ordinary Differential Equations".
            .. [4] `Cauchy-Riemann equations
                    <https://en.wikipedia.org/wiki/Cauchy-Riemann_equations>`_ on
                    Wikipedia.

            Examples
            --------
            In the first example we solve Bratu's problem::

                y'' + k * exp(y) = 0
                y(0) = y(1) = 0

            for k = 1.

            We rewrite the equation as a first order system and implement its
            right-hand side evaluation::

                y1' = y2
                y2' = -exp(y1)

            >>> def fun(x, y):
            ...     return np.vstack((y[1], -np.exp(y[0])))

            Implement evaluation of the boundary condition residuals:

            >>> def bc(ya, yb):
            ...     return np.array([ya[0], yb[0]])

            Define the initial mesh with 5 nodes:

            >>> x = np.linspace(0, 1, 5)

            This problem is known to have two solutions. To obtain both of them we
            use two different initial guesses for y. We denote them by subscripts
            a and b.

            >>> y_a = np.zeros((2, x.size))
            >>> y_b = np.zeros((2, x.size))
            >>> y_b[0] = 3

            Now we are ready to run the solver.

            >>> from scipy.integrate import solve_bvp
            >>> res_a = solve_bvp(fun, bc, x, y_a)
            >>> res_b = solve_bvp(fun, bc, x, y_b)

            Let's plot the two found solutions. We take an advantage of having the
            solution in a spline form to produce a smooth plot.

            >>> x_plot = np.linspace(0, 1, 100)
            >>> y_plot_a = res_a.sol(x_plot)[0]
            >>> y_plot_b = res_b.sol(x_plot)[0]
            >>> import matplotlib.pyplot as plt
            >>> plt.plot(x_plot, y_plot_a, label='y_a')
            >>> plt.plot(x_plot, y_plot_b, label='y_b')
            >>> plt.legend()
            >>> plt.xlabel("x")
            >>> plt.ylabel("y")
            >>> plt.show()

            We see that the two solutions have similar shape, but differ in scale
            significantly.

            In the second example we solve a simple Sturm-Liouville problem::

                y'' + k**2 * y = 0
                y(0) = y(1) = 0

            It is known that a non-trivial solution y = A * sin(k * x) is possible for
            k = pi * n, where n is an integer. To establish the normalization constant
            A = 1 we add a boundary condition::

                y'(0) = k

            Again we rewrite our equation as a first order system and implement its
            right-hand side evaluation::

                y1' = y2
                y2' = -k**2 * y1

            >>> def fun(x, y, p):
            ...     k = p[0]
            ...     return np.vstack((y[1], -k**2 * y[0]))

            Note that parameters p are passed as a vector (with one element in our
            case).

            Implement the boundary conditions:

            >>> def bc(ya, yb, p):
            ...     k = p[0]
            ...     return np.array([ya[0], yb[0], ya[1] - k])

            Setup the initial mesh and guess for y. We aim to find the solution for
            k = 2 * pi, to achieve that we set values of y to approximately follow
            sin(2 * pi * x):

            >>> x = np.linspace(0, 1, 5)
            >>> y = np.zeros((2, x.size))
            >>> y[0, 1] = 1
            >>> y[0, 3] = -1

            Run the solver with 6 as an initial guess for k.

            >>> sol = solve_bvp(fun, bc, x, y, p=[6])

            We see that the found k is approximately correct:

            >>> sol.p[0]
            6.28329460046

            And finally plot the solution to see the anticipated sinusoid:

            >>> x_plot = np.linspace(0, 1, 100)
            >>> y_plot = sol.sol(x_plot)[0]
            >>> plt.plot(x_plot, y_plot)
            >>> plt.xlabel("x")
            >>> plt.ylabel("y")
            >>> plt.show()

        solve_ivp(fun, t_span, y0, method='RK45', t_eval=None, dense_output=False, events=None, vectorized=False, **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-dimensional independent variable (time), y(t) is an
            n-dimensional vector-valued function (state) and an n-dimensional
            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 a 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 a 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 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). The
                vectorized implementation allows 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 a complex domain pass `y0` with a
                complex data type (even if the initial guess 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 4th order accuracy, but steps
                      are taken using a 5th oder accurate formula (local extrapolation
                      is done). A quartic interpolation polynomial is used for the
                      dense output [2]_. Can be applied in a complex domain.
                    * 'RK23': Explicit Runge-Kutta method of order 3(2) [3]_. The error
                      is controlled assuming 2nd order accuracy, but steps are taken
                      using a 3rd oder accurate formula (local extrapolation is done).
                      A cubic Hermit polynomial is used for the dense output.
                      Can be applied in a complex domain.
                    * 'Radau': Implicit Runge-Kutta method of Radau IIA family of
                      order 5 [4]_. The error is controlled for a 3rd 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 Formulas for the derivative
                      approximation [5]_. An implementation approach follows the one
                      described in [6]_. A quasi-constant step scheme is used
                      and accuracy enhancement using NDF modification is also
                      implemented. Can be applied in a complex domain.
                    * 'LSODA': Adams/BDF method with automatic stiffness detection and
                      switching [7]_, [8]_. This is a wrapper of the Fortran solver
                      from ODEPACK.

                You should use 'RK45' or 'RK23' methods for non-stiff problems and
                'Radau' or 'BDF' for stiff problems [9]_. If not sure, first try to run
                'RK45' and if it does unusual many iterations or diverges then 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 an old Fortran code.

                You can also pass an arbitrary class derived from `OdeSolver` which
                implements the solver.
            dense_output : bool, optional
                Whether to compute a continuous solution. Default is False.
            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 a solver.
            events : callable, list of callables or None, optional
                Events to track. Events are defined by functions which take
                a zero value at a point of an event. Each function must have a
                signature ``event(t, y)`` and return float, the solver will find an
                accurate value of ``t`` at which ``event(t, y(t)) = 0`` using a root
                finding algorithm. Additionally each ``event`` function might have
                attributes:

                    * terminal: bool, whether to terminate integration if this
                      event occurs. Implicitly False if not assigned.
                    * direction: float, direction of crossing a zero. If `direction`
                      is positive then `event` must go from negative to positive, and
                      vice-versa if `direction` is negative. If 0, then either way will
                      count. Implicitly 0 if not assigned.

                You can assign attributes like ``event.terminal = True`` to any
                function in Python. If None (default), events won't be tracked.
            vectorized : bool, optional
                Whether `fun` is implemented in a vectorized fashion. Default is False.
            options
                Options passed to a chosen solver constructor. All options available
                for already implemented solvers are listed below.
            max_step : float, optional
                Maximum allowed step size. Default is np.inf, i.e. step 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` then 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 'Radau', 'BDF' and 'LSODA' methods. The Jacobian matrix
                has shape (n, n) and its element (i, j) is equal to ``d f_i / d y_j``.
                There are 3 ways to define the Jacobian:

                    * If array_like or sparse_matrix, then the Jacobian is assumed to
                      be constant. Not supported by 'LSODA'.
                    * If callable, then the Jacobian is assumed to depend on both
                      t and y, and 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), then 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). 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 identically
                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
                Parameters defining the Jacobian matrix bandwidth for 'LSODA' method.
                The Jacobian bandwidth means that
                ``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.
            min_step, first_step : float, optional
                The minimum allowed step size and the initial step size respectively
                for 'LSODA' method. By default `min_step` is zero and `first_step` is
                selected automatically.

            Returns
            -------
            Bunch object with the following fields defined:
            t : ndarray, shape (n_points,)
                Time points.
            y : ndarray, shape (n, n_points)
                Solution values 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 arrays with times at each a corresponding event was detected,
                the length of the list equals to the number of events. None if `events`
                was None.
            nfev : int
                Number of the system rhs evaluations.
            njev : int
                Number of the Jacobian evaluations.
            nlu : int
                Number of LU decompositions.
            status : int
                Reason for algorithm termination:

                    * -1: Integration step failed.
                    * 0: The solver successfully reached the interval end.
                    * 1: A termination event occurred.

            message : string
                Verbal 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.

            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.85759374
               6.65456967   8.4515456   10.        ]
            >>> print(sol.y)
            [[ 2.          1.88836035  1.06327177  0.43319312  0.17648948  0.0719045
               0.02929499  0.01350938]
             [ 4.          3.7767207   2.12654355  0.86638624  0.35297895  0.143809
               0.05858998  0.02701876]
             [ 8.          7.5534414   4.25308709  1.73277247  0.7059579   0.287618
               0.11717996  0.05403753]]

            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[1]
            >>> 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([ 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   2.00000000e+01]

        tplquad(func, a, b, gfun, hfun, qfun, rfun, args=(), epsabs=1.49e-08, epsrel=1.49e-08)
            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
                The lower boundary curve in y which is a function taking a single
                floating point argument (x) and returning a floating point result:
                a lambda function can be useful here.
            hfun : function
                The upper boundary curve in y (same requirements as `gfun`).
            qfun : function
                The lower boundary surface in z.  It must be a function that takes
                two floats in the order (x, y) and returns a float.
            rfun : function
                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

        trapz(y, x=None, dx=1.0, axis=-1)
            Integrate along the given axis using the composite trapezoidal rule.

            Integrate `y` (`x`) along given axis.

            Parameters
            ----------
            y : array_like
                Input array to integrate.
            x : array_like, optional
                The sample points corresponding to the `y` values. If `x` is None,
                the sample points are assumed to be evenly spaced `dx` apart. The
                default is None.
            dx : scalar, optional
                The spacing between sample points when `x` is None. The default is 1.
            axis : int, optional
                The axis along which to integrate.

            Returns
            -------
            trapz : float
                Definite integral as approximated by trapezoidal rule.

            See Also
            --------
            sum, cumsum

            Notes
            -----
            Image [2]_ illustrates trapezoidal rule -- y-axis locations of points
            will be taken from `y` array, by default x-axis distances between
            points will be 1.0, alternatively they can be provided with `x` array
            or with `dx` scalar.  Return value will be equal to combined area under
            the red lines.

            References
            ----------
            .. [1] Wikipedia page: http://en.wikipedia.org/wiki/Trapezoidal_rule

            .. [2] Illustration image:
                   http://en.wikipedia.org/wiki/File:Composite_trapezoidal_rule_illustration.png

            Examples
            --------
            >>> np.trapz([1,2,3])
            4.0
            >>> np.trapz([1,2,3], x=[4,6,8])
            8.0
            >>> np.trapz([1,2,3], dx=2)
            8.0
            >>> a = np.arange(6).reshape(2, 3)
            >>> a
            array([[0, 1, 2],
                   [3, 4, 5]])
            >>> np.trapz(a, axis=0)
            array([ 1.5,  2.5,  3.5])
            >>> np.trapz(a, axis=1)
            array([ 2.,  8.])

    DATA
        __all__ = ['BDF', 'DenseOutput', 'IntegrationWarning', 'LSODA', 'OdeSo...
        absolute_import = _Feature((2, 5, 0, 'alpha', 1), (3, 0, 0, 'alpha', 0...
        division = _Feature((2, 2, 0, 'alpha', 2), (3, 0, 0, 'alpha', 0), 8192...
        print_function = _Feature((2, 6, 0, 'alpha', 2), (3, 0, 0, 'alpha', 0)...

    FILE
        /usr/lib64/python3.6/site-packages/scipy/integrate/__init__.py

quad is the basic integrator for a general (not sampled) function.  It uses a general-interface from the Fortran package QUADPACK (QAGS or QAGI).  It will return the integral in an interval and an estimate of the error in the approximation

```python
def f(x):
    return np.sin(x)**2
```

```python
I, err = integrate.quad(f, 0.0, 2.0*np.pi, epsabs=1.e-14)
print(I)
print(err)
```

    3.141592653589793
    2.3058791671639882e-09

```python
help(integrate.quad)
```

    Help on function quad in module scipy.integrate.quadpack:

    quad(func, a, b, args=(), full_output=0, epsabs=1.49e-08, epsrel=1.49e-08, 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.
        epsrel : float or int, optional
            Relative error tolerance.
        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.
        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.  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

sometimes our integrand function takes optional arguments

```python
def g(x, A, sigma):
    return A*np.exp(-x**2/sigma**2)
```

```python
I, err = integrate.quad(g, -1.0, 1.0, args=(1.0, 2.0))
print(I, err)
```

    1.8451240256511698 2.0484991765669867e-14

numpy defines the inf quantity which can be used in the integration limits.  We can integrate a Gaussian (we know the answer is sqrt(pi)

Note: behind the scenes, what the integration function does is do a variable transform like: $t = 1/x$.  This works when one limit is $\infty$, giving

$$\int_a^b f(x) dx = \int_{1/b}^{1/a} \frac{1}{t^2} f\left (\frac{1}{t}\right) dt$$

```python
I, err = integrate.quad(g, -np.inf, np.inf, args=(1.0, 1.0))
print(I, err)
```

    1.7724538509055159 1.4202636780944923e-08

### Multidimensional integrals

multidimensional integration can be done with successive calls to quad(), but there are wrappers that help

Let's compute

$$I = \int_{y=0}^{1/2} \int_{x=0}^{1-2y} xy dxdy = \frac{1}{96}$$

(this example comes from the SciPy tutorial)

Notice that the limits of integration in x depend on y.

Note the form of the function:

```
dblquad(f, a, b, ylo, yhi)
```
where `f` = `f(y, x)` -- the y argument is first

The integral will be from: $x = [a,b]$, and $y$ = `ylo(x)`, $y$ = `yhi(x)`

```python
def integrand(x,y):
    return x*y

def x_lower_lim(y):
    return 0

def x_upper_lim(y):
    return 1-2*y

# we change the definitions of x and y in this call
I, err = integrate.dblquad(integrand, 0.0, 0.5, x_lower_lim, x_upper_lim)
print(I, 1.0/I)

```

    0.010416666666666668 95.99999999999999

If you remember the python lambda functions (one expression functions), you can do this more compactly:

```python
I, err = integrate.dblquad(lambda x, y: x*y, 0.0, 0.5, lambda y: 0, lambda y: 1-2*y)
print(I)
```

    0.010416666666666668

### integration of a sampled function

here we integrate a function that is defined only at a sequece of points.  Recall that Simpson's rule will use piecewise parabola data.  Let's compute

$$I = \int_0^{2\pi} f(x_i) dx$$

with $x_i = 0, \ldots, 2\pi$ defined at $N$ points

```python
N = 17
x = np.linspace(0.0, 2.0*np.pi, N, endpoint=True)
y = np.sin(x)**2

I = integrate.simps(y, x)
print(I)
```

    3.14159265359

Romberg integration is specific to equally-spaced samples, where $N = 2^k + 1$  and can be more converge faster (it uses extrapolation of coarser integration results to achieve higher accuracy)

```python
N = 17
x = np.linspace(0.0, 2.0*np.pi, N, endpoint=True)
y = np.sin(x)**2

I = integrate.romb(y, dx=x[1]-x[0])
print(I)
```

    3.14306583533

# Interpolation

Interpolation fills in the gaps between a discrete number of points by making an assumption about the behavior of the functional form of the data.

Many different types of interpolation exist
* some ensure no new extrema are introduced
* some conserve the quantity being interpolated
* some match derivative at end points

Pathologies exist -- it is not always best to use a high-order polynomial to pass through all of the points in your dataset.

the `interp1d()` function allows for a variety of 1-d interpolation methods.  It returns an object that acts as a function, which can be evaluated at any point.

```python
import scipy.interpolate as interpolate
```

```python
help (interpolate.interp1d)
```

    Help on class interp1d in module scipy.interpolate.interpolate:

    class interp1d(scipy.interpolate.polyint._Interpolator1D)
     |  Interpolate a 1-D function.
     |
     |  `x` and `y` are arrays of values used to approximate some function f:
     |  ``y = f(x)``.  This class returns a function whose call method uses
     |  interpolation to find the value of new points.
     |
     |  Note that calling `interp1d` with NaNs present in input values results in
     |  undefined behaviour.
     |
     |  Parameters
     |  ----------
     |  x : (N,) array_like
     |      A 1-D array of real values.
     |  y : (...,N,...) array_like
     |      A N-D array of real values. The length of `y` along the interpolation
     |      axis must be equal to the length of `x`.
     |  kind : str or int, optional
     |      Specifies the kind of interpolation as a string
     |      ('linear', 'nearest', 'zero', 'slinear', 'quadratic', 'cubic'
     |      where 'zero', 'slinear', 'quadratic' and 'cubic' refer to a spline
     |      interpolation of zeroth, first, second or third order) or as an
     |      integer specifying the order of the spline interpolator to use.
     |      Default is 'linear'.
     |  axis : int, optional
     |      Specifies the axis of `y` along which to interpolate.
     |      Interpolation defaults to the last axis of `y`.
     |  copy : bool, optional
     |      If True, the class makes internal copies of x and y.
     |      If False, references to `x` and `y` are used. The default is to copy.
     |  bounds_error : bool, optional
     |      If True, a ValueError is raised any time interpolation is attempted on
     |      a value outside of the range of x (where extrapolation is
     |      necessary). If False, out of bounds values are assigned `fill_value`.
     |      By default, an error is raised unless `fill_value="extrapolate"`.
     |  fill_value : array-like or (array-like, array_like) or "extrapolate", optional
     |      - if a ndarray (or float), this value will be used to fill in for
     |        requested points outside of the data range. If not provided, then
     |        the default is NaN. The array-like must broadcast properly to the
     |        dimensions of the non-interpolation axes.
     |      - If a two-element tuple, then the first element is used as a
     |        fill value for ``x_new < x[0]`` and the second element is used for
     |        ``x_new > x[-1]``. Anything that is not a 2-element tuple (e.g.,
     |        list or ndarray, regardless of shape) is taken to be a single
     |        array-like argument meant to be used for both bounds as
     |        ``below, above = fill_value, fill_value``.
     |
     |        .. versionadded:: 0.17.0
     |      - If "extrapolate", then points outside the data range will be
     |        extrapolated.
     |
     |        .. versionadded:: 0.17.0
     |  assume_sorted : bool, optional
     |      If False, values of `x` can be in any order and they are sorted first.
     |      If True, `x` has to be an array of monotonically increasing values.
     |
     |  Methods
     |  -------
     |  __call__
     |
     |  See Also
     |  --------
     |  splrep, splev
     |      Spline interpolation/smoothing based on FITPACK.
     |  UnivariateSpline : An object-oriented wrapper of the FITPACK routines.
     |  interp2d : 2-D interpolation
     |
     |  Examples
     |  --------
     |  >>> import matplotlib.pyplot as plt
     |  >>> from scipy import interpolate
     |  >>> x = np.arange(0, 10)
     |  >>> y = np.exp(-x/3.0)
     |  >>> f = interpolate.interp1d(x, y)
     |
     |  >>> xnew = np.arange(0, 9, 0.1)
     |  >>> ynew = f(xnew)   # use interpolation function returned by `interp1d`
     |  >>> plt.plot(x, y, 'o', xnew, ynew, '-')
     |  >>> plt.show()
     |
     |  Method resolution order:
     |      interp1d
     |      scipy.interpolate.polyint._Interpolator1D
     |      builtins.object
     |
     |  Methods defined here:
     |
     |  __init__(self, x, y, kind='linear', axis=-1, copy=True, bounds_error=None, fill_value=nan, assume_sorted=False)
     |      Initialize a 1D linear interpolation class.
     |
     |  ----------------------------------------------------------------------
     |  Data descriptors defined here:
     |
     |  __dict__
     |      dictionary for instance variables (if defined)
     |
     |  __weakref__
     |      list of weak references to the object (if defined)
     |
     |  fill_value
     |
     |  ----------------------------------------------------------------------
     |  Methods inherited from scipy.interpolate.polyint._Interpolator1D:
     |
     |  __call__(self, x)
     |      Evaluate the interpolant
     |
     |      Parameters
     |      ----------
     |      x : array_like
     |          Points to evaluate the interpolant at.
     |
     |      Returns
     |      -------
     |      y : array_like
     |          Interpolated values. Shape is determined by replacing
     |          the interpolation axis in the original array with the shape of x.
     |
     |  ----------------------------------------------------------------------
     |  Data descriptors inherited from scipy.interpolate.polyint._Interpolator1D:
     |
     |  dtype

```python
def f_exact(x):
    return np.sin(x)*x

N = 10
x = np.linspace(0, 20, N)

y = f_exact(x)

fInterp = interpolate.interp1d(x, y, kind=3)

# use finer points when we plot
xplot = np.linspace(0, 20, 10*N)

plt.plot(x, y, "ro", label="known points")
plt.plot(xplot, f_exact(xplot), "b:", label="exact function")
plt.plot(xplot, fInterp(xplot), "r-", label="interpolant")

plt.legend(frameon=False, loc="best")
```

### Multi-d interpolation

Here's an example of mult-d interpolation from the official tutorial.

First we define the "answer" -- this is the true function that we will sample at a number of points and then try to use interpolation to recover

```python
def func(x, y):
    return x*(1-x)*np.cos(4*np.pi*x) * np.sin(4*np.pi*y**2)**2
```

here will use mgrid to create the grid of (x,y) where we know func exactly -- this will be for plotting.  Note the fun trick here, this is not really a function, but rather something that can magically look like an array, and we index it with the start:stop:stride.  If we set stride to an imaginary number, then it is interpreted as the number of points to put between the start and stop

```python
grid_x, grid_y = np.mgrid[0:1:100j, 0:1:200j]
```

```python
print(grid_x.shape)
print(grid_y.shape)
```

    (100, 200)
    (100, 200)

here's what the exact function looks like -- note that our function is defined in x,y, but imshow is meant for plotting an array, so the first index is the row.  We take the transpose when plotting

```python
plt.imshow(func(grid_x, grid_y).T, extent=(0,1,0,1), origin="lower")
```

now we'll define 1000 random points where we'll sample the function

```python
points = np.random.rand(1000, 2)
values = func(points[:,0], points[:,1])
```

Here's what those points look like:

```python
plt.scatter(points[:,0], points[:,1], s=1)
plt.xlim(0,1)
plt.ylim(0,1)
```

The interpolate.griddata() function provides many ways to interpolate a collection of points into a uniform grid.  There are many different interpolation methods within this function

```python
grid_z0 = interpolate.griddata(points, values, (grid_x, grid_y), method='nearest')
plt.imshow(grid_z0.T, extent=(0,1,0,1), origin="lower")
```

```python
grid_z0 = interpolate.griddata(points, values, (grid_x, grid_y), method='linear')
plt.imshow(grid_z0.T, extent=(0,1,0,1), origin="lower")
```

```python
grid_z0 = interpolate.griddata(points, values, (grid_x, grid_y), method='cubic')
plt.imshow(grid_z0.T, extent=(0,1,0,1), origin="lower")
```

# Root Finding

Often we need to find a value of a variable that zeros a function -- this is _root finding_.  Sometimes, this is a multidimensional problem.

The `brentq()` routine offers a very robust method for find roots from a scalar function.  You do need to provide an interval that bounds the root.

$f(x) = \frac{x e^x}{e^x - 1} - 5$

```python
import scipy.optimize as optimize

def f(x):
    # this is the non-linear equation that comes up in deriving Wien's law for radiation
    return (x*np.exp(x)/(np.exp(x) - 1.0) - 5.0)

root, r = optimize.brentq(f, 0.1, 10.0, full_output=True)

print(root)
print(r.converged)
```

    4.965114231744287
    True

```python
x = np.linspace(0.1, 10.0, 1000)
plt.plot(x, f(x))
plt.plot(np.array([root]), np.array([f(root)]), 'ro')
```

# ODEs

Many methods exist for integrating ordinary differential equations.  Most will want you to write your ODEs as a system of first order equations.

This system of ODEs is the Lorenz system:

$$\frac{dx}{dt} = \sigma (y - x)$$
$$\frac{dy}{dt} = rx - y - xz$$
$$\frac{dz}{dt} = xy - bz$$

the steady states of this system correspond to:

$${\bf f}({\bf x}) =
\left (
\sigma (y -x),
rx - y -xz,
xy - bz
\right )^\intercal
= 0$$

```python
# system parameters
sigma = 10.0
b = 8./3.
r = 28.0

def rhs(t, x):
    xdot = sigma*(x[1] - x[0])
    ydot = r*x[0] - x[1] - x[0]*x[2]
    zdot = x[0]*x[1] - b*x[2]

    return np.array([xdot, ydot, zdot])

def jac(t, x):

    return np.array(
        [ [-sigma, sigma, 0.0],
          [r - x[2], -1.0, -x[0]],
          [x[1], x[0], -b] ])

def f(x):
    return rhs(0.,x), jac(0.,x)
```

SciPy >= 1.0.0 has a uniform interface to the different ODE solvers, `solve_ivp()` -- we use that here.  Note, some (but not all) solvers provide a way to get the solution data at intermediate times (called dense output).

```python
def ode_integrate(X0, dt, tmax):
    """ integrate using the VODE method, storing the solution each dt """

    r = integrate.solve_ivp(rhs, (0.0, tmax), X0,
                            method="RK45", dense_output=True)

    # get the solution at intermediate times
    ts = np.arange(0.0, tmax, dt)

    Xs = r.sol(ts)
    return ts, Xs
```

```python
t, X = ode_integrate([1.0, 1.0, 20.0], 0.02, 30)
from mpl_toolkits.mplot3d import Axes3D

fig = plt.figure()
ax = fig.gca(projection='3d')
ax.plot(X[0,:], X[1,:], X[2,:])
fig.set_size_inches(8.0,6.0)
```

### Multi-variate root find

we can find the steady points in this system by doing a multi-variate root find on the RHS vector

```python
sol1 = optimize.root(f, [1., 1., 1.], jac=True)
print(sol1.x)

sol2 = optimize.root(f, [10., 10., 10.], jac=True)
print(sol2.x)

sol3 = optimize.root(f, [-10., -10., -10.], jac=True)
print(sol3.x)
```

    [ 0.  0.  0.]
    [  8.48528137   8.48528137  27.        ]
    [ -8.48528137  -8.48528137  27.        ]

```python
fig = plt.figure()
ax = fig.gca(projection='3d')
ax.plot(X[0,:], X[1,:], X[2,:])

ax.scatter(sol1.x[0], sol1.x[1], sol1.x[2], marker="x", color="r")
ax.scatter(sol2.x[0], sol2.x[1], sol2.x[2], marker="x", color="r")
ax.scatter(sol3.x[0], sol3.x[1], sol3.x[2], marker="x", color="r")

ax.set_xlabel("x")
ax.set_ylabel("y")
ax.set_zlabel("z")
```

### Stiff system of ODEs

A stiff system of ODEs is one where there are multiple disparate timescales for change and we need to respect all of them to get an accurate solution

Here is an example from Chemical Kinetics (see, ex. Byrne & Hindmarsh 1986, or the VODE source code)

\begin{equation}
\frac{d}{dt} \left (
   \begin{array}{c} y_1 \newline y_2 \newline y_3 \end{array}
  \right ) =
%
\left (
 \begin{array}{rrr}
  -0.04 y_1 & + 10^4 y_2 y_3  & \newline
   0.04 y_1 & - 10^4 y_2 y_3 & -3\times 10^7 y_2^2 \newline
   &  & 3\times 10^7 y_2^2
\end{array}
\right )
\end{equation}

\begin{equation}
{\bf J} = \left (
\begin{array}{ccc}
  -0.04 & 10^4 y_3 & 10^4 y_2 \newline
   0.04 & -10^4 y_3 - 6\times 10^7 y_2 & -10^4 y_2 \newline
   0 & 6\times 10^7 y_2 & 0
\end{array}
\right )
\end{equation}

start with $y_1(0) = 1, y_2(0) = y_3(0) = 0$.  Long term behavior is $y_1, y_2 \rightarrow 0; y_3 \rightarrow 1$

```python
def rhs(t, Y):
    """ RHS of the system -- using 0-based indexing """
    y1 = Y[0]
    y2 = Y[1]
    y3 = Y[2]

    dy1dt = -0.04*y1 + 1.e4*y2*y3
    dy2dt =  0.04*y1 - 1.e4*y2*y3 - 3.e7*y2**2
    dy3dt =                         3.e7*y2**2

    return np.array([dy1dt, dy2dt, dy3dt])

def jac(t, Y):
    """ J_{i,j} = df_i/dy_j """

    y1 = Y[0]
    y2 = Y[1]
    y3 = Y[2]

    df1dy1 = -0.04
    df1dy2 = 1.e4*y3
    df1dy3 = 1.e4*y2

    df2dy1 = 0.04
    df2dy2 = -1.e4*y3 - 6.e7*y2
    df2dy3 = -1.e4*y2

    df3dy1 = 0.0
    df3dy2 = 6.e7*y2
    df3dy3 = 0.0

    return np.array([ [ df1dy1, df1dy2, df1dy3 ],
                      [ df2dy1, df2dy2, df2dy3 ],
                      [ df3dy1, df3dy2, df3dy3 ] ])
```

```python
def vode_integrate(Y0, tmax):
    """ integrate using the NDF method """

    r = integrate.solve_ivp(rhs, (0.0, tmax), Y0,
                            method="BDF", jac=jac, rtol=1.e-7, atol=1.e-10)

    # Note: this solver does not have a dens_output method, instead we
    # access the solution data where it was evaluated internally via
    # the return object

    return r.t, r.y
```

```python
Y0 = np.array([1.0, 0.0, 0.0])
tmax = 4.e7

ts, Ys = vode_integrate(Y0, tmax)

ax = plt.gca()
ax.set_xscale('log')
ax.set_yscale('log')

plt.plot(ts, Ys[0,:], label=r"$y_1$")
plt.plot(ts, Ys[1,:], label=r"$y_2$")
plt.plot(ts, Ys[2,:], label=r"$y_3$")

plt.legend(loc="best", frameon=False)
```

# Fitting

Fitting is used to match a model to experimental data.  E.g. we have N points of $(x_i, y_i)$ with associated errors, $\sigma_i$, and we want to find a simply function that best represents the data.

Usually this means that we will need to define a metric, often called the residual, for how well our function matches the data, and then minimize this residual.  Least-squares fitting is a popular formulation.

We want to fit our data to a function $Y(x, \{a_j\})$, where $a_j$ are model parameters we can adjust.  We want to find the optimal $a_j$ to minimize the distance of $Y$ from our data:
$$\Delta_i = Y(x_i, \{a_j\}) - y_i$$

Least-squares minimizes $\chi^2$:
$$\chi^2(\{a_j\}) = \sum_{i=1}^N \left ( \frac{\Delta_i}{\sigma_i} \right )^2$$

### general linear least squares

First we'll make some experimental data (a quadratic with random fashion).  We use the randn() function to provide Gaussian normalized errors.

```python
def y_experiment2(a1, a2, a3, sigma, x):
    """ return the experimental data in a quadratic + random fashion,
        with a1, a2, a3 the coefficients of the quadratic and sigma is
        the error.  This will be poorly matched to a linear fit for
        a3 != 0 """

    N = len(x)

    # randn gives samples from the "standard normal" distribution
    r = np.random.randn(N)

    y = a1 + a2*x + a3*x*x + sigma*r

    return y

N = 40
sigma = 5.0*np.ones(N)

x = np.linspace(0, 100.0, N)
y = y_experiment2(2.0, 1.50, -0.02, sigma, x)

plt.scatter(x,y)
plt.errorbar(x, y, yerr=sigma, fmt=None)
```

```python
def resid(avec, x, y, sigma):
    """ the residual function -- this is what will be minimized by the
        scipy.optimize.leastsq() routine.  avec is the parameters we
        are optimizing -- they are packed in here, so we unpack to
        begin.  (x, y) are the data points

        scipy.optimize.leastsq() minimizes:

           x = arg min(sum(func(y)**2,axis=0))
                    y

        so this should just be the distance from a point to the curve,
        and it will square it and sum over the points
        """

    a0, a1, a2 = avec

    return (y - (a0 + a1*x + a2*x**2))/sigma

# initial guesses
a0, a1, a2 = 1, 1, 1

afit, flag = optimize.leastsq(resid, [a0, a1, a2], args=(x, y, sigma))

print(afit)

plt.plot(x, afit[0] + afit[1]*x + afit[2]*x*x )
plt.scatter(x,y)
plt.errorbar(x, y, yerr=sigma, fmt=None)
```

$\chi^2$

```python
chisq = sum(np.power(resid(afit, x, y, sigma),2))
normalization = len(x)-len(afit)
print(chisq/normalization)
```

    0.811373197043

### a nonlinear example

our experiemental data -- an exponential

```python
a0 = 2.5
a1 = 2./3.
sigma = 5.0

a0_orig, a1_orig = a0, a1

x = np.linspace(0.0, 4.0, 25)
y = a0*np.exp(a1*x) + sigma*np.random.randn(len(x))

plt.scatter(x,y)
plt.errorbar(x, y, yerr=sigma, fmt=None, label="_nolegend_")
```

our function to minimize

```python
def resid(avec, x, y):
    """ the residual function -- this is what will be minimized by the
        scipy.optimize.leastsq() routine.  avec is the parameters we
        are optimizing -- they are packed in here, so we unpack to
        begin.  (x, y) are the data points

        scipy.optimize.leastsq() minimizes:

           x = arg min(sum(func(y)**2,axis=0))
                    y

        so this should just be the distance from a point to the curve,
        and it will square it and sum over the points
        """

    a0, a1 = avec

    # note: if we wanted to deal with error bars, we would weight each
    # residual accordingly
    return y - a0*np.exp(a1*x)
```

```python
a0, a1 = 1, 1
afit, flag = optimize.leastsq(resid, [a0, a1], args=(x, y))

print(flag)
print(afit)
```

    1
    [ 2.8409826   0.62230606]

```python
plt.plot(x, afit[0]*np.exp(afit[1]*x),
           label=r"$a_0 = $ %f; $a_1 = $ %f" % (afit[0], afit[1]))
plt.plot(x, a0_orig*np.exp(a1_orig*x), ":", label="original function")
plt.legend(numpoints=1, frameon=False)
plt.scatter(x,y, c="k")
plt.errorbar(x, y, yerr=sigma, fmt=None, label="_nolegend_")
```

# FFTs

Fourier transforms convert a physical-space (or time series) representation of a function into frequency space.  This provides an equivalent representation of the data with a new view.

The FFT and its inverse in NumPy use:
$$F_k = \sum_{n=0}^{N-1} f_n e^{-2\pi i nk/N}$$

$$f_n = \frac{1}{N} \sum_{k=0}^{N-1} F_k
   e^{2\pi i n k/N}$$

Both NumPy and SciPy have FFT routines that are similar.  However, the NumPy version returns the data in a more convenient form.

It's always best to start with something you understand -- let's do a simple sine wave.  Since our function is real, we can use the rfft routines in NumPy -- the understand that we are working with real data and they don't return the negative frequency components.

One important caveat -- FFTs assume that you are periodic.  If you include both endpoints of the domain in the points that comprise your sample then you will not match this assumption.  Here we use endpoint=False with linspace()

```python
def single_freq_sine(npts):

    # a pure sine with no phase shift will result in pure imaginary
    # signal
    f_0 = 0.2

    xmax = 10.0/f_0

    xx = np.linspace(0.0, xmax, npts, endpoint=False)

    f = np.sin(2.0*np.pi*f_0*xx)

    return xx, f
```

To make our life easier, we'll define a function that plots all the stages of the FFT process

```python
def plot_FFT(xx, f):

    npts = len(xx)

    # Forward transform: f(x) -> F(k)
    fk = np.fft.rfft(f)

    # Normalization -- the '2' here comes from the fact that we are
    # neglecting the negative portion of the frequency space, since
    # the FFT of a real function contains redundant information, so
    # we are only dealing with 1/2 of the frequency space.
    #
    # technically, we should only scale the 0 bin by N, since k=0 is
    # not duplicated -- we won't worry about that for these plots
    norm = 2.0/npts

    fk = fk*norm

    fk_r = fk.real
    fk_i = fk.imag

    # the fftfreq returns the postive and negative (and 0) frequencies
    # the newer versions of numpy (>=1.8) have an rfftfreq() function
    # that really does what we want -- we'll use that here.
    k = np.fft.rfftfreq(npts)

    # to make these dimensional, we need to divide by dx.  Note that
    # max(xx) is not the true length, since we didn't have a point
    # at the endpoint of the domain.
    kfreq = k*npts/(max(xx) + xx[1])

    # Inverse transform: F(k) -> f(x) -- without the normalization
    fkinv = np.fft.irfft(fk/norm)

    # plots
    plt.subplot(411)

    plt.plot(xx, f)
    plt.xlabel("x")
    plt.ylabel("f(x)")

    plt.subplot(412)

    plt.plot(kfreq, fk_r, label=r"Re($\mathcal{F}$)")
    plt.plot(kfreq, fk_i, ls=":", label=r"Im($\mathcal{F}$)")
    plt.xlabel(r"$\nu_k$")
    plt.ylabel("F(k)")

    plt.legend(fontsize="small", frameon=False)

    plt.subplot(413)

    plt.plot(kfreq, np.abs(fk))
    plt.xlabel(r"$\nu_k$")
    plt.ylabel(r"|F(k)|")

    plt.subplot(414)

    plt.plot(xx, fkinv.real)
    plt.xlabel(r"$\nu_k$")
    plt.ylabel(r"inverse F(k)")

    f = plt.gcf()

    f.set_size_inches(10,8)
    plt.tight_layout()

```

```python
npts = 128
xx, f = single_freq_sine(npts)
plot_FFT(xx, f)
```

A cosine is shifted in phase by pi/2

```python
def single_freq_cosine(npts):

    # a pure cosine with no phase shift will result in pure real
    # signal
    f_0 = 0.2

    xmax = 10.0/f_0

    xx = np.linspace(0.0, xmax, npts, endpoint=False)

    f = np.cos(2.0*np.pi*f_0*xx)

    return xx, f
```

```python
xx, f = single_freq_cosine(npts)
plot_FFT(xx, f)
```

Now let's look at a sine with a pi/4 phase shift

```python
def single_freq_sine_plus_shift(npts):

    # a pure sine with no phase shift will result in pure imaginary
    # signal
    f_0 = 0.2

    xmax = 10.0/f_0

    xx = np.linspace(0.0, xmax, npts, endpoint=False)

    f = np.sin(2.0*np.pi*f_0*xx + np.pi/4)

    return xx, f
```

```python
xx, f = single_freq_sine_plus_shift(npts)
plot_FFT(xx, f)
```

### A frequency filter

we'll setup a simple two-frequency sine wave and filter a component

```python
def two_freq_sine(npts):

    # a pure sine with no phase shift will result in pure imaginary
    # signal
    f_0 = 0.2
    f_1 = 0.5

    xmax = 10.0/f_0

    # we call with endpoint=False -- if we include the endpoint, then for
    # a periodic function, the first and last point are identical -- this
    # shows up as a signal in the FFT.
    xx = np.linspace(0.0, xmax, npts, endpoint=False)

    f = 0.5*(np.sin(2.0*np.pi*f_0*xx) + np.sin(2.0*np.pi*f_1*xx))

    return xx, f
```

```python
npts = 256

xx, f = two_freq_sine(npts)

plt.plot(xx, f)
```

we'll take the transform: f(x) -> F(k)

```python
# normalization factor: the 2 here comes from the fact that we neglect
# the negative portion of frequency space because our input function
# is real
norm = 2.0/npts
fk = norm*np.fft.rfft(f)

ofk_r = fk.real.copy()
ofk_i = fk.imag.copy()

# get the frequencies
k = np.fft.rfftfreq(len(xx))

# since we don't include the endpoint in xx, to normalize things, we need
# max(xx) + dx to get the true length of the domain
#
# This makes the frequencies essentially multiples of 1/dx
kfreq = k*npts/(max(xx) + xx[1])

plt.plot(kfreq, fk.real, label="real")
plt.plot(kfreq, fk.imag, ":", label="imaginary")
plt.legend(frameon=False)
```

Filter out the higher frequencies

```python
fk[kfreq > 0.4] = 0.0

# element 0 of fk is the DC component
fk_r = fk.real
fk_i = fk.imag

# Inverse transform: F(k) -> f(x)
fkinv = np.fft.irfft(fk/norm)

plt.plot(xx, fkinv.real)
```

# Linear Algebra

### general manipulations of matrices

you can use regular NumPy arrays or you can use a special matrix class that offers some short cuts

```python
a = np.array([[1.0, 2.0], [3.0, 4.0]])
```

```python
print(a)
print(a.transpose())
print(a.T)
```

    [[ 1.  2.]
     [ 3.  4.]]
    [[ 1.  3.]
     [ 2.  4.]]
    [[ 1.  3.]
     [ 2.  4.]]

```python
ainv = np.linalg.inv(a)
print(ainv)
```

    [[-2.   1. ]
     [ 1.5 -0.5]]

```python
print(np.dot(a, ainv))
```

    [[  1.00000000e+00   0.00000000e+00]
     [  8.88178420e-16   1.00000000e+00]]

the eye() function will generate an identity matrix (as will the identity())

```python
print(np.eye(2))
print(np.identity(2))
```

    [[ 1.  0.]
     [ 0.  1.]]
    [[ 1.  0.]
     [ 0.  1.]]

we can solve Ax = b

```python
b = np.array([5, 7])
x = np.linalg.solve(a, b)
print(x)
```

    [-3.  4.]

### The matrix class

```python
A = np.matrix('1.0 2.0; 3.0 4.0')
print(A)
print(A.T)
```

    [[ 1.  2.]
     [ 3.  4.]]
    [[ 1.  3.]
     [ 2.  4.]]

```python
X = np.matrix('5.0 7.0')
Y = X.T

print(A*Y)
```

    [[ 19.]
     [ 43.]]

```python
print(A.I*Y)
```

    [[-3.]
     [ 4.]]

### tridiagonal matrix solve

Here we'll solve the equation:

$$f^{\prime\prime} = g(x)$$

with $g(x) = sin(x)$, and the domain $x \in [0, 2\pi]$, with boundary conditions $f(0) = f(2\pi) = 0$.  The solution is simply $f(x) = -sin(x)$.

We'll use a grid of $N$ points with $x_0$ on the left boundary and $x_{N-1}$ on the right boundary.

We difference our equation as:

$$f_{i+1} - 2 f_i + f_{i-1} = \Delta x^2 g_i$$

We keep the boundary points fixed, so we only need to solve for the $N-2$ interior points.  Near the boundaries, our difference is:
$$f_2 - 2 f_1 = \Delta x^2 g_1$$

and

$$-2f_{N-1} + f_{N-2} = \Delta x^2 g_{N-1}$$.

We can write the system of equations for solving for the $N-2$ interior points as:

\begin{equation}
A = \left (
\begin{array}{ccccccc}
-2 &  1 &    &        &         &           &          \newline
1  & -2 &  1 &        &         &           &          \newline
   &  1 & -2 & 1      &         &           &          \newline
   &         & \ddots & \ddots & \ddots  &           &          \newline
   &         &        & \ddots & \ddots  & \ddots  &          \newline
   &         &        &        & 1       & -2 & 1  \newline
   &         &        &        &         & 1  & -2 \newline
\end{array}
\right )
\end{equation}

\begin{equation}
x = \left (
\begin{array}{c}
f_\mathrm{1} \\\
f_\mathrm{2} \\\
f_\mathrm{3} \\\
\vdots \\\
\vdots \\\
f_\mathrm{N-2} \\\
f_\mathrm{N-1} \\\
\end{array}
\right )
\end{equation}

\begin{equation}
b = \Delta x^2 \left (
\begin{array}{c}
g_\mathrm{1} \\\
g_\mathrm{2} \\\
g_\mathrm{3} \\\
\vdots \\\
\vdots \\\
g_\mathrm{N-2} \\\
g_\mathrm{N-1}\\\
\end{array}
\right )
\end{equation}

Then we just solve $A x = b$

```python
import scipy.linalg as linalg

# our grid -- including endpoints
N = 100
x = np.linspace(0.0, 2.0*np.pi, N, endpoint=True)
dx = x[1]-x[0]

# our source
g = np.sin(x)

# our matrix will be tridiagonal, with [1, -2, 1] on the diagonals
# we only solve for the N-2 interior points

# diagonal
d = -2*np.ones(N-2)

# upper -- note that the upper diagonal has 1 less element than the
# main diagonal.  The SciPy banded solver wants the matrix in the
# form:
#
# *    a01  a12  a23  a34  a45    <- upper diagonal
# a00  a11  a22  a33  a44  a55    <- diagonal
# a10  a21  a32  a43  a54   *     <- lower diagonal
#

u = np.ones(N-2)
u[0] = 0.0

# lower
l = np.ones(N-2)
l[N-3] = 0.0

# put the upper, diagonal, and lower parts together as a banded matrix
A = np.matrix([u,d,l])

# solve A sol = dx**2 g for the inner N-2 points
sol = linalg.solve_banded((1,1), A, dx**2*g[1:N-1])

plt.plot(x[1:N-1], sol)
```