def interpolate(y, times, rhs, new_times, args=()):
    n = y.shape[1]
    y_out = np.empty((len(new_times), n), dtype=y.dtype)

    # this is the index into new_times
    j = 0
    for i, t in enumerate(times[:-1]):
        t0 = times[i]
        t1 = times[i+1]
        new_t0 = new_times[j]

        if new_t0 > t1:
            continue

        # construct interpolator
        y_interp = CubicHermiteInterpolate(
            t0, t1, y[i], y[i+1],
            rhs(t0, y[i], *args), rhs(t1, y[i+1], *args)
        )

        # interpolate
        while new_t0 <= t1:
            y_out[j] = y_interp(new_t0)
            j += 1

            if j == len(new_times):
                return y_out

            new_t0 = new_times[j]

    return y_out
Exemple #2
0
    def test_interpolate_grad(self):
        def rhs(t, y):
            return -y

        t0 = 0.1
        t1 = 0.2
        y0 = np.exp(-t0)
        y1 = np.exp(-t1)
        y = CubicHermiteInterpolate(t0, t1, y0, y1, rhs(t0, y0), rhs(t1, y1))
        times = np.linspace(t0, t1, 100)
        interp_grad = np.empty(len(times))
        for i in range(len(times)):
            interp_grad[i] = y.grad(times[i])
        analytic_grad = -np.exp(-times)
        np.testing.assert_allclose(analytic_grad,
                                   interp_grad,
                                   rtol=1e-5,
                                   atol=0)
def adjoint_error_and_sensitivities_interp(
        rhs, jac, drhs_dp, functional, dfunc_dy, times, y, args=(), method=runge_kutta5
):

    T = len(times)
    n = y.shape[1]

    # define the adjoint error equations
    def adjoint_error_and_sensitivities(
            t, state, y_interp, *args
    ):
        phi = state[:n]
        yt = y_interp(t)
        return np.concatenate((
            -jac(t, yt, phi, *args) - dfunc_dy(t, yt),
            [-functional(t, yt)],
            (rhs(t, yt, *args) - y_interp.grad(t)) * phi,
            -drhs_dp(t, yt, phi, *args),
        ))

    phi = np.zeros(n, dtype=y.dtype)
    n_params = len(drhs_dp(times[-1], y[-1], phi, *args))
    phi_f_error_dJdp = np.zeros(n + 2 + n_params, dtype=y.dtype)
    error = np.empty(T-1, dtype=y.dtype)

    rhs0 = rhs(times[-1], y[-1], *args)
    for i in reversed(range(len(times)-1)):

        t0 = times[i]
        t1 = times[i+1]
        phi_error1 = phi_f_error_dJdp[-1-n_params]
        rhs1 = rhs0
        rhs0 = rhs(t0, y[i], *args)

        y_interp = CubicHermiteInterpolate(
            t0, t1, y[i], y[i+1],
            rhs0, rhs1
        )

        # integrate to t0
        phi_f_error_dJdp = method(adjoint_error_and_sensitivities,
                                  t1, t0-t1,
                                  phi_f_error_dJdp, (y_interp,) + args)

        #phi_f_error_dJdp = method(adjoint_error_and_sensitivities,
        #                          t1, 0.5*(t0-t1),
        #                          phi_f_error_dJdp, (y_interp,) + args)
        #phi_f_error_dJdp = method(adjoint_error_and_sensitivities,
        #                          0.5*(t0+t1), 0.5*(t0-t1),
        #                          phi_f_error_dJdp, (y_interp,) + args)
        error[i] = phi_f_error_dJdp[-1-n_params] - phi_error1

    total_error = phi_f_error_dJdp[-1-n_params]
    sensitivities = phi_f_error_dJdp[-n_params:]
    f = phi_f_error_dJdp[-2-n_params]
    return total_error, error, f, sensitivities
Exemple #4
0
    def test_interpolate2d(self):
        def rhs(t, y):
            return -y

        t0 = 0.1
        t1 = 0.2
        y0 = np.array([np.exp(-t0), 2 * np.exp(-t0)])
        y1 = np.array([np.exp(-t1), 2 * np.exp(-t1)])
        y = CubicHermiteInterpolate(t0, t1, y0, y1, rhs(t0, y0), rhs(t1, y1))
        times = np.linspace(t0, t1, 100)
        interp_y = np.empty((len(times), len(y0)))
        for i in range(len(times)):
            interp_y[i] = y(times[i])
        analytic = np.stack((np.exp(-times), 2 * np.exp(-times)), axis=1)
        np.testing.assert_allclose(analytic, interp_y, rtol=1e-5, atol=0)
