Esempio n. 1
0
    def _compute_f_h(self, try_step_success):
        """Compute factor for h based on error norm."""
        if try_step_success and np.all(np.isfinite(self.error)):
            scale = self.atol + np.maximum(np.abs(self.y), np.abs(self.y_new)) * self.rtol
            err_norm = norm(self.error / scale) # error norm
            error_accepted = err_norm < 1
        else:
            err_norm = np.inf
            error_accepted = False

        # Find step size factor for next step
        if err_norm == 0.0:
            f_h = self.f_max
        else:
            f_h = np.max([self.f_min, np.min([self.f_max, self.f_safe * err_norm**self.err_ex])])
            
        return f_h, error_accepted, err_norm
Esempio n. 2
0
def h_start(df, a, b, y, yprime, morder, rtol, atol):
    """h_shart computes a starting step size to be used in solving initial
    value problems in ordinary differential equations.

    This method is developed by H.A. Watts and described in [1]_. This function
    is a Python translation of the Fortran source code [2]_. The two main
    modifications are:
        using the RMS norm from scipy.integrate
        allowing for complex valued input

    Parameters
    ----------
    df : callable
        Right-hand side of the system. The calling signature is fun(t, y).
        Here t is a scalar. The ndarray y has has shape (n,) and fun must
        return array_like with the same shape (n,).
    a : float
        This is the initial point of integration.
    b : float
        This is a value of the independent variable used to define the
        direction of integration. A reasonable choice is to set `b` to the
        first point at which a solution is desired. You can also use `b , if
        necessary, to restrict the length of the first integration step because
        the algorithm will not compute a starting step length which is bigger
        than abs(b-a), unless `b` has been chosen too close to `a`. (it is
        presumed that h_start has been called with `b` different from `a` on
        the machine being used.
    y : array_like, shape (n,)
        This is the vector of initial values of the n solution components at
        the initial point `a`.
    yprime : array_like, shape (n,)
        This is the vector of derivatives of the n solution components at the
        initial point `a`.  (defined by the differential equations in
        subroutine `df`)
    morder : int
        This is the order of the formula which will be used by the initial
        value method for taking the first integration step.
    rtol : float
        Relative tolereance used by the differential equation method.
    atol : float or array_like
        Absolute tolereance used by the differential equation method.

    Returns
    -------
    float
        An appropriate starting step size to be attempted by the differential
        equation method.

    References
    ----------
    .. [1] H.A. Watts, "Starting step size for an ODE solver", Journal of
           Computational and Applied Mathematics, Vol. 9, No. 2, 1983,
           pp. 177-191, ISSN 0377-0427.
           https://doi.org/10.1016/0377-0427(83)90040-7
    .. [2] Slatec Fortran code dstrt.f.
           https://www.netlib.org/slatec/src/
    """

    # needed to pass scipy unit test:
    if y.size == 0:
        return np.inf

    # compensate for modified call list
    neq = y.size
    spy = np.empty_like(y)
    pv = np.empty_like(y)
    etol = atol + rtol * np.abs(y)

    # `small` is a small positive machine dependent constant which is used for
    # protecting against computations with numbers which are too small relative
    # to the precision of floating point arithmetic. `small` should be set to
    # (approximately) the smallest positive DOUBLE PRECISION number such that
    # (1. + small) > 1.  on the machine being used. The quantity small**(3/8)
    # is used in computing increments of variables for approximating
    # derivatives by differences.  Also the algorithm will not compute a
    # starting step length which is smaller than 100*small*ABS(A).
    # `big` is a large positive machine dependent constant which is used for
    # preventing machine overflows. A reasonable choice is to set big to
    # (approximately) the square root of the largest DOUBLE PRECISION number
    # which can be held in the machine.
    big = sqrt(np.finfo(y.dtype).max)
    small = np.nextafter(np.finfo(y.dtype).epsneg, 1.0)

    # following dhstrt.f from here
    dx = b - a
    absdx = abs(dx)
    relper = small**0.375

    # compute an approximate bound (dfdxb) on the partial derivative of the
    # equation with respect to the independent variable.  protect against an
    # overflow.  also compute a bound (fbnd) on the first derivative locally.
    da = copysign(max(min(relper * abs(a), absdx), 100.0 * small * abs(a)), dx)
    da = da or relper * dx
    sf = df(a + da, y)  # evaluate
    yp = sf - yprime
    delf = norm(yp)
    dfdxb = big
    if delf < big * abs(da):
        dfdxb = delf / abs(da)
    fbnd = norm(sf)

    # compute an estimate (dfdub) of the local lipschitz constant for the
    # system of differential equations. this also represents an estimate of the
    # norm of the jacobian locally.  three iterations (two when neq=1) are used
    # to estimate the lipschitz constant by numerical differences.  the first
    # perturbation vector is based on the initial derivatives and direction of
    # integration.  the second perturbation vector is formed using another
    # evaluation of the differential equation.  the third perturbation vector
    # is formed using perturbations based only on the initial values.
    # components that are zero are always changed to non-zero values (except
    # on the first iteration).  when information is available, care is taken to
    # ensure that components of the perturbation vector have signs which are
    # consistent with the slopes of local solution curves.  also choose the
    # largest bound (fbnd) for the first derivative.

    # perturbation vector size is held constant for all iterations.  compute
    # this change from the size of the vector of initial values.
    dely = relper * norm(y)
    dely = dely or relper
    dely = copysign(dely, dx)
    delf = norm(yprime)
    fbnd = max(fbnd, delf)

    if delf:
        # use initial derivatives for first perturbation
        spy[:] = yprime
        yp[:] = yprime
    else:
        # cannot have a null perturbation vector
        spy[:] = 0.0
        yp[:] = 1.0
        delf = norm(yp)

    dfdub = 0.0
    lk = min(neq + 1, 3)
    for k in range(1, lk + 1):

        # define perturbed vector of initial values
        pv[:] = y + dely / delf * yp

        if k == 2:
            # use a shifted value of the independent variable in computing
            # one estimate
            yp[:] = df(a + da, pv)  # evaluate
            pv[:] = yp - sf

        else:
            # evaluate derivatives associated with perturbed vector and
            # compute corresponding differences
            yp[:] = df(a, pv)  # evaluate
            pv[:] = yp - yprime

        # choose largest bounds on the first derivative and a local lipschitz
        # constant
        fbnd = max(fbnd, norm(yp))
        delf = norm(pv)
        if delf >= big * abs(dely):
            # protect against an overflow
            dfdub = big
            break
        dfdub = max(dfdub, delf / abs(dely))

        if k == lk:
            break

        # choose next perturbation vector
        delf = delf or 1.0
        if k == 2:
            dy = y.copy()  # vec
            dy[:] = np.where(dy, dy, dely / relper)
        else:
            dy = pv.copy()  # abs removed (complex)
            dy[:] = np.where(dy, dy, delf)
        spy[:] = np.where(spy, spy, yp)

        # use correct direction if possible.
        yp[:] = np.where(spy, np.copysign(dy.real, spy.real), dy.real)
        if np.issubdtype(y.dtype, np.complexfloating):
            yp[:] += 1j * np.where(spy, np.copysign(dy.imag, spy.imag),
                                   dy.imag)
        delf = norm(yp)

    # compute a bound (ydpb) on the norm of the second derivative
    ydpb = dfdxb + dfdub * fbnd

    # define the tolerance parameter upon which the starting step size is to be
    # based.  a value in the middle of the error tolerance range is selected.
    tolexp = np.log10(etol)
    tolsum = tolexp.sum()
    tolmin = min(tolexp.min(), big)
    tolp = 10.0**(0.5 * (tolsum / neq + tolmin) / (morder + 1))

    # compute a starting step size based on the above first and second
    # derivative information

    # restrict the step length to be not bigger than abs(b-a).
    # (unless b is too close to a)
    h = absdx
    if ydpb == 0.0 and fbnd == 0.0:
        # both first derivative term (fbnd) and second derivative term (ydpb)
        # are zero
        if tolp < 1.0:
            h = absdx * tolp
    elif ydpb == 0.0:
        #  only second derivative term (ydpb) is zero
        if tolp < fbnd * absdx:
            h = tolp / fbnd
    else:
        # second derivative term (ydpb) is non-zero
        srydpb = sqrt(0.5 * ydpb)
        if tolp < srydpb * absdx:
            h = tolp / srydpb

    # further restrict the step length to be not bigger than  1/dfdub
    if dfdub:  # `if` added (div 0)
        h = min(h, 1.0 / dfdub)

    # finally, restrict the step length to be not smaller than
    # 100*small*abs(a).  however, if a=0. and the computed h underflowed to
    # zero, the algorithm returns small*abs(b) for the step length.
    h = max(h, 100.0 * small * abs(a))
    h = h or small * abs(b)

    # now set direction of integration
    h = copysign(h, dx)
    return h
