def build(self): self.code_lines.append( f' {constants.DT}_sqrt = {constants.DT} ** 0.5') # 2.1 noise _noise_terms(self.code_lines, self.variables, triple_integral=False) # 2.2 stage 1 _state1(self.code_lines, self.variables, self.parameters) # 2.3 stage 2 # ---- # H1s2 = x + dt * f_H0s1 + dt_sqrt * g_H1s1 # g_H1s2 = g(H1s2, t0, *args) all_H1s2 = [] for var in self.variables: self.code_lines.append( f' {var}_H1s2 = {var} + {constants.DT} * {var}_f_H0s1 + dt_sqrt * {var}_g_H1s1' ) all_H1s2.append(f'{var}_H1s2') g_names = [f'{var}_g_H1s2' for var in self.variables] self.code_lines.append( f' {", ".join(g_names)} = g({", ".join(all_H1s2 + self.parameters)})' ) self.code_lines.append(' ') # 2.4 final stage # ---- # g1 = (I1 - I11 / dt_sqrt + I10 / dt) # g2 = I11 / dt_sqrt # y1 = x + dt * f_H0s1 + g1 * g_H1s1 + g2 * g_H1s2 for var in self.variables: self.code_lines.append( f' {var}_g1 = -{var}_I1 + {var}_I11/dt_sqrt + {var}_I10/{constants.DT}' ) self.code_lines.append(f' {var}_g2 = {var}_I11 / dt_sqrt') self.code_lines.append( f' {var}_new = {var} + {constants.DT} * {var}_f_H0s1 + ' f'{var}_g1 * {var}_g_H1s1 + {var}_g2 * {var}_g_H1s2') self.code_lines.append(' ') # returns new_vars = [f'{var}_new' for var in self.variables] self.code_lines.append(f' return {", ".join(new_vars)}') # return and compile self.integral = utils.compile_code( code_scope={k: v for k, v in self.code_scope.items()}, code_lines=self.code_lines, show_code=self.show_code, func_name=self.func_name)
def build(self): # step stage common.step(self.variables, C.DT, self.A, self.C, self.code_lines, self.parameters) # variable update return_args = common.update(self.variables, C.DT, self.B, self.code_lines) # returns self.code_lines.append(f' return {", ".join(return_args)}') # compile self.integral = utils.compile_code( code_scope={k: v for k, v in self.code_scope.items()}, code_lines=self.code_lines, show_code=self.show_code, func_name=self.func_name)
def build(self): # step stage common.step(self.variables, C.DT, self.A, self.C, self.code_lines, self.parameters) # variable update return_args = common.update(self.variables, C.DT, self.B1, self.code_lines) # error adaptive item if self.adaptive: errors_ = [] for v in self.variables: result = [] for i, (b1, b2) in enumerate(zip(self.B1, self.B2)): if isinstance(b1, str): b1 = eval(b1) if isinstance(b2, str): b2 = eval(b2) diff = b1 - b2 if diff != 0.: result.append(f'd{v}_k{i + 1} * {C.DT} * {diff}') if len(result) > 0: if self.var_type == C.SCALAR_VAR: self.code_lines.append( f' {v}_te = abs({" + ".join(result)})') else: self.code_lines.append( f' {v}_te = sum(abs({" + ".join(result)}))') errors_.append(f'{v}_te') if len(errors_) > 0: self.code_lines.append(f' error = {" + ".join(errors_)}') self.code_lines.append( f' {C.DT}_new = math.where(error > tol, 0.9*{C.DT}*(tol/error)**0.2, {C.DT})' ) return_args.append(f'{C.DT}_new') # returns self.code_lines.append(f' return {", ".join(return_args)}') # compile self.integral = utils.compile_code( code_scope={k: v for k, v in self.code_scope.items()}, code_lines=self.code_lines, show_code=self.show_code, func_name=self.func_name)
def build(self): self.code_lines.append( f' {constants.DT}_sqrt = {constants.DT} ** 0.5') # 2.1 df, dg df_and_dg(self.code_lines, self.variables, self.parameters) # 2.2 dfdt dfdt(self.code_lines, self.variables) # 2.3 dW noise_terms(self.code_lines, self.variables) # 2.3 dgdW # ---- # SCALAR_WIENER : dg * dW # VECTOR_WIENER : math.sum(dg * dW, axis=-1) if self.wiener_type == constants.SCALAR_WIENER: for var in self.variables: self.code_lines.append(f' {var}_dgdW = {var}_dg * {var}_dW') else: for var in self.variables: self.code_lines.append( f' {var}_dgdW = math.sum({var}_dg * {var}_dW, axis=-1)') self.code_lines.append(' ') if self.intg_type == constants.ITO_SDE: # 2.4 new var # ---- # y = x + dfdt + dgdW for var in self.variables: self.code_lines.append( f' {var}_new = {var} + {var}_dfdt + {var}_dgdW') self.code_lines.append(' ') elif self.intg_type == constants.STRA_SDE: # 2.4 y_bar = x + math.sum(dgdW, axis=-1) all_bar = [f'{var}_bar' for var in self.variables] for var in self.variables: self.code_lines.append(f' {var}_bar = {var} + {var}_dgdW') self.code_lines.append(' ') # 2.5 dg_bar = g(y_bar, t, *args) all_dg_bar = [f'{var}_dg_bar' for var in self.variables] self.code_lines.append( f' {", ".join(all_dg_bar)} = g({", ".join(all_bar + self.parameters)})' ) # 2.6 dgdW2 # ---- # SCALAR_WIENER : dgdW2 = dg_bar * dW # VECTOR_WIENER : dgdW2 = math.sum(dg_bar * dW, axis=-1) if self.wiener_type == constants.SCALAR_WIENER: for var in self.variables: self.code_lines.append( f' {var}_dgdW2 = {var}_dg_bar * {var}_dW') else: for var in self.variables: self.code_lines.append( f' {var}_dgdW2 = math.sum({var}_dg_bar * {var}_dW, axis=-1)' ) self.code_lines.append(' ') # 2.7 new var # ---- # y = x + dfdt + 0.5 * (dgdW + dgdW2) for var in self.variables: self.code_lines.append( f' {var}_new = {var} + {var}_dfdt + 0.5 * ({var}_dgdW + {var}_dgdW2)' ) self.code_lines.append(' ') else: raise ValueError( f'Unknown SDE_INT type: {self.intg_type}. We only ' f'supports {constants.SUPPORTED_INTG_TYPE}.') # returns new_vars = [f'{var}_new' for var in self.variables] self.code_lines.append(f' return {", ".join(new_vars)}') # return and compile self.integral = utils.compile_code( code_scope={k: v for k, v in self.code_scope.items()}, code_lines=self.code_lines, show_code=self.show_code, func_name=self.func_name)
def symbolic_build(self): if self.var_type == constants.SYSTEM_VAR: raise errors.IntegratorError( f'Exponential Euler method do not support {self.var_type} variable type.' ) if self.intg_type != constants.ITO_SDE: raise errors.IntegratorError( f'Exponential Euler method only supports Ito integral, but we got {self.intg_type}.' ) if sympy is None or analysis_by_sympy is None: raise errors.PackageMissingError('SymPy must be installed when ' 'using exponential integrators.') # check bound method if hasattr(self.derivative[constants.F], '__self__'): self.code_lines = [ f'def {self.func_name}({", ".join(["self"] + list(self.arguments))}):' ] # 1. code scope closure_vars = inspect.getclosurevars(self.derivative[constants.F]) self.code_scope.update(closure_vars.nonlocals) self.code_scope.update(dict(closure_vars.globals)) self.code_scope['math'] = math # 2. code lines code_lines = self.code_lines # code_lines = [f'def {self.func_name}({", ".join(self.arguments)}):'] code_lines.append(f' {constants.DT}_sqrt = {constants.DT} ** 0.5') # 2.1 dg # dg = g(x, t, *args) all_dg = [f'{var}_dg' for var in self.variables] code_lines.append( f' {", ".join(all_dg)} = g({", ".join(self.variables + self.parameters)})' ) code_lines.append(' ') # 2.2 dW noise_terms(code_lines, self.variables) # 2.3 dgdW # ---- # SCALAR_WIENER : dg * dW # VECTOR_WIENER : math.sum(dg * dW, axis=-1) if self.wiener_type == constants.SCALAR_WIENER: for var in self.variables: code_lines.append(f' {var}_dgdW = {var}_dg * {var}_dW') else: for var in self.variables: code_lines.append( f' {var}_dgdW = math.sum({var}_dg * {var}_dW, axis=-1)') code_lines.append(' ') # 2.4 new var # ---- analysis = separate_variables(self.derivative[constants.F]) variables_for_returns = analysis['variables_for_returns'] expressions_for_returns = analysis['expressions_for_returns'] for vi, (key, vars) in enumerate(variables_for_returns.items()): # separate variables sd_variables = [] for v in vars: if len(v) > 1: raise ValueError( 'Cannot analyze multi-assignment code line.') sd_variables.append(v[0]) expressions = expressions_for_returns[key] var_name = self.variables[vi] diff_eq = analysis_by_sympy.SingleDiffEq(var_name=var_name, variables=sd_variables, expressions=expressions, derivative_expr=key, scope=self.code_scope, func_name=self.func_name) f_expressions = diff_eq.get_f_expressions( substitute_vars=diff_eq.var_name) # code lines code_lines.extend( [f" {str(expr)}" for expr in f_expressions[:-1]]) # get the linear system using sympy f_res = f_expressions[-1] df_expr = analysis_by_sympy.str2sympy(f_res.code).expr.expand() s_df = sympy.Symbol(f"{f_res.var_name}") code_lines.append( f' {s_df.name} = {analysis_by_sympy.sympy2str(df_expr)}') var = sympy.Symbol(diff_eq.var_name, real=True) # get df part s_linear = sympy.Symbol(f'_{diff_eq.var_name}_linear') s_linear_exp = sympy.Symbol(f'_{diff_eq.var_name}_linear_exp') s_df_part = sympy.Symbol(f'_{diff_eq.var_name}_df_part') if df_expr.has(var): # linear linear = sympy.collect(df_expr, var, evaluate=False)[var] code_lines.append( f' {s_linear.name} = {analysis_by_sympy.sympy2str(linear)}' ) # linear exponential code_lines.append( f' {s_linear_exp.name} = math.exp({analysis_by_sympy.sympy2str(linear)} * {constants.DT})' ) # df part df_part = (s_linear_exp - 1) / s_linear * s_df code_lines.append( f' {s_df_part.name} = {analysis_by_sympy.sympy2str(df_part)}' ) else: # linear exponential code_lines.append( f' {s_linear_exp.name} = {constants.DT}_sqrt') # df part code_lines.append( f' {s_df_part.name} = {s_df.name} * {constants.DT}') # update expression update = var + s_df_part # The actual update step code_lines.append( f' {diff_eq.var_name}_new = {analysis_by_sympy.sympy2str(update)} + {var_name}_dgdW' ) code_lines.append('') # returns new_vars = [f'{var}_new' for var in self.variables] code_lines.append(f' return {", ".join(new_vars)}') # return and compile self.integral = utils.compile_code( code_scope={k: v for k, v in self.code_scope.items()}, code_lines=self.code_lines, show_code=self.show_code, func_name=self.func_name) if hasattr(self.derivative[constants.F], '__self__'): host = self.derivative[constants.F].__self__ self.integral = self.integral.__get__(host, host.__class__)
def build(self): # 2. code lines self.code_lines.append( f' {constants.DT}_sqrt = {constants.DT} ** 0.5') # 2.1 df, dg df_and_dg(self.code_lines, self.variables, self.parameters) # 2.2 dfdt dfdt(self.code_lines, self.variables) # 2.3 dW noise_terms(self.code_lines, self.variables) # 2.3 dgdW # ---- # dg * dW for var in self.variables: self.code_lines.append(f' {var}_dgdW = {var}_dg * {var}_dW') self.code_lines.append(' ') # 2.4 df_bar = x + dfdt + math.sum(dg * dt_sqrt, axis=-1) all_df_bar = [f'{var}_df_bar' for var in self.variables] if self.wiener_type == constants.SCALAR_WIENER: for var in self.variables: self.code_lines.append( f' {var}_df_bar = {var} + {var}_dfdt + {var}_dg * {constants.DT}_sqrt' ) else: for var in self.variables: self.code_lines.append( f' {var}_df_bar = {var} + {var}_dfdt + math.sum(' f'{var}_dg * {constants.DT}_sqrt, axis=-1)') # 2.5 dg_bar = g(y_bar, t, *args) all_dg_bar = [f'{var}_dg_bar' for var in self.variables] self.code_lines.append( f' {", ".join(all_dg_bar)} = g({", ".join(all_df_bar + self.parameters)})' ) self.code_lines.append(' ') # 2.6 dgdW2 # ---- # dgdW2 = 0.5 * (dg_bar - dg) * (dW * dW / dt_sqrt - dt_sqrt) if self.intg_type == constants.ITO_SDE: for var in self.variables: self.code_lines.append( f' {var}_dgdW2 = 0.5 * ({var}_dg_bar - {var}_dg) * ' f'({var}_dW * {var}_dW / {constants.DT}_sqrt - {constants.DT}_sqrt)' ) elif self.intg_type == constants.STRA_SDE: for var in self.variables: self.code_lines.append( f' {var}_dgdW2 = 0.5 * ({var}_dg_bar - {var}_dg) * ' f'{var}_dW * {var}_dW / {constants.DT}_sqrt') else: raise ValueError(f'Unknown SDE_INT type: {self.intg_type}') self.code_lines.append(' ') # 2.7 new var # ---- # SCALAR_WIENER : y = x + dfdt + dgdW + dgdW2 # VECTOR_WIENER : y = x + dfdt + math.sum(dgdW + dgdW2, axis=-1) if self.wiener_type == constants.SCALAR_WIENER: for var in self.variables: self.code_lines.append( f' {var}_new = {var} + {var}_dfdt + {var}_dgdW + {var}_dgdW2' ) elif self.wiener_type == constants.VECTOR_WIENER: for var in self.variables: self.code_lines.append( f' {var}_new = {var} + {var}_dfdt + math.sum({var}_dgdW + {var}_dgdW2, axis=-1)' ) else: raise ValueError(f'Unknown Wiener Process : {self.wiener_type}') self.code_lines.append(' ') # returns new_vars = [f'{var}_new' for var in self.variables] self.code_lines.append(f' return {", ".join(new_vars)}') # return and compile self.integral = utils.compile_code( code_scope={k: v for k, v in self.code_scope.items()}, code_lines=self.code_lines, show_code=self.show_code, func_name=self.func_name)
def _srk1_wrapper(f, g, dt, sde_type, var_type, wiener_type, show_code, num_iter): vdt, variables, parameters, arguments, func_name = basic_info(f=f, g=g) # 1. code scope code_scope = { 'f': f, 'g': g, vdt: dt, f'{vdt}_sqrt': dt**0.5, 'math': math, 'num_iter': num_iter } # 2. code lines code_lines = [f'def {func_name}({", ".join(arguments)}):'] if var_type == constants.SYSTEM_VAR: if len(variables) > 1: raise ValueError( f'SDE_INT with {constants.SYSTEM_VAR} variable type only ' f'supports one system variable. But we got {variables}.') if wiener_type == constants.SCALAR_WIENER: _srk1_system_var_with_scalar_wiener(sde_type, code_lines, variables, parameters, vdt) elif wiener_type == constants.VECTOR_WIENER: _srk1_system_var_with_vector_wiener(sde_type, code_lines, variables, parameters, vdt) else: raise ValueError(f'Unknown Wiener type: {wiener_type}, we only ' f'supports {constants.SUPPORTED_WIENER_TYPE}') elif var_type == constants.SCALAR_VAR: if wiener_type == constants.SCALAR_WIENER: _srk2_pop_or_scalar_var_scalar_wiener(sde_type, code_lines, variables, parameters, vdt) elif wiener_type == constants.VECTOR_WIENER: _srk1_scalar_var_with_vector_wiener(sde_type, code_lines, variables, parameters, vdt) else: raise ValueError(f'Unknown Wiener type: {wiener_type}, we only ' f'supports {constants.SUPPORTED_WIENER_TYPE}') elif var_type == constants.POP_VAR: if wiener_type == constants.SCALAR_WIENER: _srk2_pop_or_scalar_var_scalar_wiener(sde_type, code_lines, variables, parameters, vdt) elif wiener_type == constants.VECTOR_WIENER: _srk2_pop_var_vector_wiener(sde_type, code_lines, variables, parameters, vdt) else: raise ValueError(f'Unknown Wiener type: {wiener_type}, we only ' f'supports {constants.SUPPORTED_WIENER_TYPE}') else: raise ValueError(f'Unknown var type: {var_type}, we only ' f'supports {constants.SUPPORTED_VAR_TYPE}') # returns new_vars = [f'{var}_new' for var in variables] code_lines.append(f' return {", ".join(new_vars)}') # return and compile utils.compile_code(code_lines, code_scope, show_code, variables) return code_scope[func_name]
def build(self): self.code_lines.append( f' {constants.DT}_sqrt = {constants.DT} ** 0.5') # 2.1 noise _noise_terms(self.code_lines, self.variables, triple_integral=True) # 2.2 stage 1 _state1(self.code_lines, self.variables, self.parameters) # 2.3 stage 2 # ---- # H0s2 = x + dt * f_H0s1 # H1s2 = x + dt * 0.25 * f_H0s1 - dt_sqrt * 0.5 * g_H1s1 # f_H0s2 = f(H0s2, t + dt, *args) # g_H1s2 = g(H1s2, t + 0.25 * dt, *args) all_H0s2, all_H1s2 = [], [] for var in self.variables: self.code_lines.append( f' {var}_H0s2 = {var} + {constants.DT} * {var}_f_H0s1') all_H0s2.append(f'{var}_H0s2') self.code_lines.append( f' {var}_H1s2 = {var} + {constants.DT} * 0.25 * {var}_f_H0s1 - ' f'dt_sqrt * 0.5 * {var}_g_H1s1') all_H1s2.append(f'{var}_H1s2') all_H0s2.append(f't + {constants.DT}') # t all_H1s2.append(f't + 0.25 * {constants.DT}') # t f_names = [f'{var}_f_H0s2' for var in self.variables] self.code_lines.append( f' {", ".join(f_names)} = f({", ".join(all_H0s2 + self.parameters[1:])})' ) g_names = [f'{var}_g_H1s2' for var in self.variables] self.code_lines.append( f' {", ".join(g_names)} = g({", ".join(all_H1s2 + self.parameters[1:])})' ) self.code_lines.append(' ') # 2.4 state 3 # --- # H0s3 = x + dt * (0.25 * f_H0s1 + 0.25 * f_H0s2) + (g_H1s1 + 0.5 * g_H1s2) * I10 / dt # H1s3 = x + dt * f_H0s1 + dt_sqrt * g_H1s1 # f_H0s3 = g(H0s3, t + 0.5 * dt, *args) # g_H1s3 = g(H1s3, t + dt, *args) all_H0s3, all_H1s3 = [], [] for var in self.variables: self.code_lines.append( f' {var}_H0s3 = {var} + {constants.DT} * (0.25 * {var}_f_H0s1 + 0.25 * {var}_f_H0s2) + ' f'({var}_g_H1s1 + 0.5 * {var}_g_H1s2) * {var}_I10 / {constants.DT}' ) all_H0s3.append(f'{var}_H0s3') self.code_lines.append( f' {var}_H1s3 = {var} + {constants.DT} * {var}_f_H0s1 + dt_sqrt * {var}_g_H1s1' ) all_H1s3.append(f'{var}_H1s3') all_H0s3.append(f't + 0.5 * {constants.DT}') # t all_H1s3.append(f't + {constants.DT}') # t f_names = [f'{var}_f_H0s3' for var in self.variables] g_names = [f'{var}_g_H1s3' for var in self.variables] self.code_lines.append( f' {", ".join(f_names)} = f({", ".join(all_H0s3 + self.parameters[1:])})' ) self.code_lines.append( f' {", ".join(g_names)} = g({", ".join(all_H1s3 + self.parameters[1:])})' ) self.code_lines.append(' ') # 2.5 state 4 # ---- # H1s4 = x + dt * 0.25 * f_H0s3 + dt_sqrt * (2 * g_H1s1 - g_H1s2 + 0.5 * g_H1s3) # g_H1s4 = g(H1s4, t + 0.25 * dt, *args) all_H1s4 = [] for var in self.variables: self.code_lines.append( f' {var}_H1s4 = {var} + 0.25 * {constants.DT} * {var}_f_H0s1 + dt_sqrt * ' f'(2 * {var}_g_H1s1 - {var}_g_H1s2 + 0.5 * {var}_g_H1s3)') all_H1s4.append(f'{var}_H1s4') all_H1s4.append(f't + 0.25 * {constants.DT}') # t g_names = [f'{var}_g_H1s4' for var in self.variables] self.code_lines.append( f' {", ".join(g_names)} = g({", ".join(all_H1s4 + self.parameters[1:])})' ) self.code_lines.append(' ') # 2.6 final stage # ---- # f1 = f_H0s1 / 6 + f_H0s2 / 6 + f_H0s3 * 2 / 3 # g1 = - I1 + I11 / dt_sqrt + 2 * I10 / dt - 2 * I111 / dt # g2 = I1 * 4 / 3 - I11 / dt_sqrt * 4 / 3 - I10 / dt * 4 / 3 + I111 / dt * 5 / 3 # g3 = I1 * 2 / 3 + I11 / dt_sqrt / 3 - I10 / dt * 2 / 3 - I111 / dt * 2 / 3 # g4 = I111 / dt # y1 = x + dt * f1 + g1 * g_H1s1 + g2 * g_H1s2 + g3 * g_H1s3 + g4 * g_H1s4 for var in self.variables: self.code_lines.append( f' {var}_f1 = {var}_f_H0s1/6 + {var}_f_H0s2/6 + {var}_f_H0s3*2/3' ) self.code_lines.append( f' {var}_g1 = -{var}_I1 + {var}_I11/dt_sqrt + 2 * {var}_I10/{constants.DT} - 2 * {var}_I111/{constants.DT}' ) self.code_lines.append( f' {var}_g2 = {var}_I1 * 4/3 - {var}_I11 / dt_sqrt * 4/3 - ' f'{var}_I10 / {constants.DT} * 4/3 + {var}_I111 / {constants.DT} * 5/3' ) self.code_lines.append( f' {var}_g3 = {var}_I1 * 2/3 + {var}_I11/dt_sqrt/3 - ' f'{var}_I10 / {constants.DT} * 2/3 - {var}_I111 / {constants.DT} * 2/3' ) self.code_lines.append(f' {var}_g4 = {var}_I111 / {constants.DT}') self.code_lines.append( f' {var}_new = {var} + {constants.DT} * {var}_f1 + {var}_g1 * {var}_g_H1s1 + ' f'{var}_g2 * {var}_g_H1s2 + {var}_g3 * {var}_g_H1s3 + {var}_g4 * {var}_g_H1s4' ) self.code_lines.append(' ') # returns new_vars = [f'{var}_new' for var in self.variables] self.code_lines.append(f' return {", ".join(new_vars)}') # return and compile self.integral = utils.compile_code( code_scope={k: v for k, v in self.code_scope.items()}, code_lines=self.code_lines, show_code=self.show_code, func_name=self.func_name)
def build(self): # 2. code lines self.code_lines.append( f' {constants.DT}_sqrt = {constants.DT} ** 0.5') # 2.1 noise _noise_terms(self.code_lines, self.variables, triple_integral=True) # 2.2 stage 1 _state1(self.code_lines, self.variables, self.parameters) # 2.3 stage 2 all_H0s2, all_H1s2 = [], [] for var in self.variables: self.code_lines.append( f' {var}_H0s2 = {var} + {constants.DT} * 0.75 * {var}_f_H0s1 + ' f'1.5 * {var}_g_H1s1 * {var}_I10 / {constants.DT}') all_H0s2.append(f'{var}_H0s2') self.code_lines.append( f' {var}_H1s2 = {var} + {constants.DT} * 0.25 * {var}_f_H0s1 + ' f'dt_sqrt * 0.5 * {var}_g_H1s1') all_H1s2.append(f'{var}_H1s2') all_H0s2.append(f't + 0.75 * {constants.DT}') # t all_H1s2.append(f't + 0.25 * {constants.DT}') # t f_names = [f'{var}_f_H0s2' for var in self.variables] self.code_lines.append( f' {", ".join(f_names)} = f({", ".join(all_H0s2 + self.parameters[1:])})' ) g_names = [f'{var}_g_H1s2' for var in self.variables] self.code_lines.append( f' {", ".join(g_names)} = g({", ".join(all_H1s2 + self.parameters[1:])})' ) self.code_lines.append(' ') # 2.4 state 3 all_H1s3 = [] for var in self.variables: self.code_lines.append( f' {var}_H1s3 = {var} + {constants.DT} * {var}_f_H0s1 - dt_sqrt * {var}_g_H1s1' ) all_H1s3.append(f'{var}_H1s3') all_H1s3.append(f't + {constants.DT}') # t g_names = [f'{var}_g_H1s3' for var in self.variables] self.code_lines.append( f' {", ".join(g_names)} = g({", ".join(all_H1s3 + self.parameters[1:])})' ) self.code_lines.append(' ') # 2.5 state 4 all_H1s4 = [] for var in self.variables: self.code_lines.append( f' {var}_H1s4 = {var} + 0.25 * {constants.DT} * {var}_f_H0s1 + dt_sqrt * ' f'(-5 * {var}_g_H1s1 + 3 * {var}_g_H1s2 + 0.5 * {var}_g_H1s3)') all_H1s4.append(f'{var}_H1s4') all_H1s4.append(f't + 0.25 * {constants.DT}') # t g_names = [f'{var}_g_H1s4' for var in self.variables] self.code_lines.append( f' {", ".join(g_names)} = g({", ".join(all_H1s4 + self.parameters[1:])})' ) self.code_lines.append(' ') # 2.6 final stage for var in self.variables: self.code_lines.append( f' {var}_f1 = {var}_f_H0s1/3 + {var}_f_H0s2 * 2/3') self.code_lines.append( f' {var}_g1 = -{var}_I1 - {var}_I11/dt_sqrt + 2 * {var}_I10/{constants.DT} - 2 * {var}_I111/{constants.DT}' ) self.code_lines.append( f' {var}_g2 = {var}_I1 * 4/3 + {var}_I11 / dt_sqrt * 4/3 - ' f'{var}_I10 / {constants.DT} * 4/3 + {var}_I111 / {constants.DT} * 5/3' ) self.code_lines.append( f' {var}_g3 = {var}_I1 * 2/3 - {var}_I11/dt_sqrt/3 - ' f'{var}_I10 / {constants.DT} * 2/3 - {var}_I111 / {constants.DT} * 2/3' ) self.code_lines.append(f' {var}_g4 = {var}_I111 / {constants.DT}') self.code_lines.append( f' {var}_new = {var} + {constants.DT} * {var}_f1 + {var}_g1 * {var}_g_H1s1 + ' f'{var}_g2 * {var}_g_H1s2 + {var}_g3 * {var}_g_H1s3 + {var}_g4 * {var}_g_H1s4' ) self.code_lines.append(' ') # returns new_vars = [f'{var}_new' for var in self.variables] self.code_lines.append(f' return {", ".join(new_vars)}') # return and compile self.integral = utils.compile_code( code_scope={k: v for k, v in self.code_scope.items()}, code_lines=self.code_lines, show_code=self.show_code, func_name=self.func_name)
def build(self): if analysis_by_sympy is None or sympy is None: raise errors.PackageMissingError( f'Package "sympy" must be installed when the users ' f'want to utilize {ExponentialEuler.__name__}. ') # check bound method if hasattr(self.f, '__self__'): self.code_lines = [ f'def {self.func_name}({", ".join(["self"] + list(self.arguments))}):' ] # code scope closure_vars = inspect.getclosurevars(self.f) self.code_scope.update(closure_vars.nonlocals) self.code_scope.update(dict(closure_vars.globals)) self.code_scope['math'] = math analysis = separate_variables(self.f) variables_for_returns = analysis['variables_for_returns'] expressions_for_returns = analysis['expressions_for_returns'] for vi, (key, all_var) in enumerate(variables_for_returns.items()): # separate variables sd_variables = [] for v in all_var: if len(v) > 1: raise ValueError( f'Cannot analyze multi-assignment code line: {v}.') sd_variables.append(v[0]) expressions = expressions_for_returns[key] var_name = self.variables[vi] diff_eq = analysis_by_sympy.SingleDiffEq(var_name=var_name, variables=sd_variables, expressions=expressions, derivative_expr=key, scope=self.code_scope, func_name=self.func_name) var = sympy.Symbol(diff_eq.var_name, real=True) try: s_df_part = tools.timeout(self.timeout)(self.solve)(diff_eq, var) except KeyboardInterrupt: raise errors.DiffEqError( f'{self.__class__} solve {self.f} failed, because ' f'symbolic differentiation of SymPy timeout due to {self.timeout} s limit. ' f'Instead, you can use {ExpEulerAuto} to make Exponential Euler ' f'integration due to due to it is capable of ' f'performing automatic differentiation.') # update expression update = var + s_df_part # The actual update step self.code_lines.append( f' {diff_eq.var_name}_new = {analysis_by_sympy.sympy2str(update)}' ) self.code_lines.append('') self.code_lines.append( f' return {", ".join([f"{v}_new" for v in self.variables])}') self.integral = utils.compile_code( code_scope={k: v for k, v in self.code_scope.items()}, code_lines=self.code_lines, show_code=self.show_code, func_name=self.func_name) if hasattr(self.f, '__self__'): host = self.f.__self__ self.integral = self.integral.__get__(host, host.__class__)