def adjoint_sensitivities_error(
        rhs, jac, drhs_dp, dfunc_dy, times, y, method=runge_kutta5
):
    T = len(times)
    if T != y.shape[0]:
        raise RuntimeError(
            'y (shape={}) should have length {}'
            .format(y.shape, T)
        )
    n = y.shape[1]

    def adjoint_sensitivities(t, phi_dJdp, y_interp):
        phi = phi_dJdp[:n]
        return np.concatenate((
            -jac(t, y_interp(t), phi),
            -drhs_dp(t, y_interp(t), phi),
        ))

    rhs_s = adjoint_sensitivities

    Ju = dfunc_dy(y)
    if Ju.shape[0] != T:
        raise RuntimeError(
            'dfunc_dy (shape={}) should return length {}'
            .format(Ju.shape, T)
        )
    phi = np.zeros(n, dtype=y.dtype)
    n_params = len(drhs_dp(times[-1], y[-1], phi))
    phi_dJdp = np.zeros(n + n_params, dtype=y.dtype)
    for i in reversed(range(len(times)-1)):
        # integrate over delta function
        phi_dJdp[:n] += Ju[i+1]

        t0 = times[i]
        t1 = times[i+1]

        y_interp = CubicHermiteInterpolate(
            t0, t1, y[i], y[i+1], rhs(t0, y[i]), rhs(t1, y[i+1])
        )

        # integrate
        phi_dJdp = method(adjoint_sensitivities,
                          t1, t0-t1, phi_dJdp, (y_interp,))

    return phi_dJdp[n:]
def adjoint_error_single_times(
        rhs, jac, dfunc_dy, times, y, method=runge_kutta5
):
    T = len(times)
    if T != y.shape[0]:
        raise RuntimeError(
            'y (shape={}) should have length {}'
            .format(y.shape, T)
        )
    n = y.shape[1]

    def adjoint_error(t, phi_error, y_interp):
        phi = phi_error[:n]
        return np.concatenate((
            -jac(t, y_interp(t), phi),
            (rhs(t, y_interp(t)) - y_interp.grad(t)) * phi,
        ))

    Ju = dfunc_dy(y)
    if Ju.shape[0] != T:
        raise RuntimeError(
            'dfunc_dy (shape={}) should return length {}'
            .format(Ju.shape, T)
        )
    phi_error = np.zeros(n + 1, dtype=y.dtype)
    error = np.empty(T-1, dtype=y.dtype)
    for i in reversed(range(len(times)-1)):
        # integrate over delta function
        phi_error[:n] += Ju[i+1]

        t0 = times[i]
        t1 = times[i+1]

        y_interp = CubicHermiteInterpolate(
            t0, t1, y[i], y[i+1], rhs(t0, y[i]), rhs(t1, y[i+1])
        )

        # integrate
        new_phi_error = method(adjoint_error,
                               t1, t0-t1, phi_error, (y_interp,))
        error[i] = new_phi_error[-1] - phi_error[-1]
        phi_error = new_phi_error

    return phi_error[-1], error
def adjoint_error(
        rhs, jac, functional, dfunc_dy, ftimes, times, y, method=runge_kutta5
):

    T = len(times)
    fT = len(ftimes)
    n = y.shape[1]
    rhs_calls = 0
    method_calls = 0

    # calculate the value and derivative of the function wrt y
    fy = interpolate(y, times, rhs, ftimes)
    Ju = dfunc_dy(fy)
    f = functional(fy)

    if Ju.shape[0] != fT:
        raise RuntimeError(
            'dfunc_dy (shape={}) should return length {}'
            .format(Ju.shape, fT)
        )

    # define the adjoint error equations
    def adjoint_error(t, phi_error, y_interp):
        phi = phi_error[:n]
        return np.concatenate((
            -jac(t, y_interp(t), phi),
            (rhs(t, y_interp(t)) - y_interp.grad(t)) * phi,
        ))

    phi_error = np.zeros(n + 1, dtype=y.dtype)
    error = np.empty(T-1, dtype=y.dtype)

    # this is index into ftimes
    j = len(ftimes) - 1
    for i in reversed(range(len(times)-1)):

        t0 = times[i]
        t1 = times[i+1]
        ft = ftimes[j]
        t = t1
        phi_error1 = phi_error[-1]

        y_interp = CubicHermiteInterpolate(
            t0, t1, y[i], y[i+1], rhs(t0, y[i]), rhs(t1, y[i+1])
        )
        rhs_calls += 2

        # ft could be == to t1
        if ft >= t:
            phi_error[:n] += Ju[j]
            j -= 1
            ft = ftimes[j]

        # break segment according to location of ftimes
        while ft > t0:
            # integrate
            phi_error = method(adjoint_error,
                               t, ft-t,
                               phi_error, (y_interp,))
            method_calls += 1

            # integrate over delta function
            phi_error[:n] += Ju[j]

            # go to new time point
            j -= 1
            t = ft
            ft = ftimes[j]

        # ft is now <= to t0, integrate to t0
        phi_error = method(adjoint_error,
                           t, t0-t,
                           phi_error, (y_interp,))
        method_calls += 1
        error[i] = phi_error[-1] - phi_error1

    if method == runge_kutta5:
        method_to_func_calls = 6
    else:
        method_to_func_calls = np.nan

    rhs_calls += method_to_func_calls * method_calls
    jac_calls = method_to_func_calls * method_calls

    return phi_error[-1], error, f, rhs_calls, jac_calls
