def check_lte(method_residual, lte, exact_symb, base_dt, implicit):

        exact, residual, dys, J = utils.symb2functions(exact_symb)
        dddy = dys[3]
        Fddy = lambda t, y: J(t, y) * dys[2](t, y)

        newton_tol = 1e-10

        # tmax varies with dt so that we can vary dt over orders of
        # magnitude.
        tmax = 50*dt

        # Run ode solver
        if implicit:
            ts, ys = ode._odeint(
                residual, [0.0], [sp.array([exact(0.0)], ndmin=1)],
                dt, tmax, method_residual,
                target_error=None,
                time_adaptor=ode.create_random_time_adaptor(base_dt),
                initialisation_actions=par(ode.higher_order_start, 3),
                newton_tol=newton_tol)

        else:
            ts, ys = ode.odeint_explicit(
                dys[1], exact(0.0), base_dt, tmax, method_residual,
                time_adaptor=ode.create_random_time_adaptor(base_dt))

        dts = utils.ts2dts(ts)

        # Check it's accurate-ish
        exact_ys = map(exact, ts)
        errors = map(op.sub, exact_ys, ys)
        utils.assert_list_almost_zero(errors, 1e-3)

        # Calculate ltes by two methods. Note that we need to drop a few
        # values because exact calculation (may) need a few dts. Could be
        # dodgy: exact dddys might not correspond to dddy in experiment if
        # done over long time and we've wandered away from the solution.
        exact_dddys = map(dddy, ts, ys)
        exact_Fddys = map(Fddy, ts, ys)
        exact_ltes = map(lte, dts[2:], dts[1:-1], dts[:-2],
                         exact_dddys[3:], exact_Fddys[3:])
        error_diff_ltes = map(op.sub, errors[1:], errors[:-1])[2:]

        # Print for debugging when something goes wrong
        print exact_ltes
        print error_diff_ltes

        # Probably the best test is that they give the same order of
        # magnitude and the same sign... Can't test much more than that
        # because we have no idea what the constant in front of the dt**4
        # term is. Effective zero (noise level) is either dt**4 or newton
        # tol, whichever is larger.
        z = 50 * max(dt**4, newton_tol)
        map(par(utils.assert_same_sign, fp_zero=z),
            exact_ltes, error_diff_ltes)
        map(par(utils.assert_same_order_of_magnitude, fp_zero=z),
            exact_ltes, error_diff_ltes)
def test_sharp_dt_change():

    # Parameters, don't fiddle with these too much or it might miss the
    # step altogether...
    alpha = 20
    step_time = 0.4
    tmax = 2.5
    tol = 1e-4

    # Set up functions
    residual = par(tanh_residual, alpha=alpha, step_time=step_time)
    exact = par(tanh_exact, alpha=alpha, step_time=step_time)

    # Run it
    return check_problem('midpoint ab', residual, exact, tol=tol)
def test_sharp_dt_change():

    # Parameters, don't fiddle with these too much or it might miss the
    # step altogether...
    alpha = 20
    step_time = 0.4
    tmax = 2.5
    tol = 1e-4

    # Set up functions
    residual = par(tanh_residual, alpha=alpha, step_time=step_time)
    exact = par(tanh_exact, alpha=alpha, step_time=step_time)

    # Run it
    return check_problem('bdf2 mp', residual, exact, tol=tol)
Esempio n. 4
0
def test_bad_timestep_handling():
    """ Check that rejecting timesteps works.
    """
    tmax = 0.4
    tol = 1e-4

    def list_cummulative_sums(values, start):
        temp = [start]
        for v in values:
            temp.append(temp[-1] + v)
        return temp

    dts = [1e-6, 1e-6, 1e-6, (1. - 0.01) * tmax]
    initial_ts = list_cummulative_sums(dts[:-1], 0.)
    initial_ys = [sp.array(exp3_exact(t), ndmin=1) for t in initial_ts]

    adaptor = par(general_time_adaptor,
                  lte_calculator=bdf2_mp_lte_estimate,
                  method_order=2)

    ts, ys = _odeint(exp3_residual, initial_ts, initial_ys, dts[-1], tmax,
                     bdf2_residual, tol, adaptor)

    # plt.plot(ts,ys)
    # plt.plot(ts[1:], utils.ts2dts(ts))
    # plt.plot(ts, map(exp3_exact, ts))
    # plt.show()

    overall_tol = len(ys) * tol * 2  # 2 is a fudge factor...
    utils.assert_list_almost_equal(ys, map(exp3_exact, ts), overall_tol)
def test_bad_timestep_handling():
    """ Check that rejecting timesteps works.
    """
    tmax = 0.4
    tol = 1e-4

    def list_cummulative_sums(values, start):
        temp = [start]
        for v in values:
            temp.append(temp[-1] + v)
        return temp

    dts = [1e-6, 1e-6, 1e-6, (1. - 0.01)*tmax]
    initial_ts = list_cummulative_sums(dts[:-1], 0.)
    initial_ys = [sp.array(exp3_exact(t), ndmin=1) for t in initial_ts]

    adaptor = par(general_time_adaptor, lte_calculator=bdf2_mp_lte_estimate,
                  method_order=2)

    ts, ys = _odeint(exp3_residual, initial_ts, initial_ys, dts[-1], tmax,
                     bdf2_residual, tol, adaptor)

    # plt.plot(ts,ys)
    # plt.plot(ts[1:], utils.ts2dts(ts))
    # plt.plot(ts, map(exp3_exact, ts))
    # plt.show()

    overall_tol = len(ys) * tol * 2  # 2 is a fudge factor...
    utils.assert_list_almost_equal(ys, map(exp3_exact, ts), overall_tol)
def symb2functions(exact_symb):
    t, y, Dy = sympy.symbols('t y Dy')
    exact = sympy.lambdify(t, exact_symb)
    residual = symb2residual(exact_symb)
    dys = [None] + map(par(symb2deriv, exact_symb), range(1, 10))
    jacobian = symb2jacobian(exact_symb)
    return exact, residual, dys, jacobian
def parallel_parameter_sweep(function, parameter_lists, serial_mode=False):
    """Run function with all combinations of parameters in parallel using
    all available cores.

    parameter_lists should be a list of lists of parameters,
    """

    import multiprocessing

    # Generate a complete set of combinations of parameters
    parameter_sets = it.product(*parameter_lists)

    # multiprocessing doesn't include a "starmap", requires all functions
    # to take a single argument. Use a function wrapper to fix this. Also
    # print the list of args while we're in there.
    wrapped_function = par(_apply_to_list_and_print_args, function)

    # For debugging we often need to run in serial (to get useful stack
    # traces).
    if serial_mode:
        results_iterator = it.imap(wrapped_function, parameter_sets)
        # Force evaluation (to be exactly the same as in parallel)
        results_iterator = list(results_iterator)

    else:
        # Run in all parameter sets in parallel
        pool = multiprocessing.Pool()
        results_iterator = pool.imap_unordered(wrapped_function,
                                               parameter_sets)
        pool.close()

        # wait for everything to finish
        pool.join()

    return results_iterator
