def test_step_variables(solvers, y, start_point, stop_point): testsolver, scipysolver = solvers solver_y_new, solver_error_estimation = testsolver.step( start_point, stop_point, randvars.Constant(y)) y_new, f_new = rk.rk_step( scipysolver.fun, start_point, y, scipysolver.f, stop_point - start_point, scipysolver.A, scipysolver.B, scipysolver.C, scipysolver.K, ) # error estimation is correct scipy_error_estimation = scipysolver._estimate_error( scipysolver.K, stop_point - start_point) np.testing.assert_allclose(solver_error_estimation, scipy_error_estimation, atol=1e-14, rtol=1e-14) # locations are correct np.testing.assert_allclose(testsolver.solver.t_old, start_point, atol=1e-14, rtol=1e-14) np.testing.assert_allclose(testsolver.solver.t, stop_point, atol=1e-14, rtol=1e-14) np.testing.assert_allclose( testsolver.solver.h_previous, stop_point - start_point, atol=1e-14, rtol=1e-14, ) # evaluations are correct np.testing.assert_allclose(testsolver.solver.y_old.mean, y, atol=1e-14, rtol=1e-14) np.testing.assert_allclose(testsolver.solver.y, y_new, atol=1e-14, rtol=1e-14) np.testing.assert_allclose(testsolver.solver.h_abs, stop_point - start_point, atol=1e-14, rtol=1e-14) np.testing.assert_allclose(testsolver.solver.f, f_new, atol=1e-14, rtol=1e-14)
def attempt_step(self, state: _odesolver_state.ODESolverState, dt: FloatLike): """Perform one ODE-step from start to stop and set variables to the corresponding values. To specify start and stop directly, rk_step() and not _step_impl() is used. Parameters ---------- state Current state of the ODE solver. dt Step-size. Returns ------- _odesolver_state.ODESolverState New state. """ y_new, f_new = rk.rk_step( self.solver.fun, state.t, state.rv.mean, self.solver.f, dt, self.solver.A, self.solver.B, self.solver.C, self.solver.K, ) # Unnormalized error estimation is used as the error estimation is normalized in # solve(). error_estimation = self.solver._estimate_error(self.solver.K, dt) y_new_as_rv = randvars.Constant(y_new) new_state = _odesolver_state.ODESolverState( ivp=state.ivp, rv=y_new_as_rv, t=state.t + dt, error_estimate=error_estimation, reference_state=state.rv.mean, ) # Update the solver settings. This part is copied from scipy's _step_impl(). self.solver.h_previous = dt self.solver.y_old = state.rv.mean self.solver.t_old = state.t self.solver.t = state.t + dt self.solver.y = y_new self.solver.h_abs = dt self.solver.f = f_new return new_state
def step(self, start: FloatArgType, stop: FloatArgType, current: randvars, **kwargs): """Perform one ODE-step from start to stop and set variables to the corresponding values. To specify start and stop directly, rk_step() and not _step_impl() is used. Parameters ---------- start : float starting location of the step stop : float stopping location of the step current : :obj:`list` of :obj:`RandomVariable` current state of the ODE. Returns ------- random_var : randvars.RandomVariable Estimated states of the discrete-time solution. error_estimation : float estimated error after having performed the step. """ y = current.mean dt = stop - start y_new, f_new = rk.rk_step( self.solver.fun, start, y, self.solver.f, dt, self.solver.A, self.solver.B, self.solver.C, self.solver.K, ) # Unnormalized error estimation is used as the error estimation is normalized in # solve(). error_estimation = self.solver._estimate_error(self.solver.K, dt) y_new_as_rv = randvars.Constant(y_new) # Update the solver settings. This part is copied from scipy's _step_impl(). self.solver.h_previous = dt self.solver.y_old = current self.solver.t_old = start self.solver.t = stop self.solver.y = y_new self.solver.h_abs = dt self.solver.f = f_new return y_new_as_rv, error_estimation
def test_step_variables(solvers, y, start_point, stop_point): testsolver, scipysolver, ode = solvers teststate = diffeq.ODESolverState( ivp=ode, rv=randvars.Constant(y), t=start_point, error_estimate=None, reference_state=None, ) testsolver.initialize(ode) solver_y_new = testsolver.attempt_step(teststate, dt=stop_point - start_point) y_new, f_new = rk.rk_step( scipysolver.fun, start_point, y, scipysolver.f, stop_point - start_point, scipysolver.A, scipysolver.B, scipysolver.C, scipysolver.K, ) # error estimation is correct scipy_error_estimation = scipysolver._estimate_error( scipysolver.K, stop_point - start_point ) np.testing.assert_allclose( solver_y_new.error_estimate, scipy_error_estimation, atol=1e-13, rtol=1e-13 ) # locations are correct np.testing.assert_allclose( testsolver.solver.t_old, start_point, atol=1e-13, rtol=1e-13 ) np.testing.assert_allclose(testsolver.solver.t, stop_point, atol=1e-13, rtol=1e-13) np.testing.assert_allclose( testsolver.solver.h_previous, stop_point - start_point, atol=1e-13, rtol=1e-13, ) # evaluations are correct np.testing.assert_allclose(testsolver.solver.y_old, y, atol=1e-13, rtol=1e-13) np.testing.assert_allclose(testsolver.solver.y, y_new, atol=1e-13, rtol=1e-13) np.testing.assert_allclose( testsolver.solver.h_abs, stop_point - start_point, atol=1e-13, rtol=1e-13 ) np.testing.assert_allclose(testsolver.solver.f, f_new, atol=1e-13, rtol=1e-13)
def _step_impl(self): # print("called") t = self.t y = self.y # we will stick to a max_step untill we reach the end h = self.max_step t_new = t + h y_new, f_new, error = rk_step(self.fun, t, y, self.f, h, self.A, self.B, self.C, self.E, self.K) # this error is produced by comparing two RK23 methods # but we will never use it self.y_old = y self.t = t_new self.y = y_new self.h_abs = h self.f = f_new return True, None
def _step_impl(self): t = self.t y = self.y max_step = self.max_step rtol = self.rtol atol = self.atol min_step = 10 * np.abs(np.nextafter(t, self.direction * np.inf) - t) if self.h_abs > max_step: h_abs = max_step elif self.h_abs < min_step: h_abs = min_step else: h_abs = self.h_abs order = self.order step_accepted = False while not step_accepted: if h_abs < min_step: return False, self.TOO_SMALL_STEP h = h_abs * self.direction t_new = t + h if self.direction * (t_new - self.t_bound) > 0: t_new = self.t_bound h = t_new - t h_abs = np.abs(h) y_new, f_new, error = rk_step(self.fun, t, y, self.f, h, self.A, self.B, self.C, self.E, self.K) scale = atol + np.maximum(np.abs(y), np.abs(y_new)) * rtol error_norm = norm(error / scale) if error_norm == 0.0: h_abs *= MAX_FACTOR step_accepted = True elif error_norm < 1: h_abs *= min(MAX_FACTOR, max(1, SAFETY * error_norm ** (-1 / (order + 1)))) step_accepted = True else: if (h_abs<self.min_step_user): step_accepted = True logger.debug("nmin step size reached") else: h_abs *= max(MIN_FACTOR, SAFETY * error_norm ** (-1 / (order + 1))) self.y_old = y self.t = t_new self.y = y_new self.h_abs = h_abs self.f = f_new return True, None