def adjoint_error_and_sensitivities_independent_ftimes(
        rhs, jac, drhs_dp, functional, dfunc_dy, fy, ftimes, times, y, args=(), method=runge_kutta5
):

    T = len(times)
    fT = len(ftimes)
    n = y.shape[1]

    # calculate the value and derivative of the function wrt y
    Ju = dfunc_dy(fy)
    f = functional(fy)

    if Ju.shape[0] != fT:
        raise RuntimeError(
            'dfunc_dy (shape={}) should return length {}'
            .format(Ju.shape, fT)
        )

    # define the adjoint error equations
    def adjoint_error_and_sensitivities(
            t, phi_error_dJdp, y_interp, *args
    ):
        phi = phi_error_dJdp[:n]
        return np.concatenate((
            -jac(t, y_interp(t), phi, *args),
            (rhs(t, y_interp(t), *args) - y_interp.grad(t)) * phi,
            -drhs_dp(t, y_interp(t), phi, *args),
        ))

    phi = np.zeros(n, dtype=y.dtype)
    n_params = len(drhs_dp(times[-1], y[-1], phi, *args))
    phi_error_dJdp = np.zeros(n + 1 + n_params, dtype=y.dtype)
    error = np.empty(T-1, dtype=y.dtype)

    # this is index into ftimes
    j = len(ftimes) - 1
    rhs0 = rhs(times[-1], y[-1], *args)
    for i in reversed(range(len(times)-1)):

        t0 = times[i]
        t1 = times[i+1]
        ft = ftimes[j]
        t = t1
        phi_error1 = phi_error_dJdp[-1-n_params]
        rhs1 = rhs0
        rhs0 = rhs(t0, y[i], *args)

        y_interp = CubicHermiteInterpolate(
            t0, t1, y[i], y[i+1],
            rhs0, rhs1
        )

        # ft could be == to t1
        if ft >= t:
            phi_error_dJdp[:n] += Ju[j]
            j -= 1
            ft = ftimes[j]

        # break segment according to location of ftimes
        while ft > t0:
            # integrate
            phi_error_dJdp = method(adjoint_error_and_sensitivities,
                               t, ft-t,
                               phi_error_dJdp, (y_interp,) + args)

            # integrate over delta function
            phi_error_dJdp[:n] += Ju[j]

            # go to new time point
            j -= 1
            t = ft
            ft = ftimes[j]

        # ft is now <= to t0, integrate to t0
        phi_error_dJdp = method(adjoint_error_and_sensitivities,
                                t, t0-t,
                                phi_error_dJdp, (y_interp,) + args)
        error[i] = phi_error_dJdp[-1-n_params] - phi_error1


    return phi_error_dJdp[-1-n_params], error, \
        f, phi_error_dJdp[-n_params:]
def adjoint_error_and_sensitivities(
        rhs, jac, drhs_dp, functional, dfunc_dy, fix_times, times, y, args=(), method=runge_kutta5
):

    T = len(times)
    n = y.shape[1]

    # calculate the value and derivative of the function wrt y
    fy = y[fix_times]
    Ju = dfunc_dy(fy)
    f = functional(fy)

    if Ju.shape != fy.shape:
        raise RuntimeError(
            'dfunc_dy (shape={}) should return shape {}'
            .format(Ju.shape, fy.shape)
        )

    # define the adjoint error equations
    def adjoint_error_and_sensitivities(
            t, phi_error_dJdp, y_interp, *args
    ):
        phi = phi_error_dJdp[:n]
        return np.concatenate((
            -jac(t, y_interp(t), phi, *args),
            (rhs(t, y_interp(t), *args) - y_interp.grad(t)) * phi,
            -drhs_dp(t, y_interp(t), phi, *args),
        ))

    phi = np.zeros(n, dtype=y.dtype)
    n_params = len(drhs_dp(times[-1], y[-1], phi, *args))
    phi_error_dJdp = np.zeros(n + 1 + n_params, dtype=y.dtype)
    error = np.empty(T-1, dtype=y.dtype)

    # this is index into ftimes
    j = len(fy) - 1
    rhs0 = rhs(times[-1], y[-1], *args)
    for i in reversed(range(len(times)-1)):

        t0 = times[i]
        t1 = times[i+1]
        t = t1
        phi_error1 = phi_error_dJdp[-1-n_params]
        rhs1 = rhs0
        rhs0 = rhs(t0, y[i], *args)

        y_interp = CubicHermiteInterpolate(
            t0, t1, y[i], y[i+1],
            rhs0, rhs1
        )

        # if t1 is a fix time then integrate over delta function
        if fix_times[i+1]:
            phi_error_dJdp[:n] += Ju[j]
            j -= 1

        # integrate to t0
        phi_error_dJdp = method(adjoint_error_and_sensitivities,
                                t, t0-t,
                                phi_error_dJdp, (y_interp,) + args)
        error[i] = phi_error_dJdp[-1-n_params] - phi_error1


    return phi_error_dJdp[-1-n_params], error, \
        f, phi_error_dJdp[-n_params:]