Esempio n. 3
0
 def _estimate_error_norm(self, K, h, scale):
     return norm(self._estimate_error(K, h) / scale)
Esempio n. 4
0
    def __init__(self, fun, t0, y0, t_bound, max_step=np.inf, rtol=1e-3,
                 atol=1e-6, vectorized=False, first_step=None, k_max=12,
                 **extraneous):
        if not (isinstance(k_max, int) and k_max > 0 and k_max < 13):
            raise ValueError("`k_max` should be an integer between 1 and 12.")
        warn_extraneous(extraneous)
        super(SWAG, self).__init__(
            fun, t0, y0, t_bound, vectorized, support_complex=True)
        self.max_step = validate_max_step(max_step)
        self.rtol, self.atol = validate_tol(rtol, atol, self.y)

        # starting step size
        self.yp = self.fun(self.t, self.y)                 # initial evaluation
        if first_step is None:
            b = self.t + copysign(min(abs(self.t_bound - self.t),
                                      self.max_step), self.direction)
            self.h = h_start(self.fun, self.t, b, self.y, self.yp,
                             1, self.rtol, self.atol)
        else:
            h_abs = validate_first_step(first_step, t0, t_bound)
            self.h = copysign(h_abs, self.direction)

        # constants
        small = np.nextafter(np.finfo(self.y.dtype).epsneg, 1)
        self.twou = 2.0 * small
        self.fouru = 4.0 * small
        self.two = (2.0, 4.0, 8.0, 16.0, 32.0, 64.0, 128.0, 256.0, 512.0,
                    1024.0, 2048.0, 4096.0, 8192.0)
        self.gstr = (0.5, 0.0833, 0.0417, 0.0264, 0.0188, 0.0143, 0.0114,
                     0.00936, 0.00789, 0.00679, 0.00592, 0.00524, 0.00468)
        iq = np.arange(1, k_max + 2)
        self.iqq = 1.0 / (iq * (iq + 1))                                # added
        self.k_max = k_max                                              # added
        self.eps = 1.0                                       # tolerances in wt
        self.p5eps = 0.5                                     # tolerances in wt

        # allocate arrays
        self.phi = np.empty((self.n, k_max + 2), self.y.dtype, 'F')
        self.psi = np.empty(k_max)
        self.alpha = np.empty(k_max)
        self.beta = np.empty(k_max)
        self.sig = np.empty(k_max + 1)
        self.v = np.empty(k_max)
        self.w = np.empty(k_max)
        self.g = np.empty(k_max + 1)
        self.gi = np.empty(k_max - 1)
        self.iv = np.zeros(max(0, k_max - 2), np.short)

        # Tolerances are dealt with like in scipy: wt is like scipy's scale
        # and will be update each step.  This is only the initial value:
        self.wt = self.atol + self.rtol * 0.5*(
            np.abs(self.y) + np.abs(self.y - self.h*self.yp))

        # initialization
        # from  *** block 0 ***  of dsteps.f, under IF START:
        _round = 0.0
        if self.y.size:                            # to pass scipy's unit tests
            _round = self.twou * norm(self.y / self.wt)
        if self.p5eps < 100.0 * _round:
            # The compensated summation of the original code that would be
            # executed if nornd == False has been removed.  Instead, this
            # warning is given to the user.
            warn("Numerical rounding may limit the accuracy "
                 "at this tolerance.")
        self.phi[:, 0] = self.yp
        self.phi[:, 1] = 0.0
        self.sig[0] = 1.0
        self.g[0] = 1.0
        self.g[1] = 0.5
        self.hold = 0.0
        self.k = 1
        self.kold = 0
        self.kprev = 0
        self.phase1 = True
        self.ivc = 0
        self.kgi = 0
        self.ns = 0

        # from ddes.f, for stiffness detection
        self.kle4 = 0
