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)
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)
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):
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
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
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)
(-(-(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 = []
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