def compute_fixedpoint_numerically(srm, t0, x0, parameter_dict, func_set): B_sym = srm.compartmental_matrix u_sym = srm.external_inputs t = srm.time_symbol tup = tuple(srm.state_vector) + (t,) u_func_non_lin = numerical_function_from_expression( u_sym, tup, parameter_dict, func_set ) B_func_non_lin = numerical_function_from_expression( B_sym, tup, parameter_dict, func_set ) # get functions of x1,...,xn by partly applying to t0 B0_func = func_subs(t, Function("B")(*tup), B_func_non_lin, t0) u0_func = func_subs(t, Function("u")(*tup), u_func_non_lin, t0) # build the kind of function that scipy.optimize.root expects # it has to have a single vector like argument def ex_func(x): tup = tuple(x) return B0_func(*tup) @ x + u0_func(*tup).reshape(x.shape) res = root(fun=ex_func, jac=False, x0=x0) assert(res.success) # chose a method that does not use the jacobian # res = root(fun=ex_func,method='krylov',x0=x0,tol=1e-5) # pe('res',locals()) return res.x
def test_numerical_function_from_expression(self): C_0, C_1, C_2 = symbols('C_0 C_1 C_2') t = Symbol('t') u_0_sym = Function('u_0') u_2_sym = Function('u_2') u_0_expr = u_0_sym(C_0, C_1, t) u_2_expr = u_2_sym(t) X = Matrix([C_0, C_1, C_2]) t_min, t_max = 0, 10 symbolic_input_fluxes = {0: u_0_expr, 2: u_2_expr} def u0_func(C_0_val, C_1_val, t_val): return C_0_val + C_1_val + t_val def u2_func(t_val): return t_val parameter_dict = {} func_set = {u_0_expr: u0_func, u_2_expr: u2_func} tup = (C_0, C_1) + (t, ) u_0_func = numerical_function_from_expression(u_0_expr, tup, parameter_dict, func_set) self.assertEqual(u_0_func(1, 2, 3), 1 + 2 + 3) tup = (t, ) u_2_func = numerical_function_from_expression(u_2_expr, tup, parameter_dict, func_set) self.assertEqual(u_2_func(2), 2) # wrong tup: C_1 is not necessary but it does not hurt # this behavior is convinient to make everything a variable of # ALL statevariables and time tup = ( C_1, t, ) u_2_func = numerical_function_from_expression(u_2_expr, tup, parameter_dict, func_set) # the superflous first argument just does not have any influence self.assertEqual(u_2_func(1002103413131, 2), 2) # wrong tup: C_0 is missing but necessary # this is a real error tup = (C_1, t) with self.assertRaises(Exception) as e: u_0_func = numerical_function_from_expression( u_0_expr, tup, parameter_dict, func_set)
def numfunc(expr): return hr.numerical_function_from_expression( expr=expr, tup=(mvs.get_TimeSymbol(), *mvs.get_StateVariableTuple()), parameter_dict=par_dict, func_set=func_dict )
def _flux_funcs(self, expr_dict): bm = self.bm srm = bm.srm sol_funcs = self.sol_funcs() flux_funcs = {} tup = tuple(bm.state_vector) + (bm.time_symbol, ) for key, expr in expr_dict.items(): if isinstance(expr, Number): # if expr is a number like 5.1 lambdify does not create a vectorized function # so the output is always a number and not an array with identical exprs which is a problem in plots def expr_func(arg_arr): return expr * np.ones_like(arg_arr) flux_funcs[key] = expr_func else: ol = numerical_function_from_expression( expr, tup, self.par_dict, self.func_dict) flux_funcs[key] = f_of_t_maker(sol_funcs, ol) return flux_funcs
alpha = 1 rho = 0.65 f_i = 1 #x_a = start_year*exp(0.0305*time_symbol)/(start_year+exp(0.0305*time_symbol)-1)+284 x_a = start_year * exp(0.0305 * (time_symbol - start_year)) / ( start_year + exp(0.0305 * (time_symbol - start_year)) - 1) + 284 T_s = T_s0 + sigma / log(2) * log(x_a / 285) Gamma = 42.7 + 1.68 * (T_s - 25) + 0.012 * (T_s - 25)**2 beta = 3 * rho * x_a * Gamma / ((rho * x_a - Gamma) * (rho * x_a + 2 * Gamma)) s_i = f_i * alpha * s_0 * (1 + 2.5 * beta * log(x_a / 285)) gpp_industrial = G_emanuel * s_i u_func_numerical = numerical_function_from_expression( gpp_industrial, # sympy expression (time_symbol, ), # tuple of symbols to be replaced by numerical values {}, # parameter dict {} # func dict ) xi_b = 2 xi_t = xi_b**(0.1 * T_s - 1.5) xi_func_numerical = numerical_function_from_expression( xi_t, # sympy expression (time_symbol, ), # tuple of symbols to be replaced by numerical values {}, # parameter dict {} # func dict ) # define a dictionary to connect the symbols with the according functions func_set = {xi: xi_func_numerical, G: u_func_numerical}
def test_stateTransitionOperator_by_different_methods(): # The state transition operator Phi can be used to reproduce the solution k_0_val = 1 k_1_val = 2 x0_0 = np.float(0.5) x0_1 = np.float(1.5) delta_t = np.float(1. / 4.) # var(["x_0", "x_1", "k_0", "k_1", "t", "u"]) # inputs = {0: u, 1: u * t} outputs = {0: k_0 * x_0**2, 1: k_1 * x_1} internal_fluxes = {} svec = Matrix([x_0, x_1]) srm = SmoothReservoirModel(state_vector=svec, time_symbol=t, input_fluxes=inputs, output_fluxes=outputs, internal_fluxes=internal_fluxes) t_0 = 0 t_max = 4 nt = 5 times = np.linspace(t_0, t_max, nt) double_times = np.linspace(t_0, t_max, 2 * (nt - 1) + 1) quad_times = np.linspace(t_0, t_max, 4 * (nt - 1) + 1) parameter_dict = {k_0: k_0_val, k_1: k_1_val, u: 1} func_dict = {} start_x = np.array([x0_0, x0_1]) #make it a column vector for later use #create the model run smr = SmoothModelRun(model=srm, parameter_dict=parameter_dict, start_values=start_x, times=times, func_set=func_dict) smr.build_state_transition_operator_cache(size=4) nr_pools = smr.nr_pools # to be able to compare the results we have to compute them for a # set of n linear independent vectors def baseVector(i): e_i = np.zeros((nr_pools, 1)) e_i[i] = 1 return e_i bvs = [baseVector(i) for i in range(nr_pools)] #pe('Phi_skew(2,1,bvs[0])',locals()) #raise test_times = np.linspace(t_0, t_max, 11) # We now rebuild the solution by means of phi and plot it along with the original solution original_sol, sol_func = smr.solve() u_sym = srm.external_inputs u_num = numerical_function_from_expression(u_sym, (t, ), parameter_dict, {}) def vectorlist2array(l): return np.stack([vec.flatten() for vec in l], 1) # def lists_dict2array_dict(d): # return {key:vectorlist2array(val) for key,val in d.items()} # def continiuous_integral_values(integrator, times): start = time.time() res = vectorlist2array([ integrator( lambda tau: smr._state_transition_operator(t, tau, u_num(tau)), t_0, t) for t in times ]) stop = time.time() exec_time = stop - start #pe('exec_time',locals()) return (times, res, exec_time) def discrete_integral_values(integrator, times): start = time.time() res = vectorlist2array([ integrator( lambda tau: smr._state_transition_operator(t, tau, u_num(tau)), taus=+times[0:i + 1]) for i, t in enumerate(times) ]) stop = time.time() exec_time = stop - start #pe('exec_time',locals()) return (times, res, exec_time) ## reconstruct the solution with Phi and the integrand # x_t=Phi(t,t0)*x_0+int_t0^t Phi(tau,t0)*u(tau) dtau # x_t=a(t)+b(t) et = bvs[0] + bvs[1] phi_arrays = { 'skew': (times, vectorlist2array([ smr._state_transition_operator(t, t_0, et).reshape(srm.nr_pools, 1) for t in times ])) } a_arrays = { 'skew': (times, vectorlist2array([ smr._state_transition_operator(t, t_0, start_x).reshape(srm.nr_pools, 1) for t in times ])), 'trapez1': (times, vectorlist2array([ smr._state_transition_operator(t, t_0, start_x).reshape(srm.nr_pools, 1) for t in times ])), 'trapez2': (double_times, vectorlist2array([ smr._state_transition_operator(t, t_0, start_x).reshape(srm.nr_pools, 1) for t in double_times ])), 'trapez4': (quad_times, vectorlist2array([ smr._state_transition_operator(t, t_0, start_x).reshape(srm.nr_pools, 1) for t in quad_times ])) } nested_boundary_tuples = [(0, t) for t in reversed(times)] b_arrays_trapez = {} b_arrays = { 'skew': continiuous_integral_values(array_integration_by_ode, times), 'trapez1': discrete_integral_values(array_integration_by_values, times), 'trapez2': discrete_integral_values(array_integration_by_values, double_times), 'trapez4': discrete_integral_values(array_integration_by_values, quad_times) } b_arrays_quad = { 'skew': continiuous_integral_values(array_quad_result, times) } x_arrays = { key: (a_arrays[key][0], a_arrays[key][1] + b_arrays[key][1]) for key in a_arrays.keys() } #x_arrays['trapez']=(times,a_arrays['skew'][1]+b_arrays['trapez'][1]) styleDict = OrderedDict({ 'skew': ('green', 6), 'trapez1': ('black', 4), 'trapez2': ('blue', 4), 'trapez4': ('brown', 2) }) def plot_comparison(axl, axr, d): for key in styleDict.keys(): if key in d.keys(): val = d[key] if len(val) == 3: time = "{:7.1e}".format(val[2]) else: time = "" axl.plot(val[0], val[1][0, :], '+', color=styleDict[key][0], markersize=styleDict[key][1], label=key + "[0]" + time) axr.plot(val[0], val[1][1, :], 'x', color=styleDict[key][0], markersize=styleDict[key][1], label=key + "[1]" + time) fig = plt.figure(figsize=(17, 27)) rpn = 5 cpn = 2 r = 1 axl = fig.add_subplot(rpn, cpn, r) plt.title("""phi components, nonlinear part of the system (x[0]) """) axr = fig.add_subplot(rpn, cpn, r + 1) plt.title("""phi components, linear part of the system (x[1]) """) plot_comparison(axl, axr, phi_arrays) axl.legend() r += cpn axl = fig.add_subplot(rpn, cpn, r) plt.title(''' original solution and reconstruction via phi, imprecise for trapez_rule and wrong for the old method ''') axr = fig.add_subplot(rpn, cpn, r + 1) axl.plot(times, original_sol[:, 0], 'o', color='blue', label="original_sol[:,0]") axr.plot(times, original_sol[:, 1], 'o', color='blue', label="original_sol[:,1]") plot_comparison(axl, axr, x_arrays) axl.legend() axr.legend() r += cpn axl = fig.add_subplot(rpn, cpn, r) plt.title('phi(t,ti-0) x0 ') axr = fig.add_subplot(rpn, cpn, r + 1) ax = fig.add_subplot(rpn, cpn, r) plot_comparison(axl, axr, a_arrays) axl.legend() axr.legend() r += cpn axl = fig.add_subplot(rpn, cpn, r) plt.title('\int_{t0}^t phi(tau,t) u(tau) d tau') axr = fig.add_subplot(rpn, cpn, r + 1) plot_comparison(axl, axr, b_arrays) axl.legend() axr.legend() #r+=cpn r += cpn axl = fig.add_subplot(rpn, cpn, r) plt.title('\int_{t0}^t phi(tau,t) u(tau) d tau by quad') axr = fig.add_subplot(rpn, cpn, r + 1) plot_comparison(axl, axr, b_arrays_quad) axl.legend() axr.legend() fig.savefig("solutions.pdf")
def lapm_for_steady_state(srm, t0, parameter_dict, func_set, x0=None): """ If a fixedpoint of the frozen system can be found, create a linear autonomous model as an equivalent for the frozen (generally nonlinear) system there. The function performs the following steps: #. Substitute symbols and symbolic functions with the parameters and numeric functions. #. Transform the general nonlinear non-autonomous system into a nonlinear autonomous system by freezing it at time :math:`t=t_0`: #. Compute :math:`u_0(x)=u(t_0,x_0)` and :math:`B_0(x)=B(t_0,x_0)` #. Look for an equilibrium :math:`x_{fix}` of the frozen system such that :math:`0=B_0(x_{fix})+u_0(x_{fix})`. If the frozen system is linear the we can compute the fixed point explicitly : :math:`x_{fix}=B_0^{-1}u_0`. In general the frozen system will be nonlinear and we will have to look for the fixed point numerically. #. Create a linear autonomous pool model. that can be investigated with the package `LAPM <https://github.com/MPIBGC-TEE/LAPM>`_ This is a special case of the general linearization of a nonlinear model along a trajectory, which in case of a fixed point is constant. At the fixed point the age distribution and solution of the nonlinear system are identical to those of a linear one. Args: srm (SmoothReservoirModel): The (symbolic) model par_set (dict): The parameter set that transforms the symbolic model into a numeric one. The keys are the sympy symbols, the values are the values used for the simulation. func_set (dict): The keys are the symbolic sympy expressions for external functions the values are the numeric functions to be used in the simulation. t0 (float): The time where the non-autonomous system is frozen. x0 (numpy.ndarray): An initial guess to start the fixed point iteration. If the frozen model is linear it will be ignored and can therefore be omitted. Returns: (lapm, x_fix) (tuple): lapm is an instance of (:class:`LAPM.linear_autonomous_pool_model.LinearAutonomousPoolModel`) representing the linearization with respect to the (constant) fixed point trajectory. It yields the same age distribution as the frozen possibly nonlinear system at the fixed point. :math:`x_{fix}` is a one dimensional vector representing the equilibrium. This is returned since it is very likely needed as start vector in the simulation for which the start distributions has been computed. (The computed distribution assumes the system to be in this state.) """ B_sym = srm.compartmental_matrix u_sym = srm.external_inputs if srm.is_linear: if srm.is_state_dependent(u_sym): # in this case we can in principle transform to # a linear Model with constant # input and new B # compute the jacobian of u sv = Matrix(srm.state_vector) M = jacobian(u_sym, sv) u_sym = u_sym - M*sv B_sym = B_sym + M t = srm.time_symbol tup = (t,) u_func = numerical_function_from_expression( u_sym, tup, parameter_dict, func_set ) B_func = numerical_function_from_expression( B_sym, tup, parameter_dict, func_set ) B0 = B_func(t0) u0 = u_func(t0) try: x_fix = (-inv(B0) @ u0).reshape(srm.nr_pools) # pe('x_fix',locals()) except LinAlgError as e: print(""" B_0=B(t_0) is not invertable If a singular matrix B_0 occurs, then the system would have traps. A steady state could then only occour if the components of u0=u(t0) would be zero for the pools connected to the trap. In this (unlikely) event the startage distribution would be ambigous because the fixedpoint x_{fix} is not uniqe (The content of the trap is a free parameter and so is its age.) """) raise e else: if x0 is None: x0 = np.ones(srm.nr_pools) warning( """ No initial guess for the fix point iteration given. For nonlinear models equilibria can in general only be found numerically by an iterative process starting from an initial guess x0 which has not been provided. Since a nonlinear model can have several equilibria the initial guess determines which one will be found by the iteration. A good guess might also increase the likelihood to find an equilibrium at all. In absence of an initial guess we will start with numpy.ones(smr.nr_pools) which might not be a good choice. We strongly advise to specify the x0 argument.""" ) x_fix = compute_fixedpoint_numerically( srm, t0, x0, parameter_dict, func_set ) # pe('x_fix',locals()) t = srm.time_symbol tup = (t,) + tuple(srm.state_vector) u_func_non_lin = numerical_function_from_expression( u_sym, tup, parameter_dict, func_set ) B_func_non_lin = numerical_function_from_expression( B_sym, tup, parameter_dict, func_set ) B0 = B_func_non_lin(t0, *x_fix) u0 = u_func_non_lin(t0, *x_fix) B0_m = Matrix(B0) u0_m = Matrix(u0) # pe('B0',locals()) # pe('u0',locals()) lapm = LinearAutonomousPoolModel(u0_m, B0_m, force_numerical=True) return lapm, x_fix
def test_numeric_steady_state(self): # two-dimensional nonlinear C_0, C_1 = symbols('C_0 C_1') state_vector = [C_0, C_1] t = Symbol('t') input_fluxes = {0: 4, 1: 2} output_fluxes = {0: C_0**2, 1: C_1} internal_fluxes = {} srm = SmoothReservoirModel(state_vector, t, input_fluxes, output_fluxes, internal_fluxes) res = compute_fixedpoint_numerically(srm, t0=0, x0=np.array([1, 1]), parameter_dict={}, func_set={}) ref = np.array([2, 2]) self.assertTrue(np.allclose(res, ref)) self.assertTupleEqual(res.shape, ref.shape) # two-dimensional with external functions # although linear the code will assume u(C_1,C_2,t) to be # nonlinear since it can not check C_0, C_1 = symbols('C_0 C_1') state_vector = [C_0, C_1] t = Symbol('t') f_expr = Function('f')(C_0, t) def f_func(C_0_val, t_val): return C_0_val + t_val func_set = {f_expr: f_func} input_fluxes = {0: f_expr, 1: 2} output_fluxes = {0: 2 * C_0, 1: C_1} internal_fluxes = {} srm = SmoothReservoirModel(state_vector, t, input_fluxes, output_fluxes, internal_fluxes) res = compute_fixedpoint_numerically(srm, t0=2, x0=np.array([4, 4]), parameter_dict={}, func_set=func_set) ref = np.array([2, 2]) self.assertTrue(np.allclose(res, ref)) self.assertTupleEqual(res.shape, ref.shape) # two-dimensional with coupled with linear external functions C_0, C_1 = symbols('C_0 C_1') state_vector = [C_0, C_1] t = Symbol('t') f_expr = Function('f')(t) def f_func(t_val): return np.sin(t_val) func_set = {f_expr: f_func} input_fluxes = {0: C_0 * f_expr, 1: 2} output_fluxes = {0: C_0, 1: C_1} internal_fluxes = {(0, 1): 0.5 * C_0**3} srm = SmoothReservoirModel(state_vector, t, input_fluxes, output_fluxes, internal_fluxes) t0 = 2 res = compute_fixedpoint_numerically(srm, t0=t0, x0=np.array([1, 2]), parameter_dict={}, func_set=func_set) # make sure that the righthandside of the ode is zero F_sym = srm.F F_func = numerical_function_from_expression(F_sym, tup=(C_0, C_1, t), parameter_dict={}, func_set=func_set) F_res = F_func(*res, t0) ref = np.array([0, 0]) self.assertTrue(np.allclose(F_res, ref)) self.assertTupleEqual(res.shape, ref.shape)
# # But this would involve 2 inversions. # To save one inversion we use (A*B)^-1=B^-1*A^-1 return np.matmul(np.linalg.inv(Phi_t0_mat(s)), Phi_t0_mat(t)) if s == t_0: return Phi_t0_mat(t) # fixme: mm 3/9/2019 # the following inversion is very expensive and should be cached or avoided # to save space in a linear succession state Transition operators from step to step # then everything can be reached by a return np.matmul(Phi_t0_mat(t), np.linalg.inv(Phi_t0_mat(s))) tup = (srm.time_symbol, ) + tuple(srm.state_vector) B_func = numerical_function_from_expression(srm.compartmental_matrix, tup, parameter_dict, func_dict) def Phi_direct(t, s): """ For t_0 <s <t we have compute Phi(t,s) directly by integrating d Phi/dt= B(x(tau)) from s to t with the startvalue Phi(s)=UnitMatrix It assumes the existence of a solution x """ check_phi_args(t, s) # the next call will cost nothing if s and t are smaller than t_max # since the ivp caches the solutions up to t_0 after the first call.
# If you look at the result you see that the euler forwar approximation assumes the flux at time t to be constant for the timestep expr_disc # if we assume tat delta_t is 1 day and it counts days # it becomes even simpler expr_disc.subs({delta_t:1}) #Which is the same as if we had 't' replaced in the above formula wiht it # + # this expression we turn now into a numeric funciton of it # although in our example it depends only on t and C_leaf_litter we make it a function of ALL state variables to be able to call it in the same way as u_func and B_func argtup=(mvs.get_TimeSymbol(), *mvs.get_StateVariableTuple()) C_leaf_litter_func = hr.numerical_function_from_expression( expr=expr_cont, tup=argtup, parameter_dict=par_dict, func_set=func_dict ) # call it for testing C_leaf_litter_func(0,*X_0) # + #mass production of output functions def numfunc(expr): return hr.numerical_function_from_expression( expr=expr, tup=(mvs.get_TimeSymbol(), *mvs.get_StateVariableTuple()), parameter_dict=par_dict, func_set=func_dict )
def phi_num(self, tup): bm = self.bm u_num = numerical_function_from_expression(bm.u_expr, tup, self.par_dict, self.func_dict) return u_num