Esempio n. 5
0
    def _step_impl(self):

        # current state
        x = self.t
        y = self.y.copy()
        self.y_old = self.y                           # added, for dense output

        # load variables (hold != self.step_size, rounding matters)
        (hold, h, wt, k, kold, phi, yp, psi, alpha, beta, sig, v, w, g,
         phase1, ns, kprev, ivc, iv, kgi, gi, gstr, iqq, eps, p5eps) = (
          self.hold, self.h, self.wt, self.k, self.kold, self.phi, self.yp,
          self.psi, self.alpha, self.beta, self.sig, self.v, self.w, self.g,
          self.phase1, self.ns, self.kprev, self.ivc, self.iv, self.kgi,
          self.gi, self.gstr, self.iqq, self.eps, self.p5eps)

        # from *** ddes.f ***
        min_step = self.fouru * abs(x)                                  # added

        # stiffness detection
        if kold > 4:
            self.kle4 = 0
        else:
            self.kle4 += 1
            if self.kle4 > 50 and self.k_max > 4:
                # This warning is issued once, after 50 consequtive steps are
                # taken with order <= 4, while k_max > 4.
                warn("Your problem appears to be stiff (for this tolerance).")
                self.kle4 = 0

        # extrapolate if too close to t_bound
        d = self.t_bound - x
        if abs(d) <= min_step:
            self.kold = 0                                    # for dense output
            y[:] += d * yp
            # ouput
            self.t = self.t_bound
            self.y = y
            return True, None

        # don't allow to step over t_bound
        if self.direction * (h - d) > 0:
            h = d

        # limit h to max_step
        if self.max_step != np.inf:
            h = min(self.max_step, abs(h))
            h = copysign(h, self.direction)

        # (***first executable statement dsteps)
        if abs(h) < min_step:
            return False, self.TOO_SMALL_STEP

        # If error tolerance is too small, increase it to an acceptable value
        # or rather terminate the integration with an error
        _round = self.twou * norm(y / wt)
        if p5eps < _round:
            eps = 2.0 * _round * (1.0 + self.fouru)
            return False, ("tolerance too tight.\n"
                           f"suggested minimal increase factor: {eps}")

        ifail = 0

        # ***     begin block 1     ***
        # Compute coefficients of formulas for this step.  Avoid computing
        # those quantities not changed when step size is not changed.

        while True:
            kp1 = k + 1
            km1 = k - 1
            km2 = k - 2

            # ns is the number of dsteps taken with size h, including the
            # current one.  When k < ns, no coefficients change
            if h != hold:
                ns = 0
            if ns <= kold:
                ns += 1

            if k >= ns:
                # Compute those components of alpha(*), beta(*), psi(*), sig(*)
                # which are changed
                nsm1 = ns - 1                                           # added
                psi_old = psi[nsm1:km1].copy()                          # added
                psi[nsm1] = h * ns
                alpha[nsm1] = 1.0 / ns
                beta[nsm1] = 1.0
                sig[ns] = 1.0
                for i, temp2 in enumerate(psi_old, start=ns):
                    temp1 = h + temp2
                    alp = h / temp1                                     # added
                    psi[i] = temp1
                    alpha[i] = alp
                    beta[i] = beta[i-1] * psi[i-1] / temp2
                    sig[i+1] = (i + 1) * alp * sig[i]

                # compute coefficients g(*)

                # initialize v(*) and set w(*).
                if ns == 1:
                    w[:k] = v[:k] = iqq[:k]
                    ivc = kgi = 0
                    if k != 1:
                        kgi = 1
                        gi[0] = w[1]
                else:
                    # if order was raised, update diagonal part of v(*)
                    if k > kprev:
                        if ivc != 0:
                            ivc -= 1
                            jv = kp1 - iv[ivc]
                        else:
                            jv = 1
                            w[km1] = v[km1] = iqq[km1]
                            if k == 2:
                                kgi = 1
                                gi[0] = w[1]
                        for j, alp in enumerate(alpha[jv:nsm1], start=jv):
                            i = km1 - j
                            v[i] -= alp * v[i+1]
                            w[i] = v[i]
                        if k == ns and jv < nsm1:
                            kgi = nsm1
                            gi[kgi-1] = w[1]
                    # update v(*) and set w(*)
                    limit1 = kp1 - ns
                    v[:limit1] -= alpha[nsm1] * v[1:limit1+1]
                    w[:limit1+1] = v[:limit1+1]
                    g[ns] = w[0]
                    if limit1 != 1:
                        kgi = ns
                        gi[nsm1] = w[1]
                    if k < kold:
                        iv[ivc] = limit1 + 2
                        ivc += 1

                # compute the g(*) in the work vector w(*)
                kprev = k
                for i, alp in enumerate(alpha[ns:k], start=ns):
                    limit2 = k - i
                    w[:limit2] -= alp * w[1:limit2+1]
                    g[i+1] = w[0]

            # ***     end block 1     ***

            # ***     begin block 2     ***
            # Predict a solution p(*), evaluate derivatives using predicted
            # solution, estimate local error at order k and errors at orders
            # k, k-1, k-2 as if constant step size were used.

            # change phi to phi star
            phi[:, ns:k] *= beta[ns:k]

            # predict solution and differences
            phi[:, kp1] = phi[:, k]
            phi[:, k] = 0.0
            p = h * (phi[:, :k] @ g[:k]) + y
            for i in range(k, 0, -1):
                phi[:, i-1] += phi[:, i]
            xold = x
            x += h
            absh = abs(h)
            yp[:] = self.fun(x, p)                                   # evaluate

            # added update of wt:
            wt[:] = self.atol + self.rtol * 0.5*(np.abs(p) + np.abs(y))

            # estimate errors at orders k, k-1, k-2
            temp3 = 1.0 / wt
            temp4 = yp - phi[:, 0]
            if k > 2:
                erkm2 = absh * norm((phi[:, km2] + temp4) * temp3)
                erkm2 *= sig[km2] * gstr[km2-1]
            if k > 1:
                erkm1 = absh * norm((phi[:, km1] + temp4) * temp3)
                erkm1 *= sig[km1] * gstr[km2]
            erk = absh * norm(temp4 * temp3)
            err = erk * (g[km1] - g[k])
            erk *= sig[k] * gstr[km1]

            # test if order should be lowered
            knew = k
            if k > 2 and max(erkm1, erkm2) < erk:
                knew = km1
            elif k == 2 and erkm1 < 0.5 * erk:
                knew = km1

            # test if step successful
            if err <= eps:
                # success
                break
            # else: failure

            # ***     end block 2     ***

            # ***     begin block 3     ***
            # The step is unsuccessful.  restore x, phi(*,*), psi(*). if third
            # consecutive failure, set order to one.  If step fails more than
            # three times, consider an optimal step size.  Double error
            # tolerance and return if estimated step size is too small for
            # machine precision.

            # restore x, phi(*,*) and psi(*)
            phase1 = False
            x = xold
            phi[:, :k] -= phi[:, 1:kp1]
            phi[:, :k] /= beta[:k]
            psi[:km1] = psi[1:k] - h

            # On third failure, set order to one.
            # Thereafter, use optimal step size.
            NFS[()] += 1
            ifail += 1
            temp2 = 0.5
            if ifail >= 4 and p5eps < 0.25 * erk:
                temp2 = sqrt(p5eps / erk)
            if ifail >= 3:
                knew = 1
            h *= temp2
            k = knew
            ns = 0
            if abs(h) < min_step:
                return False, self.TOO_SMALL_STEP

            # ***     end block 3     ***

        # end while loop

        # ***     begin block 4     ***
        # The step is successful.  Correct the predicted solution, evaluate the
        # derivatives using the corrected solution and update the differences.
        # Determine best order and step size for next step.

        kold = k
        hold = h

        # correct and evaluate
        y[:] = h * g[k] * (yp - phi[:, 0]) + p
        yp[:] = self.fun(x, y)                                       # evaluate
        # p does not need to store y_old for dense output anymore.

        # update differences for next step
        phi[:, k] = yp - phi[:, 0]
        phi[:, kp1] = phi[:, k] - phi[:, kp1]
        phi[:, :k] += phi[:, k, np.newaxis]

        # Estimate error at order k+1 unless:
        #   - in first phase when always raise order,
        #   - already decided to lower order,
        #   - step size not constant so estimate unreliable
        if knew == km1 or k == self.k_max:
            phase1 = False
        erkp1 = 0.0
        if phase1:
            # raise order
            k = kp1
            erk = erkp1
        elif knew == km1:
            # lower order, as already decided in block 2
            k = km1
            erk = erkm1
        elif k < ns:
            erkp1 = gstr[k] * absh * norm(phi[:, kp1] / wt)
            # Using estimated error at order k+1, determine appropriate order
            # for next step
            if k == 1:
                if erkp1 < 0.5 * erk and k < self.k_max:
                    # raise order
                    k = kp1
                    erk = erkp1
                # else: no order change
            elif erkm1 <= min(erk, erkp1):
                # lower order
                k = km1
                erk = erkm1
            elif not (erkp1 > erk or k == self.k_max):
                # Here erkp1 < erk < max(erkm1, erkm2) else order would
                # have been lowered in block 2.  Thus order is to be raised
                k = kp1
                erk = erkp1
            # else: no order change
        # else: no order change

        # With new order determine appropriate step size for next step
        if phase1 or p5eps >= erk * self.two[k]:
            hnew = h + h
        elif p5eps >= erk:
            # keep step size (double, or don't increase at all)
            hnew = h
        else:
            # calculate reduced step size
            r = (p5eps / erk) ** (1.0 / (k + 1))
            hnew = absh * max(0.5, min(0.9, r))
            hnew = copysign(max(hnew, min_step), h)
        h = hnew

        # ***     end block 4     ***

        # output
        self.t = x
        self.y = y

        # store the non-mutable variables for the next step:
        (self.h, self.hold, self.k, self.kold, self.phase1, self.ns,
         self.kprev, self.ivc, self.kgi) = (
            h, hold, k, kold, phase1, ns, kprev, ivc, kgi)
        return True, None