def symb2functions(exact_symb):
    t, y, Dy = sympy.symbols('t y Dy')
    exact = sympy.lambdify(t, exact_symb)
    residual = symb2residual(exact_symb)
    dys = [None]+map(par(symb2deriv, exact_symb), range(1, 10))
    jacobian = symb2jacobian(exact_symb)
    return exact, residual, dys, jacobian
def parallel_parameter_sweep(function, parameter_lists, serial_mode=False):
    """Run function with all combinations of parameters in parallel using
    all available cores.

    parameter_lists should be a list of lists of parameters,
    """

    import multiprocessing

    # Generate a complete set of combinations of parameters
    parameter_sets = it.product(*parameter_lists)

    # multiprocessing doesn't include a "starmap", requires all functions
    # to take a single argument. Use a function wrapper to fix this. Also
    # print the list of args while we're in there.
    wrapped_function = par(_apply_to_list_and_print_args, function)

    # For debugging we often need to run in serial (to get useful stack
    # traces).
    if serial_mode:
        results_iterator = it.imap(wrapped_function, parameter_sets)
         # Force evaluation (to be exactly the same as in parallel)
        results_iterator = list(results_iterator)

    else:
        # Run in all parameter sets in parallel
        pool = multiprocessing.Pool()
        results_iterator = pool.imap_unordered(
            wrapped_function, parameter_sets)
        pool.close()

        # wait for everything to finish
        pool.join()

    return results_iterator
def _timestep_scheme_factory(method):
    """Construct the functions for the named method. Input is either a
    string with the name of the method or a dict with the name and a list
    of parameters.

    Returns a triple of functions for:
    * a time residual
    * a timestep adaptor
    * intialisation actions
    """

    _method_dict = {}

    # If it's a dict then get the label attribute, otherwise assume it's a
    # string...
    if isinstance(method, dict):
        _method_dict = method
    else:
        _method_dict = {'label' : method}

    label = _method_dict.get('label').lower()

    if label == 'bdf2':
        return bdf2_residual, None, par(higher_order_start, 2)

    elif label == 'bdf2 mp':
        return bdf2_residual, bdf2_mp_time_adaptor,\
            par(higher_order_start, 2)

    elif label == 'bdf1':
        return bdf1_residual, None, None

    elif label == 'midpoint':
        return midpoint_residual, None, None

    elif label == 'midpoint ab':
        # Get values from dict (with defaults if not set).
        n_start = _method_dict.setdefault('n_start', 2)
        ab_start = _method_dict.setdefault('ab_start_point', 't_n')
        use_y_np1_in_interp = _method_dict.setdefault('use_y_np1_in_interp', False)

        interp = _method_dict.setdefault(
            'interpolator', par(my_interpolate, n_interp=n_start,
                                use_y_np1_in_interp=use_y_np1_in_interp))

        adaptor = par(midpoint_ab_time_adaptor, ab_start_point=ab_start,
                      interpolator=interp)

        return midpoint_residual, adaptor, par(higher_order_start, n_start)

    elif label == 'trapezoid':
        # TR is actually self starting but due to technicalities with
        # getting derivatives of y from implicit formulas we need an extra
        # starting point.
        return TrapezoidRuleResidual(), None, par(higher_order_start, 2)

    else:
        message = "Method '"+label+"' not recognised."
        raise ValueError(message)
Esempio n. 11
0
def newton(residual,
           x0,
           jacobian_func=None,
           newton_tol=1e-8,
           solve_function=None,
           jacobian_fd_eps=1e-10,
           max_iter=20):
    """Find the minimum of the residual function using Newton's method.

    Optionally specify a Jacobian calculation function, a tolerance and/or
    a function to solve the linear system J.dx = r.

    If no Jacobian_Func is given the Jacobian is finite differenced.
    If no solve function is given then sp.linalg.solve is used.

    Norm for measuring residual is max(abs(..)).
    """
    if jacobian_func is None:
        jacobian_func = par(finite_diff_jacobian,
                            residual,
                            eps=jacobian_fd_eps)

    if solve_function is None:
        solve_function = sp.linalg.solve

    # Wrap the solve function to deal with non-matrix cases (i.e. when we
    # only have one degree of freedom and the "Jacobian" is just a number).
    def wrapped_solve(A, b):
        if len(b) == 1:
            return b / A[0][0]
        else:
            try:
                return solve_function(A, b)
            except scipy.linalg.LinAlgError:
                print "\n", A, b, "\n"
                raise

    # Call the real Newton solve function
    return _newton(residual, x0, jacobian_func, newton_tol, wrapped_solve,
                   max_iter)
def newton(residual, x0, jacobian_func=None, newton_tol=1e-8,
           solve_function=None,
           jacobian_fd_eps=1e-10, max_iter=20):
    """Find the minimum of the residual function using Newton's method.

    Optionally specify a Jacobian calculation function, a tolerance and/or
    a function to solve the linear system J.dx = r.

    If no Jacobian_Func is given the Jacobian is finite differenced.
    If no solve function is given then sp.linalg.solve is used.

    Norm for measuring residual is max(abs(..)).
    """
    if jacobian_func is None:
        jacobian_func = par(
            finite_diff_jacobian,
            residual,
            eps=jacobian_fd_eps)

    if solve_function is None:
        solve_function = sp.linalg.solve

    # Wrap the solve function to deal with non-matrix cases (i.e. when we
    # only have one degree of freedom and the "Jacobian" is just a number).
    def wrapped_solve(A, b):
        if len(b) == 1:
            return b/A[0][0]
        else:
            try:
                return solve_function(A, b)
            except scipy.linalg.LinAlgError:
                print "\n", A, b, "\n"
                raise

    # Call the real Newton solve function
    return _newton(residual, x0, jacobian_func, newton_tol,
                   wrapped_solve, max_iter)
    exact_symb = (y0 - g0) * sympy.exp(-l * sym_t) + g

    # Don't just diff exact, or we don't get the y dependence in there!
    dydt_symb = -l * sym_y + l * g + sympy.diff(g, sym_t, 1)
    F = sympy.diff(dydt_symb, sym_y, 1)

    exact_f = sympy.lambdify(sym_t, exact_symb)
    dydt_f = sympy.lambdify((sym_t, sym_y), dydt_symb)

    def residual(t, y, dydt):
        return dydt - dydt_f(t, y)

    return residual, dydt_f, exact_f, (exact_symb, F)


trig_midpoint_killer_problem = par(midpoint_method_killer_problem, 5,
                                   "sin(t) + cos(t)")

poly_midpoint_killer_problem = par(midpoint_method_killer_problem, 5, "t**2")


# ODE example for paper?
def damped_oscillation_residual(omega, beta, t, y, dydt):
    return dydt - damped_oscillation_dydt(omega, beta, t, y)


def damped_oscillation_dydt(omega, beta, t, y):
    return beta * sp.exp(-beta * t) * sp.sin(omega * t) - omega * sp.exp(
        -beta * t) * sp.cos(omega * t)


