def subode(subode, prefix="", components=None): """ Load an ODE and add it to the present ODE (deprecated) Argument -------- subode : str The subode which should be added. prefix : str (optional) A prefix which all state and parameters are prefixed with. components : list, tuple of str (optional) A list of components which will be extracted and added to the present ODE. If not given the whole ODE will be added. """ warning("Usage of 'sub_ode()' is deprecated. " "Use 'import_ode()' instead.") import_ode(subode, prefix, components)
def exec_ode(ode_str, name, **arguments): """ Execute an ode given by a str Arguments --------- ode_str : str The ode as a str name : str The name of ODE arguments : dict (optional) Optional arguments which can control loading of model """ # Dict to collect declared intermediates intermediate_dispatcher = IntermediateDispatcher() # Create an ODE which will be populated with data when ode file is loaded ode = ODE(name, intermediate_dispatcher) intermediate_dispatcher.ode = ode debug(f"Loading {ode.name}") # Create namespace which the ode file will be executed in _init_namespace(ode, arguments, intermediate_dispatcher) # Write str to file with open("_tmp_gotrand.ode", "w") as f: f.write(ode_str) # Execute file and collect with open("_tmp_gotrand.ode") as f: exec(compile(f.read(), "_tmp_gotrand.ode", "exec"), intermediate_dispatcher) os.unlink("_tmp_gotrand.ode") # Finalize ODE ode.finalize() # Check for completeness if not ode.is_complete: warning(f"ODE mode '{ode.name}' is not complete.") info(f"Loaded ODE model '{ode.name}' with:") for what in ["full_states", "parameters"]: num = getattr(ode, f"num_{what}") info(f"{('Num ' + what.replace('_', ' ')).rjust(20)}: {num}") return ode
def gotranrun(filename, params): # Copy of default parameters generation = parameters.generation.copy() # Set body parameters generation.code.body.representation = params.code.body_repr generation.code.body.use_cse = params.code.use_cse generation.code.body.optimize_exprs = params.code.optimize_exprs # Set what code we are going to generate and not for what_not in [ "componentwise_rhs_evaluation", "forward_backward_subst", "linearized_rhs_evaluation", "lu_factorization", "jacobian", ]: generation.functions[what_not].generate = False # Always generate code for monitored expressions generation.functions.monitored.generate = True # If scipy is used to solve we generate RHS and potentially a jacobian if params.solver == "scipy": generation.functions.rhs.generate = True generation.functions.jacobian.generate = params.code.generate_jacobian else: generation.solvers[params.solver].generate = True # Compile executeable code from gotran ode model_arguments = params.model_arguments if len(model_arguments) == 1 and model_arguments[0] == "": model_arguments = [] if len(model_arguments) % 2 != 0: error("Expected an even number of values for 'model_arguments'") arguments = dict() for arg_name, arg_value in [ (model_arguments[i * 2], model_arguments[i * 2 + 1]) for i in range(int(len(model_arguments) / 2)) ]: arguments[arg_name] = arg_value ode = load_ode(filename, **arguments) # Check for DAE if ode.is_dae: error( "Can only integrate pure ODEs. {0} includes algebraic states " "and is hence a DAE.".format(ode.name), ) # Get monitored and plot states plot_states = params.plot_y # Get x_values x_name = params.plot_x state_names = [state.name for state in ode.full_states] monitored_plot = [ plot_states.pop(plot_states.index(name)) for name in plot_states[:] if name not in state_names ] monitored = [] all_monitored_names = [] for expr in sorted(ode.intermediates + ode.state_expressions): if expr.name not in monitored: monitored.append(expr.name) all_monitored_names.append(expr.name) # Check valid monitored plot for mp in monitored_plot: if mp not in monitored: error(f"{mp} is not a state or intermediate in this ODE") # Check x_name if x_name not in ["time"] + monitored + state_names: error( "Expected plot_x to be either 'time' or one of the plotable " "variables, got {}".format(x_name), ) # Logic if x_name is not 'time' as we then need to add the name to # either plot_states or monitored_plot if x_name != "time": if x_name in state_names: plot_states.append(x_name) else: monitored_plot.append(x_name) module = compile_module(ode, params.code.language, monitored, generation) parameter_values = params.parameters init_conditions = params.init_conditions if len(parameter_values) == 1 and parameter_values[0] == "": parameter_values = [] if len(init_conditions) == 1 and init_conditions[0] == "": init_conditions = [] if len(parameter_values) % 2 != 0: error("Expected an even number of values for 'parameters'") if len(init_conditions) % 2 != 0: error("Expected an even number of values for 'initial_conditions'") user_params = dict() for param_name, param_value in [ (parameter_values[i * 2], parameter_values[i * 2 + 1]) for i in range(int(len(parameter_values) / 2)) ]: user_params[param_name] = float(param_value) user_ic = dict() for state_name, state_value in [ (init_conditions[i * 2], init_conditions[i * 2 + 1]) for i in range(int(len(init_conditions) / 2)) ]: user_ic[state_name] = float(state_value) # Use scipy to integrate model t0 = 0.0 t1 = params.tstop dt = params.dt rhs = module.rhs jac = module.compute_jacobian if params.code.generate_jacobian else None y0 = module.init_state_values(**user_ic) model_params = module.init_parameter_values(**user_params) # Check for steady state solve if params.steady_state.solve and root: result = root( rhs, y0, args=(0.0, model_params), jac=jac, method=params.steady_state.method, tol=params.steady_state.tol, ) if result.success: y0 = result.x print( "Found stead state:", ", ".join( f"{state.name}: {value:e}" for value, state in zip(y0, ode.full_states) ), ) else: warning(result.message) tsteps = np.linspace(t0, t1, int(t1 / dt) + 1) # What solver should we use if params.solver == "scipy": try: from scipy.integrate import odeint except Exception as e: error(f"Problem importing scipy.integrate.odeint. {e}") results = odeint(rhs, y0, tsteps, Dfun=jac, args=(model_params,)) else: # Get generated forward method forward = getattr(module, "forward_" + params.solver) results = [y0] states = y0.copy() # Integrate solution using generated forward method for ind, t in enumerate(tsteps[:-1]): # Step solver forward(states, t, dt, model_params) results.append(states.copy()) # Plot results if not (plot_states or monitored or params.save_results): return # allocate memory for saving results if params.save_results: save_results = np.zeros( (len(results), 1 + len(state_names) + len(all_monitored_names)), ) all_monitor_inds = np.array( [monitored.index(monitor) for monitor in all_monitored_names], dtype=int, ) all_results_header = ", ".join(["time"] + state_names + all_monitored_names) plot_inds = [module.state_indices(state) for state in plot_states] monitor_inds = np.array( [monitored.index(monitor) for monitor in monitored_plot], dtype=int, ) monitored_get_values = np.zeros(len(monitored), dtype=np.float_) # Allocate memory plot_values = np.zeros((len(plot_states) + len(monitored_plot), len(results))) for ind, (time, res) in enumerate(zip(tsteps, results)): if plot_states: plot_values[: len(plot_states), ind] = res[plot_inds] if monitored_plot or params.save_results: module.monitor(res, time, model_params, monitored_get_values) if monitored_plot: plot_values[len(plot_states) :, ind] = monitored_get_values[monitor_inds] if params.save_results: save_results[ind, 0] = time save_results[ind, 1 : len(state_names) + 1] = res save_results[ind, len(state_names) + 1 :] = monitored_get_values[ all_monitor_inds ] # Save data if params.save_results: np.savetxt( f"{params.basename}.csv", save_results, header=all_results_header, delimiter=", ", ) # Plot data if not (plot_states + monitored_plot): return # Fixes for derivatives monitored_plot_updated = [] for monitor in monitored_plot: expr, what = special_expression(monitor, ode) if what == DERIVATIVE_EXPRESSION: var, dep = expr.groups() if var in ode.present_ode_objects and dep in ode.present_ode_objects: monitored_plot_updated.append(f"d{var}/d{dep}") else: monitored_plot_updated.append(monitor) else: monitored_plot_updated.append(monitor) plot_items = plot_states + monitored_plot if x_name != "time": x_values = plot_values[plot_items.index(x_name)] else: x_values = tsteps plt.rcParams["lines.linewidth"] = 2 # line_styles = cycle([c+s for s in ["-", "--", "-.", ":"] # for c in plt.rcParams["axes.color_cycle"]]) line_styles = cycle( [ c + s for s in ["-", "--", "-.", ":"] for c in ["b", "g", "r", "c", "m", "y", "k"] ], ) plotted_items = 0 for what, values in zip(plot_items, plot_values): if what == x_name: continue plotted_items += 1 plt.plot(x_values, values, next(line_styles)) if plotted_items > 1: plt.legend([f"$\\mathrm{{{latex(value)}}}$" for value in plot_items]) elif plot_items: plt.ylabel(f"$\\mathrm{{{latex(plot_items[0])}}}$") plt.xlabel(f"$\\mathrm{{{latex(x_name)}}}$") plt.title(ode.name.replace("_", "\\_")) plt.show()
def component(*args): """ Set the present component, deprecated """ warning("Usage of 'component()' is deprecated. " "Use 'expressions()' instead.") return expressions(*args)
def _load(filename, name, **arguments): """ Load an ODE or Cell from file and return the instance The method looks for a file with .ode extension. Arguments --------- filename : str Name of the ode file to load name : str (optional) Set the name of ODE (defaults to filename) arguments : dict (optional) Optional arguments which can control loading of model """ timer = Timer("Load ODE") # noqa: F841 filename = Path(filename).with_suffix(".ode").absolute() # Extract name from filename name = filename.stem # Execute the file if not filename.is_file(): error(f"Could not find '{filename}'") # Copy file temporary to current directory basename = Path(filename.name).absolute() copyfile = False if basename != filename: shutil.copy(filename, basename) filename = basename name = filename.stem copyfile = True # If a Param is provided turn it into its value for key, value in list(arguments.items()): if isinstance(value, Param): arguments[key] = value.getvalue() # Dict to collect namespace intermediate_dispatcher = IntermediateDispatcher() # Create an ODE which will be populated with data when ode file is loaded ode = ODE(name, intermediate_dispatcher) intermediate_dispatcher.ode = ode debug(f"Loading {ode.name}") # Create namespace which the ode file will be executed in _init_namespace(ode, arguments, intermediate_dispatcher) # Execute file and collect with open(filename, "r") as f: exec(compile(f.read(), filename, "exec"), intermediate_dispatcher) # Finalize ODE ode.finalize() # Check for completeness if not ode.is_complete: warning(f"ODE model '{ode.name}' is not complete.") info(f"Loaded ODE model '{ode.name}' with:") for what in ["full_states", "parameters"]: num = getattr(ode, f"num_{what}") info(f"{('Num ' + what.replace('_', ' ')).rjust(20)}: {num}") if copyfile: os.unlink(filename) return ode
def _recreate_body(self, body_expressions, **results): """ Create body expressions based on the given result_expressions In this method are all expressions replaced with something that should be used to generate code. The parameters in: parameters["generation"]["code"] decides how parameters, states, body expressions and indexed expressions are represented. """ if not (results or body_expressions): return for result_name, result_expressions in list(results.items()): check_kwarg( result_expressions, result_name, list, context=CodeComponent._recreate_body, itemtypes=(Expression, Comment), ) # Extract all result expressions result_expressions = sum(list(results.values()), []) # A map between result expression and result name result_names = dict( (result_expr, result_name) for result_name, result_exprs in list(results.items()) for result_expr in result_exprs ) timer = Timer(f"Recreate body expressions for {self.name}") # noqa: F841 # Initialize the replace_dictionaries replace_dict = self.param_state_replace_dict der_replace_dict = {} # Get a copy of the map of where objects are used in and their # present dependencies so any updates done in these dictionaries does not # affect the original dicts object_used_in = defaultdict(set) for expr, used in list(self.root.object_used_in.items()): object_used_in[expr].update(used) expression_dependencies = defaultdict(set) for expr, deps in list(self.root.expression_dependencies.items()): expression_dependencies[expr].update(deps) # Get body parameters body_repr = self._params["body"]["representation"] optimize_exprs = self._params["body"]["optimize_exprs"] # Set body related variables if the body should be represented by an array if "array" in body_repr: body_name = self._params["body"]["array_name"] available_indices = deque() max_index = -1 body_ind = 0 index_available_at = defaultdict(list) if body_name == result_name: error("body and result cannot have the same name.") # Initiate shapes with inf self.shapes[body_name] = (float("inf"),) # Iterate over body expressions and recreate the different expressions # according to state, parameters, body and result expressions replaced_expr_map = OrderedDict() new_body_expressions = [] present_ode_objects = dict( (state.name, state) for state in self.root.full_states ) present_ode_objects.update( (param.name, param) for param in self.root.parameters ) old_present_ode_objects = present_ode_objects.copy() def store_expressions(expr, new_expr): "Help function to store new expressions" timer = Timer( # noqa: F841 f"Store expression while recreating body of {self.name}", ) # noqa: F841 # Update sym replace dict if isinstance(expr, Derivatives): der_replace_dict[expr.sym] = new_expr.sym else: replace_dict[expr.sym] = new_expr.sym # Store the new expression for later references present_ode_objects[expr.name] = new_expr replaced_expr_map[expr] = new_expr # Append the new expression new_body_expressions.append(new_expr) # Update dependency information if expr in object_used_in: for dep in object_used_in[expr]: if dep in expression_dependencies: expression_dependencies[dep].remove(expr) expression_dependencies[dep].add(new_expr) object_used_in[new_expr] = object_used_in.pop(expr) if expr in expression_dependencies: expression_dependencies[new_expr] = expression_dependencies.pop(expr) self.add_comment("Recreated body expressions") # The main iteration over all body_expressions for expr in body_expressions: # 1) Comments if isinstance(expr, Comment): new_body_expressions.append(expr) continue assert isinstance(expr, Expression) # 2) Check for expression optimizations if not (optimize_exprs == "none" or expr in result_expressions): timer_opt = Timer( # noqa: F841 f"Handle expression optimization for {self.name}", ) # noqa: F841 # If expr is just a number we exchange the expression with the # number if "numerals" in optimize_exprs and isinstance(expr.expr, sp.Number): replace_dict[expr.sym] = expr.expr # Remove information about this expr beeing used for dep in object_used_in[expr]: expression_dependencies[dep].remove(expr) object_used_in.pop(expr) continue # If the expr is just a symbol (symbol multiplied with a scalar) # we exchange the expression with the sympy expressions elif "symbols" in optimize_exprs and ( isinstance(expr.expr, (sp.Symbol, AppliedUndef)) or isinstance(expr.expr, sp.Mul) and len(expr.expr.args) == 2 and isinstance(expr.expr.args[1], (sp.Symbol, AppliedUndef)) and expr.expr.args[0].is_number ): # Add a replace rule based on the stored sympy expression sympy_expr = expr.expr.xreplace(der_replace_dict).xreplace( replace_dict, ) if isinstance(expr.sym, sp.Derivative): der_replace_dict[expr.sym] = sympy_expr else: replace_dict[expr.sym] = sympy_expr # Get exchanged repr if isinstance(expr.expr, (sp.Symbol, AppliedUndef)): name = sympycode(expr.expr) else: name = sympycode(expr.expr.args[1]) dep_expr = present_ode_objects[name] # If using reused body expressions we need to update the # index information so that the index previously available # for this expressions gets available at the last expressions # the present expression is used in. if ( isinstance(dep_expr, IndexedExpression) and dep_expr.basename == body_name and "reused" in body_repr ): ind = dep_expr.indices[0] # Remove available index information dep_used_in = sorted(object_used_in[dep_expr]) for used_expr in dep_used_in: if ind in index_available_at[used_expr]: index_available_at[used_expr].remove(ind) # Update with new indices all_used_in = object_used_in[expr].copy() all_used_in.update(dep_used_in) for used_expr in sorted(all_used_in, reverse=True): if used_expr in body_expressions: index_available_at[used_expr].append(ind) break # Update information about this expr beeing used for dep in object_used_in[expr]: expression_dependencies[dep].remove(expr) expression_dependencies[dep].add(dep_expr) object_used_in.pop(expr) continue del timer_opt # 3) General operations for all Expressions that are kept # Before we process the expression we check if any indices gets # available with the expr (Only applies for the "reused" option for # body_repr.) if "reused" in body_repr: # Check if any indices are available at this expression ind available_indices.extend(index_available_at[expr]) # Store a map of old name this will preserve the ordering of # expressions with the same name, similar to how this is treated in # the actual ODE. present_ode_objects[expr.name] = expr old_present_ode_objects[expr.name] = expr # 4) Handle result expression if expr in result_expressions: timer_result = Timer( # noqa: F841 f"Handle result expressions for {self.name}", ) # noqa: F841 # Get the result name result_name = result_names[expr] # If the expression is an IndexedExpression with the same basename # as the result name we just recreate it if ( isinstance(expr, IndexedExpression) or isinstance(expr, StateIndexedExpression) or isinstance(expr, ParameterIndexedExpression) ) and result_name == expr.basename: new_expr = recreate_expression(expr, der_replace_dict, replace_dict) # Not an indexed expression else: # Get index based on the original ordering index = (results[result_name].index(expr),) # Create the IndexedExpression # NOTE: First replace any derivative expression replaces, then state and # NOTE: params if isinstance(expr, StateDerivative): new_expr = StateIndexedExpression( result_name, index, expr.expr.xreplace(der_replace_dict).xreplace(replace_dict), expr.state, (len(results[result_name]),), array_params=self._params.array, ) else: new_expr = IndexedExpression( result_name, index, expr.expr.xreplace(der_replace_dict).xreplace(replace_dict), (len(results[result_name]),), array_params=self._params.array, ) if new_expr.basename not in self.indexed_map: self.indexed_map[new_expr.basename] = OrderedDict() self.indexed_map[new_expr.basename][expr] = new_expr # Copy counter from old expression so it sort properly new_expr._recount(expr._count) # Store the expressions store_expressions(expr, new_expr) del timer_result # 4) Handle indexed expression # All indexed expressions are just kept but recreated with updated # sympy expressions elif isinstance(expr, IndexedExpression): timer_indexed = Timer( # noqa: F841 f"Handle indexed expressions for {self.name}", ) # noqa: F841 new_expr = recreate_expression(expr, der_replace_dict, replace_dict) # Store the expressions store_expressions(expr, new_expr) del timer_indexed # 5) If replacing all body exressions with an indexed expression elif "array" in body_repr: timer_body = Timer( # noqa: F841 f"Handle body expressions for {self.name}", ) # noqa: F841 # 5a) If we reuse array indices if "reused" in body_repr: if available_indices: ind = available_indices.popleft() else: max_index += 1 ind = max_index # Check when present ind gets available again for used_expr in sorted(object_used_in[expr], reverse=True): if used_expr in body_expressions: index_available_at[used_expr].append(ind) break else: warning("SHOULD NOT COME HERE!") # 5b) No reuse of array indices. Here each index corresponds to # a distinct body expression else: ind = body_ind # Increase body_ind body_ind += 1 # Create the IndexedExpression new_expr = IndexedExpression( body_name, ind, expr.expr.xreplace(der_replace_dict).xreplace(replace_dict), array_params=self._params.array, enum=expr.name, ) if body_name not in self.indexed_map: self.indexed_map[body_name] = OrderedDict() self.indexed_map[body_name][expr] = new_expr # Copy counter from old expression so they sort properly new_expr._recount(expr._count) # Store the expressions store_expressions(expr, new_expr) del timer_body # 6) If the expression is just an ordinary body expression and we # are using named representation of body else: timer_expr = Timer(f"Handle expressions for {self.name}") # noqa: F841 # If the expression is a state derivative we need to add a # replacement for the Derivative symbol if isinstance(expr, StateDerivative): new_expr = Intermediate( sympycode(expr.sym), expr.expr.xreplace(der_replace_dict).xreplace(replace_dict), ) new_expr._recount(expr._count) else: new_expr = recreate_expression(expr, der_replace_dict, replace_dict) del timer_expr # Store the expressions store_expressions(expr, new_expr) # Store indices for any added arrays if "reused_array" == body_repr: if max_index > -1: self.shapes[body_name] = (max_index + 1,) else: self.shapes.pop(body_name) elif "array" == body_repr: if body_ind > 0: self.shapes[body_name] = (body_ind,) else: self.shapes.pop(body_name) # Store the shape of the added result expressions for result_name, result_expressions in list(results.items()): if result_name not in self.shapes: self.shapes[result_name] = (len(result_expressions),) return new_body_expressions