Esempio n. 6
0
    def _step_impl(self):
        from scipy.integrate._ivp.bdf import (change_D, solve_bdf_system,
                                              NEWTON_MAXITER, MIN_FACTOR,
                                              MAX_FACTOR, MAX_ORDER)
        from scipy.integrate._ivp.common import norm
        t = self.t
        D = self.D

        max_step = self.max_step
        min_step = 10 * np.abs(np.nextafter(t, self.direction * np.inf) - t)
        if self.h_abs > max_step:
            h_abs = max_step
            change_D(D, self.order, max_step / self.h_abs)
            self.n_equal_steps = 0
        elif self.h_abs < min_step:
            h_abs = min_step
            change_D(D, self.order, min_step / self.h_abs)
            self.n_equal_steps = 0
        else:
            h_abs = self.h_abs

        atol = self.atol
        rtol = self.rtol
        order = self.order

        alpha = self.alpha
        gamma = self.gamma
        error_const = self.error_const

        J = self.J
        LU = self.LU
        current_jac = self.jac is None

        step_accepted = False
        while not step_accepted:
            if h_abs < min_step:
                return False, self.TOO_SMALL_STEP

            h = h_abs * self.direction
            t_new = t + h

            if self.direction * (t_new - self.t_bound) > 0:
                t_new = self.t_bound
                change_D(D, order, np.abs(t_new - t) / h_abs)
                self.n_equal_steps = 0
                LU = None

            h = t_new - t
            h_abs = np.abs(h)

            y_predict = np.sum(D[:order + 1], axis=0)

            scale = atol + rtol * np.abs(y_predict)
            psi = np.dot(D[1:order + 1].T, gamma[1:order + 1]) / alpha[order]

            converged = False
            c = h / alpha[order]
            while not converged:
                if LU is None:
                    LU = self.lu(self.I - c * J)

                converged, n_iter, y_new, d = solve_bdf_system(
                    self.fun, t_new, y_predict, c, psi, LU, self.solve_lu,
                    scale, self.newton_tol)

                if not converged:
                    if current_jac:
                        break
                    J = self.jac(t_new, y_predict)
                    LU = None
                    current_jac = True

            if not converged:
                factor = 0.5
                h_abs *= factor
                change_D(D, order, factor)
                self.n_equal_steps = 0
                LU = None
                continue

            safety = round(0.9 * (2 * NEWTON_MAXITER + 1) /
                           (2 * NEWTON_MAXITER + n_iter),
                           ndigits=15)

            scale = atol + rtol * np.abs(y_new)
            error = error_const[order] * d
            error_norm = norm(error / scale)

            if error_norm > 1:
                factor = max(MIN_FACTOR,
                             safety * error_norm**(-1 / (order + 1)))
                h_abs *= factor
                change_D(D, order, factor)
                self.n_equal_steps = 0
                # As we didn't have problems with convergence, we don't
                # reset LU here.
            else:
                step_accepted = True

        self.n_equal_steps += 1

        self.t = t_new
        self.y = y_new

        self.h_abs = h_abs
        self.J = J
        self.LU = LU

        # Update differences. The principal relation here is
        # D^{j + 1} y_n = D^{j} y_n - D^{j} y_{n - 1}. Keep in mind that D
        # contained difference for previous interpolating polynomial and
        # d = D^{k + 1} y_n. Thus this elegant code follows.
        D[order + 2] = d - D[order + 1]
        D[order + 1] = d
        for i in reversed(range(order + 1)):
            D[i] += D[i + 1]

        if self.n_equal_steps < order + 1:
            return True, None

        if order > 1:
            error_m = error_const[order - 1] * D[order]
            error_m_norm = norm(error_m / scale)
        else:
            error_m_norm = np.inf

        if order < MAX_ORDER:
            error_p = error_const[order + 1] * D[order + 2]
            error_p_norm = norm(error_p / scale)
        else:
            error_p_norm = np.inf

        error_norms = np.array([error_m_norm, error_norm, error_p_norm])
        with np.errstate(divide='ignore'):
            factors = error_norms**(-1 / np.arange(order, order + 3))

        delta_order = np.argmax(factors) - 1
        order += delta_order
        self.order = order

        factor = min(MAX_FACTOR, safety * np.max(factors))

        # # This is the custom modification for PriNCe
        if round(self.h_abs * factor, ndigits=15) > self.max_step:
            if round(self.h_abs, ndigits=15) != self.max_step:
                change_D(D, order, max_step / self.h_abs)
                self.h_abs = self.max_step
                self.n_equal_steps = 0
                self.LU = None
            self.n_equal_steps = 0
            return True, None
        # custom modications end
        self.h_abs *= factor
        change_D(D, order, factor)
        self.n_equal_steps = 0
        self.LU = None

        return True, None