def damped_oscillation_exact(omega, beta, t):
Esempio n. 14
0
def generate_predictor_scheme(pt_infos, predictor_name, symbolic=None):

    # Extract symbolic expressions from what we're given.
    if symbolic is not None:
        try:
            symb_exact, symb_F = symbolic

        except TypeError:  # not iterable
            symb_exact = symbolic

            Sy = sympy.symbols('y', real=True)
            symb_dy = sympy.diff(symb_exact, St, 1).subs(symb_exact, Sy)
            symb_F = sympy.diff(symb_dy, Sy).subs(Sy, symb_exact)

        dydt_func = sympy.lambdify(St, sympy.diff(symb_exact, St, 1))
        f_y = sympy.lambdify(St, symb_exact)

    else:
        dydt_func = None
        f_y = None

    # To cancel errors we need the final step to be at t_np1
    # assert pt_infos[0].time == 0
    n_hist = len(pt_infos) + 5

    # Create symbolic and python functions of (corrector) dts and ts
    # respectively that give the appropriate dts for this predictor.
    p_dtns = []
    p_dtn_funcs = []
    for pt1, pt2 in zip(pt_infos[:-1], pt_infos[1:]):
        p_dtns.append(sum_dts(pt1.time, pt2.time))
        p_dtn_funcs.append(generate_p_dt_func(p_dtns[-1]))

    # For each time point the predictor uses construct symbolic error
    # estimates for the requested estimate at that point and python
    # functions to calculate the value of the estimate.
    y_errors = map(ptinfo2yerr, pt_infos)
    y_funcs = map(par(ptinfo2yfunc, y_of_t_func=f_y), pt_infos)
    dy_errors = map(ptinfo2dyerr, pt_infos)
    dy_funcs = map(par(ptinfo2dyfunc, dydt_func=dydt_func), pt_infos)

    # Construct errors and function for the predictor
    # ============================================================
    if predictor_name == "ebdf2" or predictor_name == "wrong step ebdf2":

        if predictor_name == "wrong step ebdf2":
            temp_p_dtnm1 = p_dtns[1] + Sdts[0]
        else:
            temp_p_dtnm1 = p_dtns[1]

        y_np1_p_expr = y_np1_exact - (
            # Natural ebdf2 lte:
            ebdf2_lte(p_dtns[0], temp_p_dtnm1, Sdddynph)
            # error due to approximation to derivative at tn:
            + ode.ebdf2_step(p_dtns[0], 0, dy_errors[1], p_dtns[1], 0)
            # error due to approximation to yn
            + ode.ebdf2_step(p_dtns[0], y_errors[1], 0, p_dtns[1], 0)
            # error due to approximation to ynm1 (typically zero)
            + ode.ebdf2_step(p_dtns[0], 0, 0, p_dtns[1], y_errors[2]))

        def predictor_func(ts, ys):

            dtn = p_dtn_funcs[0](ts)
            yn = y_funcs[1](ts, ys)
            dyn = dy_funcs[1](ts, ys)

            dtnm1 = p_dtn_funcs[1](ts)
            ynm1 = y_funcs[2](ts, ys)
            return ode.ebdf2_step(dtn, yn, dyn, dtnm1, ynm1)

    elif predictor_name == "ab2":

        y_np1_p_expr = y_np1_exact - (
            # Natural ab2 lte:
            ab2_lte(p_dtns[0], p_dtns[1], Sdddynph)
            # error due to approximation to derivative at tn
            + ode.ab2_step(p_dtns[0], 0, dy_errors[1], p_dtns[1], 0)
            # error due to approximation to derivative at tnm1
            + ode.ab2_step(p_dtns[0], 0, 0, p_dtns[1], dy_errors[2])
            # error due to approximation to yn
            + ode.ab2_step(p_dtns[0], y_errors[1], 0, p_dtns[1], 0))

        def predictor_func(ts, ys):

            dtn = p_dtn_funcs[0](ts)
            yn = y_funcs[1](ts, ys)
            dyn = dy_funcs[1](ts, ys)

            dtnm1 = p_dtn_funcs[1](ts)
            dynm1 = dy_funcs[2](ts, ys)

            return ode.ab2_step(dtn, yn, dyn, dtnm1, dynm1)

    elif predictor_name == "ibdf2":

        y_np1_p_expr = y_np1_exact - (
            # Natural bdf2 lte:
            bdf2_lte(p_dtns[0], p_dtns[1], Sdddynph)
            # error due to approximation to derivative at tnp1
            + ode.ibdf2_step(p_dtns[0], 0, dy_errors[0], p_dtns[1], 0)
            # errors due to approximations to y at tn and tnm1
            + ode.ibdf2_step(p_dtns[0], y_errors[1], 0, p_dtns[1], 0) +
            ode.ibdf2_step(p_dtns[0], 0, 0, p_dtns[1], y_errors[2]))

        def predictor_func(ts, ys):

            pdts = [f(ts) for f in p_dtn_funcs]

            dynp1 = dy_funcs[0](ts, ys)
            yn = y_funcs[1](ts, ys)
            ynm1 = y_funcs[2](ts, ys)

            tsin = [
                t_at_time_point(ts, pt.time) for pt in reversed(pt_infos[1:])
            ]
            ysin = [ys[-(pt.time + 1)] for pt in reversed(pt_infos[1:])]
            dt = pdts[0]

            return ode.ibdf2_step(pdts[0], yn, dynp1, pdts[1], ynm1)

    elif predictor_name == "ibdf3":

        y_np1_p_expr = y_np1_exact - (
            # Natural bdf2 lte:
            bdf3_lte(p_dtns[0], p_dtns[1], Sdddynph)
            # error due to approximation to derivative at tnp1
            + ode.ibdf3_step(dy_errors[0], p_dtns[0], 0, p_dtns[1], 0,
                             p_dtns[2], 0)
            # errors due to approximations to y at tn, tnm1, tnm2
            + ode.ibdf3_step(0, p_dtns[0], y_errors[1], p_dtns[1], 0,
                             p_dtns[2], 0) +
            ode.ibdf3_step(0, p_dtns[0], 0, p_dtns[1], y_errors[2], p_dtns[2],
                           0) + ode.ibdf3_step(0, p_dtns[0], 0, p_dtns[1], 0,
                                               p_dtns[2], y_errors[3]))

        def predictor_func(ts, ys):

            pdts = [f(ts) for f in p_dtn_funcs]

            dynp1 = dy_funcs[0](ts, ys)
            yn = y_funcs[1](ts, ys)
            ynm1 = y_funcs[2](ts, ys)
            ynm2 = y_funcs[3](ts, ys)

            return ode.ibdf3_step(dynp1, pdts[0], yn, pdts[1], ynm1, pdts[2],
                                  ynm2)

    elif predictor_name == "ebdf3":
        y_np1_p_expr = y_np1_exact - (
            # Natural ebdf3 lte error:
            ebdf3_lte(p_dtns[0], p_dtns[1], p_dtns[2], Sdddynph)
            # error due to approximation to derivative at tn
            + ode.ebdf3_step(p_dtns[0], 0, dy_errors[1], p_dtns[1], 0,
                             p_dtns[2], 0)
            # errors due to approximation to y at tn, tnm1, tnm2
            + ode.ebdf3_step(p_dtns[0], y_errors[1], 0, p_dtns[1], 0,
                             p_dtns[2], 0) +
            ode.ebdf3_step(p_dtns[0], 0, 0, p_dtns[1], y_errors[2], p_dtns[2],
                           0) + ode.ebdf3_step(p_dtns[0], 0, 0, p_dtns[1], 0,
                                               p_dtns[2], y_errors[3]))

        def predictor_func(ts, ys):

            pdts = [f(ts) for f in p_dtn_funcs]

            dyn = dy_funcs[1](ts, ys)
            yn = y_funcs[1](ts, ys)
            ynm1 = y_funcs[2](ts, ys)
            ynm2 = y_funcs[3](ts, ys)

            return ode.ebdf3_step(pdts[0], yn, dyn, pdts[1], ynm1, pdts[2],
                                  ynm2)

    elif predictor_name == "use exact dddy":

        symb_dddy = sympy.diff(symb_exact, St, 3)
        f_dddy = sympy.lambdify(St, symb_dddy)

        print symb_exact
        print "dddy =", symb_dddy

        # "Predictor" is just exactly dddy, error forumlated so that matrix
        # turns out right. Calculate at one before last point (same as lte
        # "location").
        def predictor_func(ts, ys):
            tn = t_at_time_point(ts, pt_infos[1].time)
            return f_dddy(tn)

        y_np1_p_expr = Sdddynph

    elif predictor_name == "use exact Fddy":

        symb_ddy = sympy.diff(symb_exact, St, 2)
        f_Fddy = sympy.lambdify(St, symb_F * symb_ddy)

        print "Fddy =", symb_F * symb_ddy

        # "Predictor" is just exactly dddy, error forumlated so that matrix
        # turns out right. Calculate at one before last point (same as lte
        # "location").
        def predictor_func(ts, ys):
            tn = t_at_time_point(ts, pt_infos[1].time)
            return f_Fddy(tn)

        y_np1_p_expr = SFddynph

    else:
        raise ValueError("Unrecognised predictor name " + predictor_name)

    return predictor_func, y_np1_p_expr
