示例#1
0
    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)
示例#2
0
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
示例#3
0
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()
示例#4
0
 def component(*args):
     """
     Set the present component, deprecated
     """
     warning("Usage of 'component()' is deprecated. " "Use 'expressions()' instead.")
     return expressions(*args)
示例#5
0
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
示例#6
0
    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