Esempio n. 7
0
    def _step_impl(self):
        t = self.t
        y = self.y
        f = self.f
        n = y.size

        max_step = self.max_step
        atol = self.atol
        rtol = self.rtol

        min_step = 1e-20  #10 * np.abs(np.nextafter(t, self.direction * np.inf) - t)
        if self.h_abs > max_step:
            h_abs = max_step
            h_abs_old = None
            error_norm_old = None
        elif self.h_abs < min_step:
            h_abs = min_step
            h_abs_old = None
            error_norm_old = None
        else:
            h_abs = self.h_abs
            h_abs_old = self.h_abs_old
            error_norm_old = self.error_norm_old

        J = self.J
        LU_real = self.LU_real
        LU_complex = self.LU_complex

        current_jac = self.current_jac
        jac = self.jac

        rejected = False
        step_accepted = False
        message = None
        while not step_accepted:
            if h_abs < min_step:
                return False, self.TOO_SMALL_STEP

            h = h_abs * self.direction
            t_new = t + h

            if self.direction * (t_new - self.t_bound) > 0:
                t_new = self.t_bound
                h = t_new - t  # may introduce numerical rounding errors
            h_abs = np.abs(h)

            if self.sol is None:
                Z0 = np.zeros((3, y.shape[0]))
            else:
                Z0 = self.sol(t + h * C).T - y

            scale = atol + np.abs(y) * rtol

            converged = False
            while not converged:
                if LU_real is None or LU_complex is None:
                    if self.mass_matrix is None:
                        LU_real = self.lu(MU_REAL / h * self.I - J)
                        LU_complex = self.lu(MU_COMPLEX / h * self.I - J)
                    else:
                        try:
                            LU_real = self.lu(MU_REAL / h * self.mass_matrix -
                                              J)
                            LU_complex = self.lu(MU_COMPLEX / h *
                                                 self.mass_matrix - J)
                        except ValueError as e:
                            # import pdb; pdb.set_trace()
                            return False, 'LU decomposition failed ({})'.format(
                                e)
                if BPRINT:
                    print('solving system at t={} with dt={}'.format(t, h))
                    U_matrix = np.triu(LU_real[0], k=0)
                    L_matrix = np.tril(LU_real[0], k=-1) + self.I
                    self.info['cond']['LU_real'].append(
                        np.linalg.cond(U_matrix * L_matrix))
                    U_matrix = np.triu(LU_complex[0], k=0)
                    L_matrix = np.tril(LU_complex[0], k=-1) + self.I
                    self.info['cond']['LU_complex'].append(
                        np.linalg.cond(U_matrix * L_matrix))
                    self.info['cond']['t'].append(t)
                    self.info['cond']['h'].append(h)
                    print('\tcond(LU_real)    = {:.3e}'.format(
                        self.info['cond']['LU_real'][-1]))
                    print('\tcond(LU_complex) = {:.3e}'.format(
                        self.info['cond']['LU_complex'][-1]))

                converged, n_iter, Z, rate = solve_collocation_system(
                    self.fun, t, y, h, Z0, scale, self.newton_tol, LU_real,
                    LU_complex, self.solve_lu, self.mass_matrix)

                if not converged:
                    if BPRINT:
                        print('no convergence at t={} with dt={}'.format(t, h))
                    if current_jac:  # we only allow one Jacobian computation per time step
                        if BPRINT:
                            print('  Jac had already been updated')
                        break

                    J = self.jac(t, y, f)
                    current_jac = True
                    LU_real = None
                    LU_complex = None