def main():

    parser = argparse.ArgumentParser(description=main.__doc__,

    # Don't mess up my formating in the help message
    formatter_class=argparse.RawDescriptionHelpFormatter)

    parser.add_argument('--clean', action='store_true', help='delete old data')
    parser.add_argument('--serial', action='store_true',
                         help="Don't do parallel sweeps")
    parser.add_argument('--outdir', help='delete old data')
    parser.add_argument('--quick', action='store_true',
                        help='quick version of parameters for testing')
    args = parser.parse_args()

    if args.outdir is None:
        args.outdir = os.path.abspath('../experiments/intermag')

    argsdict = {
                '-driver' : ['ll', 'llg'],
                '-tmax' : 4,
                '-hlib-bem' : 0,
                '-renormalise' : 0,
                '-mesh' : ['ut_sphere'], # 'sq_square'],
                '-ms-method' : ['implicit', 'disabled', 'decoupled', 'sphere'],
                '-solver' : 'gmres',
                "-matrix-type" : "som",
                '-prec' : 'som-main-ilu-1',
                '-ts' : ['rk2', 'midpoint-bdf', 'euler'],
                '-scale' : 2,
                '-fd-jac' : 1,
                '-damping' : [1.0, 0.1, 0.01],
                '-check-angles' : 1,
                }
    refines = [1, 2, 3, 4]

    # Only do a couple of things for a test run
    if args.quick:
        argsdict['-ms-method'] = 'disabled'
        argsdict['-damping'] = 1.0
        argsdict['-ts'] = 'euler'
        refines = [1, 2, 3]


    # The exact function to run
    f = par(locate_stable_points,
            refines_list = refines,
            dt_guess=0.1,
            dir_naming_args=mm.argdict_varying_args(argsdict),
            root_outdir=args.outdir,
            nbisection=2)

    # Create list of all possible combinations of args
    argsets = mm.product_of_argdict(argsdict)

    # Filter out impossible combinations, bit hacky...
    def is_bad_combination(data):
        if data['-ts'] == 'rk2' or data['-ts'] == 'euler':
            if data['-driver'] == 'llg' or data['-ms-method'] == 'implicit':
                return True
        elif data['-ts'] == 'midpoint-bdf' or data['-ts'] == 'bdf2':
            if data['-driver'] == 'll':
                return True

        return False

    argsets = [a for a in argsets if not is_bad_combination(a)]


    dts = list(utils.parallel_map(f, argsets, serial_mode=args.serial))

    print(dts)

    return 0
Esempio n. 16
0
    def check_lte(method_residual, lte, exact_symb, base_dt, implicit):

        exact, residual, dys, J = utils.symb2functions(exact_symb)
        dddy = dys[3]
        Fddy = lambda t, y: J(t, y) * dys[2](t, y)

        newton_tol = 1e-10

        # tmax varies with dt so that we can vary dt over orders of
        # magnitude.
        tmax = 50 * dt

        # Run ode solver
        if implicit:
            ts, ys = ode._odeint(
                residual, [0.0], [sp.array([exact(0.0)], ndmin=1)],
                dt,
                tmax,
                method_residual,
                target_error=None,
                time_adaptor=ode.create_random_time_adaptor(base_dt),
                initialisation_actions=par(ode.higher_order_start, 3),
                newton_tol=newton_tol)

        else:
            ts, ys = ode.odeint_explicit(
                dys[1],
                exact(0.0),
                base_dt,
                tmax,
                method_residual,
                time_adaptor=ode.create_random_time_adaptor(base_dt))

        dts = utils.ts2dts(ts)

        # Check it's accurate-ish
        exact_ys = map(exact, ts)
        errors = map(op.sub, exact_ys, ys)
        utils.assert_list_almost_zero(errors, 1e-3)

        # Calculate ltes by two methods. Note that we need to drop a few
        # values because exact calculation (may) need a few dts. Could be
        # dodgy: exact dddys might not correspond to dddy in experiment if
        # done over long time and we've wandered away from the solution.
        exact_dddys = map(dddy, ts, ys)
        exact_Fddys = map(Fddy, ts, ys)
        exact_ltes = map(lte, dts[2:], dts[1:-1], dts[:-2], exact_dddys[3:],
                         exact_Fddys[3:])
        error_diff_ltes = map(op.sub, errors[1:], errors[:-1])[2:]

        # Print for debugging when something goes wrong
        print exact_ltes
        print error_diff_ltes

        # Probably the best test is that they give the same order of
        # magnitude and the same sign... Can't test much more than that
        # because we have no idea what the constant in front of the dt**4
        # term is. Effective zero (noise level) is either dt**4 or newton
        # tol, whichever is larger.
        z = 50 * max(dt**4, newton_tol)
        map(par(utils.assert_same_sign, fp_zero=z), exact_ltes,
            error_diff_ltes)
        map(par(utils.assert_same_order_of_magnitude, fp_zero=z), exact_ltes,
            error_diff_ltes)
    dtnm2 = ts[-3] - ts[-4]
    dtnm3 = ts[-4] - ts[-5]

    ynp1 = ys[-1]
    yn = ys[-2]
    ynm1 = ys[-3]
    ynm2 = ys[-4]
    ynm3 = ys[-5]

    return (
        dtn*(dtn + dtnm1)*(-(-(ynm1 - ynm2)/dtnm2 + (yn - ynm1)/dtnm1)/(dtnm1 + dtnm2) + (-(yn - ynm1)/dtnm1 + (ynp1 - yn)/dtn)/(dtn + dtnm1))/(dtn + dtnm1 + dtnm2) + dtn*(dtn + dtnm1)*((-(-(ynm1 - ynm2)/dtnm2 + (yn - ynm1)/dtnm1)/(dtnm1 + dtnm2) + (-(yn - ynm1)/dtnm1 + (ynp1 - yn)/dtn)/(dtn + dtnm1)) /
                                                                                                                                                                                          (dtn + dtnm1 + dtnm2) - (-(-(ynm2 - ynm3)/dtnm3 + (ynm1 - ynm2)/dtnm2)/(dtnm2 + dtnm3) + (-(ynm1 - ynm2)/dtnm2 + (yn - ynm1)/dtnm1)/(dtnm1 + dtnm2))/(dtnm1 + dtnm2 + dtnm3))*(dtn + dtnm1 + dtnm2)/(dtn + dtnm1 + dtnm2 + dtnm3) + dtn*(-(yn - ynm1)/dtnm1 + (ynp1 - yn)/dtn)/(dtn + dtnm1) + (ynp1 - yn)/dtn
    )


