def interpolate(y, times, rhs, new_times, args=()): n = y.shape[1] y_out = np.empty((len(new_times), n), dtype=y.dtype) # this is the index into new_times j = 0 for i, t in enumerate(times[:-1]): t0 = times[i] t1 = times[i+1] new_t0 = new_times[j] if new_t0 > t1: continue # construct interpolator y_interp = CubicHermiteInterpolate( t0, t1, y[i], y[i+1], rhs(t0, y[i], *args), rhs(t1, y[i+1], *args) ) # interpolate while new_t0 <= t1: y_out[j] = y_interp(new_t0) j += 1 if j == len(new_times): return y_out new_t0 = new_times[j] return y_out
def test_interpolate_grad(self): def rhs(t, y): return -y t0 = 0.1 t1 = 0.2 y0 = np.exp(-t0) y1 = np.exp(-t1) y = CubicHermiteInterpolate(t0, t1, y0, y1, rhs(t0, y0), rhs(t1, y1)) times = np.linspace(t0, t1, 100) interp_grad = np.empty(len(times)) for i in range(len(times)): interp_grad[i] = y.grad(times[i]) analytic_grad = -np.exp(-times) np.testing.assert_allclose(analytic_grad, interp_grad, rtol=1e-5, atol=0)
def adjoint_error_and_sensitivities_interp( rhs, jac, drhs_dp, functional, dfunc_dy, times, y, args=(), method=runge_kutta5 ): T = len(times) n = y.shape[1] # define the adjoint error equations def adjoint_error_and_sensitivities( t, state, y_interp, *args ): phi = state[:n] yt = y_interp(t) return np.concatenate(( -jac(t, yt, phi, *args) - dfunc_dy(t, yt), [-functional(t, yt)], (rhs(t, yt, *args) - y_interp.grad(t)) * phi, -drhs_dp(t, yt, phi, *args), )) phi = np.zeros(n, dtype=y.dtype) n_params = len(drhs_dp(times[-1], y[-1], phi, *args)) phi_f_error_dJdp = np.zeros(n + 2 + n_params, dtype=y.dtype) error = np.empty(T-1, dtype=y.dtype) rhs0 = rhs(times[-1], y[-1], *args) for i in reversed(range(len(times)-1)): t0 = times[i] t1 = times[i+1] phi_error1 = phi_f_error_dJdp[-1-n_params] rhs1 = rhs0 rhs0 = rhs(t0, y[i], *args) y_interp = CubicHermiteInterpolate( t0, t1, y[i], y[i+1], rhs0, rhs1 ) # integrate to t0 phi_f_error_dJdp = method(adjoint_error_and_sensitivities, t1, t0-t1, phi_f_error_dJdp, (y_interp,) + args) #phi_f_error_dJdp = method(adjoint_error_and_sensitivities, # t1, 0.5*(t0-t1), # phi_f_error_dJdp, (y_interp,) + args) #phi_f_error_dJdp = method(adjoint_error_and_sensitivities, # 0.5*(t0+t1), 0.5*(t0-t1), # phi_f_error_dJdp, (y_interp,) + args) error[i] = phi_f_error_dJdp[-1-n_params] - phi_error1 total_error = phi_f_error_dJdp[-1-n_params] sensitivities = phi_f_error_dJdp[-n_params:] f = phi_f_error_dJdp[-2-n_params] return total_error, error, f, sensitivities
def test_interpolate2d(self): def rhs(t, y): return -y t0 = 0.1 t1 = 0.2 y0 = np.array([np.exp(-t0), 2 * np.exp(-t0)]) y1 = np.array([np.exp(-t1), 2 * np.exp(-t1)]) y = CubicHermiteInterpolate(t0, t1, y0, y1, rhs(t0, y0), rhs(t1, y1)) times = np.linspace(t0, t1, 100) interp_y = np.empty((len(times), len(y0))) for i in range(len(times)): interp_y[i] = y(times[i]) analytic = np.stack((np.exp(-times), 2 * np.exp(-times)), axis=1) np.testing.assert_allclose(analytic, interp_y, rtol=1e-5, atol=0)
def adjoint_sensitivities_error( rhs, jac, drhs_dp, dfunc_dy, times, y, method=runge_kutta5 ): T = len(times) if T != y.shape[0]: raise RuntimeError( 'y (shape={}) should have length {}' .format(y.shape, T) ) n = y.shape[1] def adjoint_sensitivities(t, phi_dJdp, y_interp): phi = phi_dJdp[:n] return np.concatenate(( -jac(t, y_interp(t), phi), -drhs_dp(t, y_interp(t), phi), )) rhs_s = adjoint_sensitivities Ju = dfunc_dy(y) if Ju.shape[0] != T: raise RuntimeError( 'dfunc_dy (shape={}) should return length {}' .format(Ju.shape, T) ) phi = np.zeros(n, dtype=y.dtype) n_params = len(drhs_dp(times[-1], y[-1], phi)) phi_dJdp = np.zeros(n + n_params, dtype=y.dtype) for i in reversed(range(len(times)-1)): # integrate over delta function phi_dJdp[:n] += Ju[i+1] t0 = times[i] t1 = times[i+1] y_interp = CubicHermiteInterpolate( t0, t1, y[i], y[i+1], rhs(t0, y[i]), rhs(t1, y[i+1]) ) # integrate phi_dJdp = method(adjoint_sensitivities, t1, t0-t1, phi_dJdp, (y_interp,)) return phi_dJdp[n:]
def adjoint_error_single_times( rhs, jac, dfunc_dy, times, y, method=runge_kutta5 ): T = len(times) if T != y.shape[0]: raise RuntimeError( 'y (shape={}) should have length {}' .format(y.shape, T) ) n = y.shape[1] def adjoint_error(t, phi_error, y_interp): phi = phi_error[:n] return np.concatenate(( -jac(t, y_interp(t), phi), (rhs(t, y_interp(t)) - y_interp.grad(t)) * phi, )) Ju = dfunc_dy(y) if Ju.shape[0] != T: raise RuntimeError( 'dfunc_dy (shape={}) should return length {}' .format(Ju.shape, T) ) phi_error = np.zeros(n + 1, dtype=y.dtype) error = np.empty(T-1, dtype=y.dtype) for i in reversed(range(len(times)-1)): # integrate over delta function phi_error[:n] += Ju[i+1] t0 = times[i] t1 = times[i+1] y_interp = CubicHermiteInterpolate( t0, t1, y[i], y[i+1], rhs(t0, y[i]), rhs(t1, y[i+1]) ) # integrate new_phi_error = method(adjoint_error, t1, t0-t1, phi_error, (y_interp,)) error[i] = new_phi_error[-1] - phi_error[-1] phi_error = new_phi_error return phi_error[-1], error
def adjoint_error( rhs, jac, functional, dfunc_dy, ftimes, times, y, method=runge_kutta5 ): T = len(times) fT = len(ftimes) n = y.shape[1] rhs_calls = 0 method_calls = 0 # calculate the value and derivative of the function wrt y fy = interpolate(y, times, rhs, ftimes) Ju = dfunc_dy(fy) f = functional(fy) if Ju.shape[0] != fT: raise RuntimeError( 'dfunc_dy (shape={}) should return length {}' .format(Ju.shape, fT) ) # define the adjoint error equations def adjoint_error(t, phi_error, y_interp): phi = phi_error[:n] return np.concatenate(( -jac(t, y_interp(t), phi), (rhs(t, y_interp(t)) - y_interp.grad(t)) * phi, )) phi_error = np.zeros(n + 1, dtype=y.dtype) error = np.empty(T-1, dtype=y.dtype) # this is index into ftimes j = len(ftimes) - 1 for i in reversed(range(len(times)-1)): t0 = times[i] t1 = times[i+1] ft = ftimes[j] t = t1 phi_error1 = phi_error[-1] y_interp = CubicHermiteInterpolate( t0, t1, y[i], y[i+1], rhs(t0, y[i]), rhs(t1, y[i+1]) ) rhs_calls += 2 # ft could be == to t1 if ft >= t: phi_error[:n] += Ju[j] j -= 1 ft = ftimes[j] # break segment according to location of ftimes while ft > t0: # integrate phi_error = method(adjoint_error, t, ft-t, phi_error, (y_interp,)) method_calls += 1 # integrate over delta function phi_error[:n] += Ju[j] # go to new time point j -= 1 t = ft ft = ftimes[j] # ft is now <= to t0, integrate to t0 phi_error = method(adjoint_error, t, t0-t, phi_error, (y_interp,)) method_calls += 1 error[i] = phi_error[-1] - phi_error1 if method == runge_kutta5: method_to_func_calls = 6 else: method_to_func_calls = np.nan rhs_calls += method_to_func_calls * method_calls jac_calls = method_to_func_calls * method_calls return phi_error[-1], error, f, rhs_calls, jac_calls
def adjoint_error_and_sensitivities_independent_ftimes( rhs, jac, drhs_dp, functional, dfunc_dy, fy, ftimes, times, y, args=(), method=runge_kutta5 ): T = len(times) fT = len(ftimes) n = y.shape[1] # calculate the value and derivative of the function wrt y Ju = dfunc_dy(fy) f = functional(fy) if Ju.shape[0] != fT: raise RuntimeError( 'dfunc_dy (shape={}) should return length {}' .format(Ju.shape, fT) ) # define the adjoint error equations def adjoint_error_and_sensitivities( t, phi_error_dJdp, y_interp, *args ): phi = phi_error_dJdp[:n] return np.concatenate(( -jac(t, y_interp(t), phi, *args), (rhs(t, y_interp(t), *args) - y_interp.grad(t)) * phi, -drhs_dp(t, y_interp(t), phi, *args), )) phi = np.zeros(n, dtype=y.dtype) n_params = len(drhs_dp(times[-1], y[-1], phi, *args)) phi_error_dJdp = np.zeros(n + 1 + n_params, dtype=y.dtype) error = np.empty(T-1, dtype=y.dtype) # this is index into ftimes j = len(ftimes) - 1 rhs0 = rhs(times[-1], y[-1], *args) for i in reversed(range(len(times)-1)): t0 = times[i] t1 = times[i+1] ft = ftimes[j] t = t1 phi_error1 = phi_error_dJdp[-1-n_params] rhs1 = rhs0 rhs0 = rhs(t0, y[i], *args) y_interp = CubicHermiteInterpolate( t0, t1, y[i], y[i+1], rhs0, rhs1 ) # ft could be == to t1 if ft >= t: phi_error_dJdp[:n] += Ju[j] j -= 1 ft = ftimes[j] # break segment according to location of ftimes while ft > t0: # integrate phi_error_dJdp = method(adjoint_error_and_sensitivities, t, ft-t, phi_error_dJdp, (y_interp,) + args) # integrate over delta function phi_error_dJdp[:n] += Ju[j] # go to new time point j -= 1 t = ft ft = ftimes[j] # ft is now <= to t0, integrate to t0 phi_error_dJdp = method(adjoint_error_and_sensitivities, t, t0-t, phi_error_dJdp, (y_interp,) + args) error[i] = phi_error_dJdp[-1-n_params] - phi_error1 return phi_error_dJdp[-1-n_params], error, \ f, phi_error_dJdp[-n_params:]
def adjoint_error_and_sensitivities( rhs, jac, drhs_dp, functional, dfunc_dy, fix_times, times, y, args=(), method=runge_kutta5 ): T = len(times) n = y.shape[1] # calculate the value and derivative of the function wrt y fy = y[fix_times] Ju = dfunc_dy(fy) f = functional(fy) if Ju.shape != fy.shape: raise RuntimeError( 'dfunc_dy (shape={}) should return shape {}' .format(Ju.shape, fy.shape) ) # define the adjoint error equations def adjoint_error_and_sensitivities( t, phi_error_dJdp, y_interp, *args ): phi = phi_error_dJdp[:n] return np.concatenate(( -jac(t, y_interp(t), phi, *args), (rhs(t, y_interp(t), *args) - y_interp.grad(t)) * phi, -drhs_dp(t, y_interp(t), phi, *args), )) phi = np.zeros(n, dtype=y.dtype) n_params = len(drhs_dp(times[-1], y[-1], phi, *args)) phi_error_dJdp = np.zeros(n + 1 + n_params, dtype=y.dtype) error = np.empty(T-1, dtype=y.dtype) # this is index into ftimes j = len(fy) - 1 rhs0 = rhs(times[-1], y[-1], *args) for i in reversed(range(len(times)-1)): t0 = times[i] t1 = times[i+1] t = t1 phi_error1 = phi_error_dJdp[-1-n_params] rhs1 = rhs0 rhs0 = rhs(t0, y[i], *args) y_interp = CubicHermiteInterpolate( t0, t1, y[i], y[i+1], rhs0, rhs1 ) # if t1 is a fix time then integrate over delta function if fix_times[i+1]: phi_error_dJdp[:n] += Ju[j] j -= 1 # integrate to t0 phi_error_dJdp = method(adjoint_error_and_sensitivities, t, t0-t, phi_error_dJdp, (y_interp,) + args) error[i] = phi_error_dJdp[-1-n_params] - phi_error1 return phi_error_dJdp[-1-n_params], error, \ f, phi_error_dJdp[-n_params:]