## End of the convergence loop
            if not converged:
                if BPRINT:
                    print('   --> dt will be reduced')
                h_abs *= 0.5
                LU_real = None
                LU_complex = None
                continue

            y_new = y + Z[-1]

            if self.constant_dt:
                step_accepted = True
                error_norm = 0.
            else:
                ZE = Z.T.dot(E) / h
                if self.mass_matrix is None:
                    error = self.solve_lu(LU_real, f + ZE)
                    error_norm = norm(error / scale)
                else:  # see Hairer II, chapter IV.8, page 127
                    error = self.solve_lu(LU_real,
                                          f + self.mass_matrix.dot(ZE))
                    if self.index_algebraic_vars is not None:
                        error[
                            self.
                            index_algebraic_vars] = 0.  # ideally error*(h**index)
                        error_norm = np.linalg.norm(
                            error / scale) / (n - self.nvars_algebraic)**0.5
                        # we exclude the algebraic components, as they would otherwise artificially lower the error norm
                        # error_norm = norm(error / scale)
                    else:
                        error_norm = norm(error / scale)

                scale = atol + np.maximum(np.abs(y), np.abs(y_new)) * rtol
                error_norm = norm(error / scale)
                safety = 0.9 * (2 * NEWTON_MAXITER + 1) / (2 * NEWTON_MAXITER +
                                                           n_iter)
                if BPRINT:
                    print('\t1st error estimate: {:.3e}'.format(error_norm))
                if rejected and error_norm > 1:
                    # if error_norm > 1:
                    if BPRINT:
                        print('\t rejected')
                    if self.mass_matrix is None:
                        error = self.solve_lu(LU_real,
                                              self.fun(t, y + error) + ZE)
                        error_norm = norm(error / scale)
                    else:
                        error = self.solve_lu(
                            LU_real,
                            self.fun(t, y + error) + self.mass_matrix.dot(ZE))
                        if self.index_algebraic_vars is not None:
                            error[
                                self.
                                index_algebraic_vars] = 0.  # ideally error*(h**index)
                            error_norm = np.linalg.norm(error / scale) / (
                                n - self.nvars_algebraic)**0.5
                            # we exclude the algebraic components, as they would otherwise artificially lower the error norm
                            # error_norm = norm(error / scale)
                        else:
                            error_norm = norm(error / scale)

                    if BPRINT:
                        print(
                            '\t2nd error estimate: {:.3e}'.format(error_norm))
                if error_norm > 1:
                    if BPRINT and y_new.size < 10:
                        print('\terror=', error / scale)
                    factor = predict_factor(h_abs, h_abs_old, error_norm,
                                            error_norm_old)
                    h_abs *= max(MIN_FACTOR, safety * factor)
                    LU_real = None
                    LU_complex = None
                    rejected = True
                else:
                    if BPRINT:
                        print('\terror estimate is small enough')
                    step_accepted = True