bdf2_residual = par(bdf_residual, dydt_func=bdf2_dydt)
bdf3_residual = par(bdf_residual, dydt_func=bdf3_dydt)
bdf4_residual = par(bdf_residual, dydt_func=bdf4_dydt)


# Assumption: we never actually undo a timestep (otherwise dys will become
# out of sync with ys).
class TrapezoidRuleResidual(object):

    """A class to calculate trapezoid rule residuals.

    We need a class because we need to store the past data. Other residual
    calculations do not.
    """

    def __init__(self):
def _timestep_scheme_factory(method):
    """Construct the functions for the named method. Input is either a
    string with the name of the method or a dict with the name and a list
    of parameters.

    Returns a triple of functions for:
    * a time residual
    * a timestep adaptor
    * intialisation actions
    """

    _method_dict = {}

    # If it's a dict then get the label attribute, otherwise assume it's a
    # string...
    if isinstance(method, dict):
        _method_dict = method
    else:
        _method_dict = {'label': method}

    label = _method_dict.get('label').lower()

    if label == 'bdf2':
        return bdf2_residual, None, par(higher_order_start, 2)

    elif label == 'bdf2 mp':
        return bdf2_residual, bdf2_mp_time_adaptor,\
            par(higher_order_start, 2)

    elif label == 'bdf1':
        return bdf1_residual, None, None

    elif label == 'midpoint':
        return midpoint_residual, None, None

    elif label == 'midpoint ab':
        # Get values from dict (with defaults if not set).
        n_start = _method_dict.setdefault('n_start', 2)
        ab_start = _method_dict.setdefault('ab_start_point', 't_n')
        use_y_np1_in_interp = _method_dict.setdefault('use_y_np1_in_interp',
                                                      False)

        interp = _method_dict.setdefault(
            'interpolator',
            par(my_interpolate,
                n_interp=n_start,
                use_y_np1_in_interp=use_y_np1_in_interp))

        adaptor = par(midpoint_ab_time_adaptor,
                      ab_start_point=ab_start,
                      interpolator=interp)

        return midpoint_residual, adaptor, par(higher_order_start, n_start)

    elif label == 'trapezoid':
        # TR is actually self starting but due to technicalities with
        # getting derivatives of y from implicit formulas we need an extra
        # starting point.
        return TrapezoidRuleResidual(), None, par(higher_order_start, 2)

    else:
        message = "Method '" + label + "' not recognised."
        raise ValueError(message)
    g0 = g.subs(sym_t, 0).evalf()
    exact_symb = (y0 - g0)*sympy.exp(-l*sym_t) + g

    # Don't just diff exact, or we don't get the y dependence in there!
    dydt_symb = -l*sym_y + l*g + sympy.diff(g, sym_t, 1)
    F = sympy.diff(dydt_symb, sym_y, 1)

    exact_f = sympy.lambdify(sym_t, exact_symb)
    dydt_f = sympy.lambdify((sym_t, sym_y), dydt_symb)

    def residual(t, y, dydt):
        return dydt - dydt_f(t, y)

    return residual, dydt_f, exact_f, (exact_symb, F)

trig_midpoint_killer_problem = par(midpoint_method_killer_problem, 5,
                                   "sin(t) + cos(t)")

poly_midpoint_killer_problem = par(midpoint_method_killer_problem, 5, "t**2")


# ODE example for paper?
def damped_oscillation_residual(omega, beta, t, y, dydt):
    return dydt - damped_oscillation_dydt(omega, beta, t, y)
def damped_oscillation_dydt(omega, beta, t, y):
    return beta*sp.exp(-beta*t)*sp.sin(omega*t) - omega*sp.exp(-beta*t)*sp.cos(omega*t)
def damped_oscillation_exact(omega, beta, t):
    return sp.exp(-beta*t) * sp.sin(omega*t)

def damped_oscillation_dddydt(omega, beta, t):
    """See notes 16/8/2013."""
    a = sp.exp(- beta*t) * sp.sin(omega * t) # y in notes
Esempio n. 20
0
def _timestep_scheme_factory(method):
    """Construct the functions for the named method. Input is either a
    string with the name of the method or a dict with the name and a list
    of parameters.

    Returns a triple of functions for:
    * a time residual
    * a timestep adaptor
    * intialisation actions
    """

    _method_dict = {}

    # If it's a dict then get the label attribute, otherwise assume it's a
    # string...
    if isinstance(method, dict):
        _method_dict = method
    else:
        _method_dict = {'label': method}

    label = _method_dict.get('label').lower()

    if label == 'bdf2':
        return bdf2_residual, None, par(higher_order_start, 2)

    elif label == 'bdf3':
        return bdf3_residual, None, par(higher_order_start, 3)

    elif label == 'bdf2 mp':
        adaptor = par(general_time_adaptor,
                      lte_calculator=bdf2_mp_lte_estimate,
                      method_order=2)
        return (bdf2_residual, adaptor, par(higher_order_start, 3))

    elif label == 'bdf2 ebdf3':
        dydt_func = _method_dict.get('dydt_func', None)
        lte_est = par(ebdf3_lte_estimate, dydt_func=dydt_func)
        adaptor = par(general_time_adaptor,
                      lte_calculator=lte_est,
                      method_order=2)
        return (bdf2_residual, adaptor, par(higher_order_start, 4))

    elif label == 'bdf1':
        return bdf1_residual, None, None

    elif label == 'imr':
        return imr_residual, None, None

    elif label == 'imr ebdf3':
        dydt_func = _method_dict.get('dydt_func', None)
        lte_est = par(ebdf3_lte_estimate, dydt_func=dydt_func)
        adaptor = par(general_time_adaptor,
                      lte_calculator=lte_est,
                      method_order=2)
        return imr_residual, adaptor, par(higher_order_start, 5)

    elif label == 'imr w18':
        import simpleode.algebra.two_predictor as tp
        p1_points = _method_dict['p1_points']
        p1_pred = _method_dict['p1_pred']

        p2_points = _method_dict['p2_points']
        p2_pred = _method_dict['p2_pred']

        symbolic = _method_dict['symbolic']

        lte_est = tp.generate_predictor_pair_lte_est(
            *tp.generate_predictor_pair_scheme(
                p1_points, p1_pred, p2_points, p2_pred, symbolic=symbolic))

        adaptor = par(general_time_adaptor,
                      lte_calculator=lte_est,
                      method_order=2)

        # ??ds don't actually need this many history values to start but it
        # makes things easier so use this many for now.
        return imr_residual, adaptor, par(higher_order_start, 12)

    elif label == 'trapezoid' or label == 'tr':
        # TR is actually self starting but due to technicalities with
        # getting derivatives of y from implicit formulas we need an extra
        # starting point.
        return TrapezoidRuleResidual(), None, par(higher_order_start, 2)

    elif label == 'tr ab':

        dydt_func = _method_dict.get('dydt_func')

        adaptor = par(general_time_adaptor,
                      lte_calculator=tr_ab_lte_estimate,
                      dydt_func=dydt_func,
                      method_order=2)

        return TrapezoidRuleResidual(), adaptor, par(higher_order_start, 2)

    else:
        message = "Method '" + label + "' not recognised."
        raise ValueError(message)
def _timestep_scheme_factory(method):
    """Construct the functions for the named method. Input is either a
    string with the name of the method or a dict with the name and a list
    of parameters.

    Returns a triple of functions for:
    * a time residual
    * a timestep adaptor
    * intialisation actions
    """

    _method_dict = {}

    # If it's a dict then get the label attribute, otherwise assume it's a
    # string...
    if isinstance(method, dict):
        _method_dict = method
    else:
        _method_dict = {'label': method}

    label = _method_dict.get('label').lower()

    if label == 'bdf2':
        return bdf2_residual, None, par(higher_order_start, 2)

    elif label == 'bdf3':
        return bdf3_residual, None, par(higher_order_start, 3)

    elif label == 'bdf2 mp':
        adaptor = par(general_time_adaptor,
                      lte_calculator=bdf2_mp_lte_estimate,
                      method_order=2)
        return (bdf2_residual, adaptor, par(higher_order_start, 3))

    elif label == 'bdf2 ebdf3':
        dydt_func = _method_dict.get('dydt_func', None)
        lte_est = par(ebdf3_lte_estimate, dydt_func=dydt_func)
        adaptor = par(general_time_adaptor,
                      lte_calculator=lte_est,
                      method_order=2)
        return (bdf2_residual, adaptor, par(higher_order_start, 4))

    elif label == 'bdf1':
        return bdf1_residual, None, None

    elif label == 'imr':
        return imr_residual, None, None

    elif label == 'imr ebdf3':
        dydt_func = _method_dict.get('dydt_func', None)
        lte_est = par(ebdf3_lte_estimate, dydt_func=dydt_func)
        adaptor = par(general_time_adaptor,
                      lte_calculator=lte_est,
                      method_order=2)
        return imr_residual, adaptor, par(higher_order_start, 5)

    elif label == 'imr w18':
        import simpleode.algebra.two_predictor as tp
        p1_points = _method_dict['p1_points']
        p1_pred = _method_dict['p1_pred']

        p2_points = _method_dict['p2_points']
        p2_pred = _method_dict['p2_pred']

        symbolic = _method_dict['symbolic']

        lte_est = tp.generate_predictor_pair_lte_est(
            *tp.generate_predictor_pair_scheme(p1_points, p1_pred,
                                               p2_points, p2_pred,
                                               symbolic=symbolic))

        adaptor = par(general_time_adaptor,
                      lte_calculator=lte_est,
                      method_order=2)

        # ??ds don't actually need this many history values to start but it
        # makes things easier so use this many for now.
        return imr_residual, adaptor, par(higher_order_start, 12)

    elif label == 'trapezoid' or label == 'tr':
        # TR is actually self starting but due to technicalities with
        # getting derivatives of y from implicit formulas we need an extra
        # starting point.
        return TrapezoidRuleResidual(), None, par(higher_order_start, 2)

    elif label == 'tr ab':

        dydt_func = _method_dict.get('dydt_func')

        adaptor = par(general_time_adaptor,
                      lte_calculator=tr_ab_lte_estimate,
                      dydt_func=dydt_func,
                      method_order=2)

        return TrapezoidRuleResidual(), adaptor, par(higher_order_start, 2)

    else:
        message = "Method '"+label+"' not recognised."
        raise ValueError(message)
Esempio n. 22
0
            (-(-(ynm1 - ynm2) / dtnm2 + (yn - ynm1) / dtnm1) /
             (dtnm1 + dtnm2) + (-(yn - ynm1) / dtnm1 + (ynp1 - yn) / dtn) /
             (dtn + dtnm1)) / (dtn + dtnm1 + dtnm2) + dtn * (dtn + dtnm1) *
            ((-(-(ynm1 - ynm2) / dtnm2 + (yn - ynm1) / dtnm1) /
              (dtnm1 + dtnm2) + (-(yn - ynm1) / dtnm1 + (ynp1 - yn) / dtn) /
              (dtn + dtnm1)) / (dtn + dtnm1 + dtnm2) -
             (-(-(ynm2 - ynm3) / dtnm3 + (ynm1 - ynm2) / dtnm2) /
              (dtnm2 + dtnm3) + (-(ynm1 - ynm2) / dtnm2 +
                                 (yn - ynm1) / dtnm1) / (dtnm1 + dtnm2)) /
             (dtnm1 + dtnm2 + dtnm3)) * (dtn + dtnm1 + dtnm2) /
            (dtn + dtnm1 + dtnm2 + dtnm3) + dtn * (-(yn - ynm1) / dtnm1 +
                                                   (ynp1 - yn) / dtn) /
            (dtn + dtnm1) + (ynp1 - yn) / dtn)


bdf2_residual = par(bdf_residual, dydt_func=bdf2_dydt)
bdf3_residual = par(bdf_residual, dydt_func=bdf3_dydt)
bdf4_residual = par(bdf_residual, dydt_func=bdf4_dydt)


# Assumption: we never actually undo a timestep (otherwise dys will become
# out of sync with ys).
class TrapezoidRuleResidual(object):
    """A class to calculate trapezoid rule residuals.

    We need a class because we need to store the past data. Other residual
    calculations do not.
    """
    def __init__(self):
        self.dys = []