## Step is converged and accepted
        recompute_jac = jac is not None and n_iter > 2 and rate > 1e-3

        if self.constant_dt:
            factor = self.max_step / h_abs  # return to the maximum value
        else:
            factor = predict_factor(h_abs, h_abs_old, error_norm,
                                    error_norm_old)
            factor = min(MAX_FACTOR, safety * factor)

        if not recompute_jac and factor < 1.2:
            factor = 1
        else:
            LU_real = None
            LU_complex = None

        f_new = self.fun(t_new, y_new)
        if recompute_jac:
            J = jac(t_new, y_new, f_new)
            current_jac = True
        elif jac is not None:
            current_jac = False

        self.h_abs_old = self.h_abs
        self.error_norm_old = error_norm

        self.h_abs = h_abs * factor

        self.y_old = y

        self.t = t_new
        self.y = y_new
        self.f = f_new

        self.Z = Z

        self.LU_real = LU_real
        self.LU_complex = LU_complex
        self.current_jac = current_jac
        self.J = J

        self.t_old = t
        self.sol = self._compute_dense_output()

        if self.bPrintProgress:
            print('t=', t)
        return step_accepted, message
Esempio n. 8
0
def solve_collocation_system(fun,
                             t,
                             y,
                             h,
                             Z0,
                             scale,
                             tol,
                             LU_real,
                             LU_complex,
                             solve_lu,
                             mass_matrix=None):
    """Solve the collocation system.

    Parameters
    ----------
    fun : callable
        Right-hand side of the system.
    t : float
        Current time.
    y : ndarray, shape (n,)
        Current state.
    h : float
        Step to try.
    Z0 : ndarray, shape (3, n)
        Initial guess for the solution. It determines new values of `y` at
        ``t + h * C`` as ``y + Z0``, where ``C`` is the Radau method constants.
    scale : float
        Problem tolerance scale, i.e. ``rtol * abs(y) + atol``.
    tol : float
        Tolerance to which solve the system. This value is compared with
        the normalized by `scale` error.
    LU_real, LU_complex
        LU decompositions of the system Jacobians.
    solve_lu : callable
        Callable which solves a linear system given a LU decomposition. The
        signature is ``solve_lu(LU, b)``.
    mass_matrix : {None, array_like, sparse_matrix}, optional
           Defined the constant mass matrix of the system, with shape (n,n).
           It may be singular, thus defining a problem of the differential-
           algebraic type (DAE). The default value is None (equivalent to
           an identity mass matrix).

    Returns
    -------
    converged : bool
        Whether iterations converged.
    n_iter : int
        Number of completed iterations.
    Z : ndarray, shape (3, n)
        Found solution.
    rate : float
        The rate of convergence.
    """
    # raise Exception('custom radau')
    n = y.shape[0]
    M_real = MU_REAL / h
    M_complex = MU_COMPLEX / h

    W = TI.dot(Z0)
    Z = Z0

    F = np.empty((3, n))
    ch = h * C

    dW_norm_old = None
    dW = np.empty_like(W)
    converged = False
    rate = None
    for k in range(NEWTON_MAXITER):
        if BPRINT:
            print('\titer {}/{}'.format(k, NEWTON_MAXITER))
        for i in range(3):
            F[i] = fun(t + ch[i], y + Z[i])

        if not np.all(np.isfinite(F)):
            if BPRINT:
                print('\t\tF contains non real numbers...')
            break

        if mass_matrix is None:
            f_real = F.T.dot(TI_REAL) - M_real * W[0]
            f_complex = F.T.dot(TI_COMPLEX) - M_complex * (W[1] + 1j * W[2])
        else:
            f_real = F.T.dot(TI_REAL) - M_real * mass_matrix.dot(W[0])
            f_complex = F.T.dot(
                TI_COMPLEX) - M_complex * mass_matrix.dot(W[1] + 1j * W[2])

        if BPRINT:
            print('\t\tresiduals: ||f_real||={:.3e}'.format(norm(f_real)))
            print('\t\t           ||f_cplx||={:.3e}'.format(norm(f_complex)))

        dW_real = solve_lu(LU_real, f_real)
        dW_complex = solve_lu(LU_complex, f_complex)

        dW[0] = dW_real
        dW[1] = dW_complex.real
        dW[2] = dW_complex.imag

        dW_norm = norm(dW / scale)
        if BPRINT:
            print('\t\tdW_norm={:.3e}'.format(dW_norm))

        if dW_norm_old is not None:
            rate = dW_norm / dW_norm_old

        if BPRINT and rate is not None:
            print('\t\trate={:.3e}'.format(rate))
            print('\t\testimated true error: ||dW||={:.3E}'.format(
                rate / (1 - rate) * dW_norm))

        if (rate is not None and (rate >= 1 or rate**(NEWTON_MAXITER - k) /
                                  (1 - rate) * dW_norm > tol)):
            # Newton loop diverges or does not converge fast enough
            if BPRINT:
                print(
                    '\tfinal loop convergence would reach ||dW||={:.3e}>tol--> Newton failed'
                    .format(rate**(NEWTON_MAXITER - k) / (1 - rate) * dW_norm))
            break

        W += dW
        Z = T.dot(W)

        if (dW_norm == 0
                or rate is not None and rate / (1 - rate) * dW_norm < tol):
            if BPRINT:
                print('\t\tconverged')
            converged = True
            break

        dW_norm_old = dW_norm

    return converged, k + 1, Z, rate
Esempio n. 9
0
File: rk.py Progetto: spraharsh/pele
    def _step_impl(self):
        t = self.t
        y = self.y

        max_step = self.max_step
        rtol = self.rtol
        atol = self.atol

        min_step = 10 * np.abs(np.nextafter(t, self.direction * np.inf) - t)
        if self.line_search_class == None:
            self.line_search_class = BacktrackingLineSearch(
                ctol=self.ls_ctol,
                max_iter=self.ls_max_iter,
                dec_scale=self.ls_dec_scale)
        if self.h_abs > max_step:
            h_abs = max_step
        elif self.h_abs < min_step:
            h_abs = min_step
        else:
            h_abs = self.h_abs

        order = self.order
        step_accepted = False
        while not step_accepted:
            if h_abs < min_step:
                return False, self.TOO_SMALL_STEP

            h = h_abs * self.direction
            t_new = t + h

            if self.direction * (t_new - self.t_bound) > 0:
                t_new = self.t_bound

            h = t_new - t
            h_abs = np.abs(h)

            y_new, f_new, error, step = rk_step(self.fun, t, y, self.f, h,
                                                self.A, self.B, self.C, self.E,
                                                self.K)
            scale = atol + np.maximum(np.abs(y), np.abs(y_new)) * rtol
            error_norm = norm(error / scale)

            if error_norm == 0.0:
                h_abs *= MAX_FACTOR
                step_accepted = True
            elif error_norm < 1:
                h_abs *= min(MAX_FACTOR,
                             max(1, SAFETY * error_norm**(-1 / (order + 1))))
                step_accepted = True
            else:
                h_abs *= max(MIN_FACTOR,
                             SAFETY * error_norm**(-1 / (order + 1)))
            if step_accepted:
                # check whether the step doesn't increase the energy value
                # This is basically an afterthought, but we can
                # refine the idea.
                # Note that the notation here becomes optimizerish
                # but this takes into account the difference
                energy, gradient = self.get_energy_gradient(y)
                (energy_at_x, grad_at_x, step_scale,
                 new_step) = self.line_search_class.line_search(
                     y, energy, gradient, step, self.get_energy_gradient)
                y_new = y + new_step
                f_new = -grad_at_x

        self.y_old = y

        self.t = t_new
        self.y = y_new

        self.h_abs = h_abs
        self.f = f_new

        return True, None
Esempio n. 10
0
    def _step_impl(self):
        t = self.t
        y = self.y

        max_step = self.max_step
        rtol = self.rtol
        atol = self.atol

        min_step = 10 * np.abs(np.nextafter(t, self.direction * np.inf) - t)

        if self.h_abs > max_step:
            h_abs = max_step
        elif self.h_abs < min_step:
            h_abs = min_step
        else:
            h_abs = self.h_abs

        order = self.order
        step_accepted = False

        while not step_accepted:
            if h_abs < min_step:
                return False, self.TOO_SMALL_STEP

            h = h_abs * self.direction
            t_new = t + h

            if self.direction * (t_new - self.t_bound) > 0:
                t_new = self.t_bound

            h = t_new - t
            h_abs = np.abs(h)

            y_new, f_new, error = rk_step(self.fun, t, y, self.f, h, self.A,
                                          self.B, self.C, self.E, self.K)
            scale = atol + np.maximum(np.abs(y), np.abs(y_new)) * rtol
            error_norm = norm(error / scale)
            if error_norm == 0.0:
                h_abs *= MAX_FACTOR
                step_accepted = True
            elif error_norm < 1:
                h_abs *= min(MAX_FACTOR,
                             max(1, SAFETY * error_norm ** (-1 / (order + 1))))
                step_accepted = True
            else:
                if (h_abs<self.min_step_user):
                    step_accepted = True
                    logger.debug("nmin step size reached")
                else:
                    h_abs *= max(MIN_FACTOR,
                             SAFETY * error_norm ** (-1 / (order + 1)))

        self.y_old = y

        self.t = t_new
        self.y = y_new

        self.h_abs = h_abs
        self.f = f_new

        return True, None