Esempio n. 23
0
def test_tr_ab2_scheme_generation():
    """Make sure tr-ab can be derived using same methodology as I'm using
    (also checks lte expressions).
    """

    dddy = sympy.symbols("y'''")
    y_np1_tr = sympy.symbols("y_{n+1}_tr")

    ab_pred, ynp1_ab2_expr = generate_predictor_scheme([
        PTInfo(0, None, None),
        PTInfo(1, "corr_val", "exp test"),
        PTInfo(2, "corr_val", "exp test")
    ],
                                                       "ab2",
                                                       symbolic=sympy.exp(St))

    # ??ds hacky, have to change the symbol to represent where y''' is being
    # evaluated by hand!
    ynp1_ab2_expr = ynp1_ab2_expr.subs(Sdddynph, dddy)

    # Check that it gives the same result as we know from the lte
    utils.assert_sym_eq(y_np1_exact - ab2_lte(Sdts[0], Sdts[1], dddy),
                        ynp1_ab2_expr)

    # Now do the solve etc.
    y_np1_tr_expr = y_np1_exact - tr_lte(Sdts[0], dddy)
    A = system2matrix([ynp1_ab2_expr, y_np1_tr_expr], [dddy, y_np1_exact])
    x = A.inv()

    exact_ynp1_symb = sum([
        y_est * xi.factor()
        for xi, y_est in zip(x.row(1), [y_np1_p1, y_np1_tr])
    ])

    exact_ynp1_f = sympy.lambdify((y_np1_p1, y_np1_tr, Sdts[0], Sdts[1]),
                                  exact_ynp1_symb)

    utils.assert_sym_eq(exact_ynp1_symb - y_np1_tr,
                        (y_np1_p1 - y_np1_tr) / (3 * (1 + Sdts[1] / Sdts[0])))

    # Construct an lte estimator from this estimate
    def lte_est(ts, ys):
        ynp1_p = ab_pred(ts, ys)

        dtn = ts[-1] - ts[-2]
        dtnm1 = ts[-2] - ts[-3]

        ynp1_exact = exact_ynp1_f(ynp1_p, ys[-1], dtn, dtnm1)
        return ynp1_exact - ys[-1]

    # Solve exp using tr
    t0 = 0.0
    dt = 1e-2
    ts, ys = ode.odeint(er.exp_residual,
                        er.exp_exact(t0),
                        tmax=2.0,
                        dt=dt,
                        method='tr')

    # Get error estimates using standard tr ab and the one we just
    # constructed here, then compare.
    this_ltes = ode.get_ltes_from_data(ts, ys, lte_est)

    tr_ab_lte = par(ode.tr_ab_lte_estimate, dydt_func=lambda t, y: y)
    standard_ltes = ode.get_ltes_from_data(ts, ys, tr_ab_lte)

    # Should be the same (actually the sign is different, but this doesn't
    # matter in lte).
    utils.assert_list_almost_equal(this_ltes,
                                   map(lambda a: a * -1, standard_ltes), 1e-8)
def test_tr_ab2_scheme_generation():
    """Make sure tr-ab can be derived using same methodology as I'm using
    (also checks lte expressions).
    """

    dddy = sympy.symbols("y'''")
    y_np1_tr = sympy.symbols("y_{n+1}_tr")

    ab_pred, ynp1_ab2_expr = generate_predictor_scheme([PTInfo(0, None, None),
                                                        PTInfo(
                                                            1,
                                                            "corr_val",
                                                            "exp test"),
                                                        PTInfo(
                                                            2, "corr_val", "exp test")],
                                                       "ab2",
                                                       symbolic=sympy.exp(St))

    # ??ds hacky, have to change the symbol to represent where y''' is being
    # evaluated by hand!
    ynp1_ab2_expr = ynp1_ab2_expr.subs(Sdddynph, dddy)

    # Check that it gives the same result as we know from the lte
    utils.assert_sym_eq(y_np1_exact - ab2_lte(Sdts[0], Sdts[1], dddy),
                        ynp1_ab2_expr)

    # Now do the solve etc.
    y_np1_tr_expr = y_np1_exact - tr_lte(Sdts[0], dddy)
    A = system2matrix([ynp1_ab2_expr, y_np1_tr_expr], [dddy, y_np1_exact])
    x = A.inv()

    exact_ynp1_symb = sum([y_est * xi.factor() for xi, y_est in
                           zip(x.row(1), [y_np1_p1, y_np1_tr])])

    exact_ynp1_f = sympy.lambdify(
        (y_np1_p1,
         y_np1_tr,
         Sdts[0],
         Sdts[1]),
        exact_ynp1_symb)

    utils.assert_sym_eq(exact_ynp1_symb - y_np1_tr,
                        (y_np1_p1 - y_np1_tr)/(3*(1 + Sdts[1]/Sdts[0])))

    # Construct an lte estimator from this estimate
    def lte_est(ts, ys):
        ynp1_p = ab_pred(ts, ys)

        dtn = ts[-1] - ts[-2]
        dtnm1 = ts[-2] - ts[-3]

        ynp1_exact = exact_ynp1_f(ynp1_p, ys[-1], dtn, dtnm1)
        return ynp1_exact - ys[-1]

    # Solve exp using tr
    t0 = 0.0
    dt = 1e-2
    ts, ys = ode.odeint(er.exp_residual, er.exp_exact(t0),
                        tmax=2.0, dt=dt, method='tr')

    # Get error estimates using standard tr ab and the one we just
    # constructed here, then compare.
    this_ltes = ode.get_ltes_from_data(ts, ys, lte_est)

    tr_ab_lte = par(ode.tr_ab_lte_estimate, dydt_func=lambda t, y: y)
    standard_ltes = ode.get_ltes_from_data(ts, ys, tr_ab_lte)

    # Should be the same (actually the sign is different, but this doesn't
    # matter in lte).
    utils.assert_list_almost_equal(
        this_ltes, map(lambda a: a*-1, standard_ltes), 1e-8)
def generate_predictor_scheme(pt_infos, predictor_name, symbolic=None):

    # Extract symbolic expressions from what we're given.
    if symbolic is not None:
        try:
            symb_exact, symb_F = symbolic

        except TypeError:  # not iterable
            symb_exact = symbolic

            Sy = sympy.symbols('y', real=True)
            symb_dy = sympy.diff(symb_exact, St, 1).subs(symb_exact, Sy)
            symb_F = sympy.diff(symb_dy, Sy).subs(Sy, symb_exact)

        dydt_func = sympy.lambdify(St, sympy.diff(symb_exact, St, 1))
        f_y = sympy.lambdify(St, symb_exact)

    else:
        dydt_func = None
        f_y = None

    # To cancel errors we need the final step to be at t_np1
    # assert pt_infos[0].time == 0
    n_hist = len(pt_infos) + 5

    # Create symbolic and python functions of (corrector) dts and ts
    # respectively that give the appropriate dts for this predictor.
    p_dtns = []
    p_dtn_funcs = []
    for pt1, pt2 in zip(pt_infos[:-1], pt_infos[1:]):
        p_dtns.append(sum_dts(pt1.time, pt2.time))
        p_dtn_funcs.append(generate_p_dt_func(p_dtns[-1]))

    # For each time point the predictor uses construct symbolic error
    # estimates for the requested estimate at that point and python
    # functions to calculate the value of the estimate.
    y_errors = map(ptinfo2yerr, pt_infos)
    y_funcs = map(par(ptinfo2yfunc, y_of_t_func=f_y), pt_infos)
    dy_errors = map(ptinfo2dyerr, pt_infos)
    dy_funcs = map(par(ptinfo2dyfunc, dydt_func=dydt_func), pt_infos)

    # Construct errors and function for the predictor
    # ============================================================
    if predictor_name == "ebdf2" or predictor_name == "wrong step ebdf2":

        if predictor_name == "wrong step ebdf2":
            temp_p_dtnm1 = p_dtns[1] + Sdts[0]
        else:
            temp_p_dtnm1 = p_dtns[1]

        y_np1_p_expr = y_np1_exact - (
            # Natural ebdf2 lte:
            ebdf2_lte(p_dtns[0], temp_p_dtnm1, Sdddynph)
            # error due to approximation to derivative at tn:
            + ode.ebdf2_step(p_dtns[0], 0, dy_errors[1], p_dtns[1], 0)
            # error due to approximation to yn
            + ode.ebdf2_step(p_dtns[0], y_errors[1], 0, p_dtns[1], 0)
            # error due to approximation to ynm1 (typically zero)
            + ode.ebdf2_step(p_dtns[0], 0, 0, p_dtns[1], y_errors[2]))

        def predictor_func(ts, ys):

            dtn = p_dtn_funcs[0](ts)
            yn = y_funcs[1](ts, ys)
            dyn = dy_funcs[1](ts, ys)

            dtnm1 = p_dtn_funcs[1](ts)
            ynm1 = y_funcs[2](ts, ys)
            return ode.ebdf2_step(dtn, yn, dyn, dtnm1, ynm1)

    elif predictor_name == "ab2":

        y_np1_p_expr = y_np1_exact - (
            # Natural ab2 lte:
            ab2_lte(p_dtns[0], p_dtns[1], Sdddynph)
            # error due to approximation to derivative at tn
            + ode.ab2_step(p_dtns[0], 0, dy_errors[1], p_dtns[1], 0)
            # error due to approximation to derivative at tnm1
            + ode.ab2_step(p_dtns[0], 0, 0, p_dtns[1], dy_errors[2])
            # error due to approximation to yn
            + ode.ab2_step(p_dtns[0], y_errors[1], 0, p_dtns[1], 0))

        def predictor_func(ts, ys):

            dtn = p_dtn_funcs[0](ts)
            yn = y_funcs[1](ts, ys)
            dyn = dy_funcs[1](ts, ys)

            dtnm1 = p_dtn_funcs[1](ts)
            dynm1 = dy_funcs[2](ts, ys)

            return ode.ab2_step(dtn, yn, dyn, dtnm1, dynm1)

    elif predictor_name == "ibdf2":

        y_np1_p_expr = y_np1_exact - (
            # Natural bdf2 lte:
            bdf2_lte(p_dtns[0], p_dtns[1], Sdddynph)
            # error due to approximation to derivative at tnp1
            + ode.ibdf2_step(p_dtns[0], 0, dy_errors[0], p_dtns[1], 0)
            # errors due to approximations to y at tn and tnm1
            + ode.ibdf2_step(p_dtns[0], y_errors[1], 0, p_dtns[1], 0)
            + ode.ibdf2_step(p_dtns[0], 0, 0, p_dtns[1], y_errors[2]))

        def predictor_func(ts, ys):

            pdts = [f(ts) for f in p_dtn_funcs]

            dynp1 = dy_funcs[0](ts, ys)
            yn = y_funcs[1](ts, ys)
            ynm1 = y_funcs[2](ts, ys)

            tsin = [t_at_time_point(ts, pt.time)
                    for pt in reversed(pt_infos[1:])]
            ysin = [ys[-(pt.time+1)] for pt in reversed(pt_infos[1:])]
            dt = pdts[0]

            return ode.ibdf2_step(pdts[0], yn, dynp1, pdts[1], ynm1)

    elif predictor_name == "ibdf3":

        y_np1_p_expr = y_np1_exact - (
            # Natural bdf2 lte:
            bdf3_lte(p_dtns[0], p_dtns[1], Sdddynph)
            # error due to approximation to derivative at tnp1
            + ode.ibdf3_step(
                dy_errors[0],
                p_dtns[0],
                0,
                p_dtns[1],
                0,
                p_dtns[2],
                0)
            # errors due to approximations to y at tn, tnm1, tnm2
            + ode.ibdf3_step(
                0,
                p_dtns[0],
                y_errors[1],
                p_dtns[1],
                0,
                p_dtns[2],
                0)
            + ode.ibdf3_step(
                0,
                p_dtns[0],
                0,
                p_dtns[1],
                y_errors[2],
                p_dtns[2],
                0)
            + ode.ibdf3_step(0, p_dtns[0], 0, p_dtns[1], 0, p_dtns[2], y_errors[3]))

        def predictor_func(ts, ys):

            pdts = [f(ts) for f in p_dtn_funcs]

            dynp1 = dy_funcs[0](ts, ys)
            yn = y_funcs[1](ts, ys)
            ynm1 = y_funcs[2](ts, ys)
            ynm2 = y_funcs[3](ts, ys)

            return ode.ibdf3_step(dynp1, pdts[0], yn, pdts[1], ynm1,
                                  pdts[2], ynm2)

    elif predictor_name == "ebdf3":
        y_np1_p_expr = y_np1_exact - (
            # Natural ebdf3 lte error:
            ebdf3_lte(p_dtns[0], p_dtns[1], p_dtns[2], Sdddynph)
            # error due to approximation to derivative at tn
            + ode.ebdf3_step(
                p_dtns[0],
                0,
                dy_errors[1],
                p_dtns[1],
                0,
                p_dtns[2],
                0)
            # errors due to approximation to y at tn, tnm1, tnm2
            + ode.ebdf3_step(p_dtns[0], y_errors[1], 0,
                             p_dtns[1], 0, p_dtns[2], 0)
            + ode.ebdf3_step(p_dtns[0], 0, 0,
                             p_dtns[1], y_errors[2], p_dtns[2], 0)
            + ode.ebdf3_step(p_dtns[0], 0, 0,
                             p_dtns[1], 0, p_dtns[2], y_errors[3]))

        def predictor_func(ts, ys):

            pdts = [f(ts) for f in p_dtn_funcs]

            dyn = dy_funcs[1](ts, ys)
            yn = y_funcs[1](ts, ys)
            ynm1 = y_funcs[2](ts, ys)
            ynm2 = y_funcs[3](ts, ys)

            return ode.ebdf3_step(pdts[0], yn, dyn,
                                  pdts[1], ynm1,
                                  pdts[2], ynm2)

    elif predictor_name == "use exact dddy":

        symb_dddy = sympy.diff(symb_exact, St, 3)
        f_dddy = sympy.lambdify(St, symb_dddy)

        print symb_exact
        print "dddy =", symb_dddy

        # "Predictor" is just exactly dddy, error forumlated so that matrix
        # turns out right. Calculate at one before last point (same as lte
        # "location").
        def predictor_func(ts, ys):
            tn = t_at_time_point(ts, pt_infos[1].time)
            return f_dddy(tn)

        y_np1_p_expr = Sdddynph

    elif predictor_name == "use exact Fddy":

        symb_ddy = sympy.diff(symb_exact, St, 2)
        f_Fddy = sympy.lambdify(St, symb_F * symb_ddy)

        print "Fddy =", symb_F * symb_ddy

        # "Predictor" is just exactly dddy, error forumlated so that matrix
        # turns out right. Calculate at one before last point (same as lte
        # "location").
        def predictor_func(ts, ys):
            tn = t_at_time_point(ts, pt_infos[1].time)
            return f_Fddy(tn)

        y_np1_p_expr = SFddynph

    else:
        raise ValueError("Unrecognised predictor name " + predictor_name)

    return predictor_func, y_np1_p_expr