Ejemplo n.º 1
0
    def expanded_expression(self, expr):
        """
        Return the expanded expression.

        The returned expanded expression consists of the original
        expression given by it basics objects (States, Parameters and
        IndexedObjects)
        """

        timer = Timer("Expand expression")  # noqa: F841

        check_arg(expr, Expression)

        # First check cache
        exp_expr = self._expanded_expressions.get(expr)

        if exp_expr is not None:
            return exp_expr

        # If not recursively expand the expression
        der_subs = {}
        subs = {}
        for obj in self.expression_dependencies[expr]:

            if isinstance(obj, Derivatives):
                der_subs[obj.sym] = self.expanded_expression(obj)

            elif isinstance(obj, Expression):
                subs[obj.sym] = self.expanded_expression(obj)

        # Do the substitution
        exp_expr = expr.expr.xreplace(der_subs).xreplace(subs)
        self._expanded_expressions[expr] = exp_expr

        return exp_expr
Ejemplo n.º 2
0
        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)
def gotran2matlab(filename, params):
    """
    Create a matlab code from a gotran model
    """

    timer = Timer(f"Generate Matlab code from {filename}")  # noqa: F841

    # Load Gotran model
    ode = load_ode(filename)

    gen = MatlabCodeGenerator(params)

    info("")
    info(f"Generating Matlab files for the {ode.name} ode...")

    # Collect monitored
    if params.functions.monitored.generate:
        monitored = [expr.name for expr in ode.intermediates + ode.state_expressions]
    else:
        monitored = None

    for function_name, code in list(gen.code_dict(ode, monitored).items()):
        open(f"{ode.name}_{function_name}.m", "w").write(code)

    info("  done.")
    def __init__(
        self,
        jacobian,
        function_name="compute_diagonal_jacobian",
        result_name="diag_jac",
        params=None,
    ):
        """
        Create a DiagonalJacobianComponent

        Arguments
        ---------
        jacobian : gotran.JacobianComponent
            The Jacobian of the ODE
        function_name : str
            The name of the function which should be generated
        result_name : str (optional)
            The basename of the indexed result expression
        params : dict
            Parameters determining how the code should be generated
        """
        check_arg(jacobian, JacobianComponent)

        descr = ("Compute the diagonal jacobian of the right hand side of the "
                 "{0} ODE".format(jacobian.root))
        super(DiagonalJacobianComponent, self).__init__(
            "DiagonalJacobian",
            jacobian.root,
            function_name,
            descr,
            params=params,
        )

        what = "Computing diagonal jacobian"
        timer = Timer(what)  # noqa: F841

        self.add_comment(what)

        N = jacobian.jacobian.shape[0]
        self.shapes[result_name] = (N, )
        jacobian_name = jacobian.results[0]

        # Create IndexExpressions of the diagonal Jacobian
        for expr in jacobian.indexed_objects(jacobian_name):
            if expr.indices[0] == expr.indices[1]:
                self.add_indexed_expression(result_name, expr.indices[0],
                                            expr.expr)

        self.diagonal_jacobian = sp.Matrix(N, N, lambda i, j: 0.0)

        for i in range(N):
            self.diagonal_jacobian[i, i] = jacobian.jacobian[i, i]

        # Call recreate body with the jacobian diagonal expressions as the
        # result expressions
        results = {result_name: self.indexed_objects(result_name)}
        results, body_expressions = self._body_from_results(**results)
        self.body_expressions = self._recreate_body(body_expressions,
                                                    **results)
    def __init__(
        self,
        diagonal_jacobian,
        with_body=True,
        function_name="compute_diagonal_jacobian_action",
        result_name="diag_jac_action",
        params=None,
    ):
        """
        Create a DiagonalJacobianActionComponent

        Arguments
        ---------
        jacobian : gotran.JacobianComponent
            The Jacobian of the ODE
        with_body : bool
            If true, the body for computing the jacobian will be included
        function_name : str
            The name of the function which should be generated
        result_name : str
            The basename of the indexed result expression
        params : dict
            Parameters determining how the code should be generated
        """
        timer = Timer("Computing jacobian action component")  # noqa: F841
        check_arg(diagonal_jacobian, DiagonalJacobianComponent)
        descr = ("Compute the diagonal jacobian action of the right hand side "
                 "of the {0} ODE".format(diagonal_jacobian.root))
        super(DiagonalJacobianActionComponent, self).__init__(
            "DiagonalJacobianAction",
            diagonal_jacobian.root,
            function_name,
            descr,
            params=params,
        )

        x = self.root.full_state_vector
        jac = diagonal_jacobian.diagonal_jacobian

        self._action_vector = sp.Matrix(len(x), 1, lambda i, j: 0)

        self.add_comment("Computing the action of the jacobian")

        # Create Jacobian matrix
        self.shapes[result_name] = (len(x), )
        for i, expr in enumerate(jac * x):
            self._action_vector[i] = self.add_indexed_expression(
                result_name, i, expr)

        # Call recreate body with the jacobian action expressions as the
        # result expressions
        results = {result_name: self.indexed_objects(result_name)}
        if with_body:
            results, body_expressions = self._body_from_results(**results)
        else:
            body_expressions = results[result_name]

        self.body_expressions = self._recreate_body(body_expressions,
                                                    **results)
Ejemplo n.º 6
0
    def _body_from_dependencies(self, **results):

        timer = Timer(f"Compute dependencies for {self.name}")  # noqa: F841

        # Extract all result expressions
        result_expressions = sum(list(results.values()), [])

        # Check passed expressions
        ode_expr_deps = self.root.expression_dependencies
        exprs = set(result_expressions)
        not_checked = set()
        used_states = set()
        used_parameters = set()

        for expr in result_expressions:
            check_arg(
                expr,
                (Expression, Comment),
                context=CodeComponent._body_from_results,
            )

            if isinstance(expr, Comment):
                continue

            # Collect dependencies
            for obj in ode_expr_deps[expr]:
                if isinstance(obj, (Expression, Comment)):
                    not_checked.add(obj)
                elif isinstance(obj, State):
                    used_states.add(obj)
                elif isinstance(obj, Parameter):
                    used_parameters.add(obj)

        # use list to make the order consistent
        not_checked_list = sorted(not_checked)

        # Collect all dependencies
        while len(not_checked_list) > 0:
            dep_expr = not_checked_list.pop()
            exprs.add(dep_expr)
            for obj in ode_expr_deps[dep_expr]:
                if isinstance(obj, (Expression, Comment)):
                    if obj not in exprs:
                        if obj not in not_checked_list:
                            not_checked_list.append(obj)
                elif isinstance(obj, State):
                    used_states.add(obj)
                elif isinstance(obj, Parameter):
                    used_parameters.add(obj)

        # Sort used state, parameters and expr
        self.used_states = sorted(used_states)
        self.used_parameters = sorted(used_parameters)

        # Return a sorted list of all collected expressions
        return results, sorted(exprs)
Ejemplo n.º 7
0
def gotran2py(filename, params):
    """
    Create a c header file from a gotran model
    """
    timer = Timer(f"Generate Python code from {filename}")

    # Load Gotran model
    ode = load_ode(filename)

    # Collect monitored
    if params.functions.monitored.generate:
        monitored = [
            expr.name for expr in ode.intermediates + ode.state_expressions
        ]
    else:
        monitored = None

    # Create a Python code generator
    gen = PythonCodeGenerator(
        params,
        ns=params.namespace,
        import_inside_functions=params.import_inside_functions,
    )

    output = params.output

    if output:
        if not output.endswith(".py"):
            output += ".py"
    else:
        output = filename.replace(".ode", "") + ".py"

    info("")
    info(f"Generating Python code for the {ode.name} ode...")
    if params.class_code:
        code = gen.class_code(ode, monitored=monitored)
    else:
        code = gen.module_code(ode, monitored=monitored)
    info("  done.")
    with open(output, "w") as f:
        f.write(code)

    del timer

    if params.list_timings:
        list_timings()
Ejemplo n.º 8
0
    def add_derivative(self, der_expr, dep_var, expr, dependent=None):
        """
        Add a derivative expression

        Arguments
        ---------
        der_expr : gotran.Expression, gotran.State, sympy.AppliedUndef
            The Expression or State which is differentiated
        dep_var : gotran.State, gotran.Time, gotran.Expression, sympy.AppliedUndef, sympy.Symbol
            The dependent variable
        expr : sympy.Basic
            The expression which the differetiation should be equal
        dependent : gotran.ODEObject
            If given the count of this expression will follow as a
            fractional count based on the count of the dependent object
        """
        timer = Timer("Add derivatives")  # noqa: F841

        if isinstance(der_expr, AppliedUndef):
            name = sympycode(der_expr)
            der_expr = self.root.present_ode_objects.get(name)

            if der_expr is None:
                error(f"{name} is not registered in this ODE")

        if isinstance(dep_var, (AppliedUndef, sp.Symbol)):
            name = sympycode(dep_var)
            dep_var = self.root.present_ode_objects.get(name)

            if dep_var is None:
                error(f"{name} is not registered in this ODE")

        # Check if der_expr is a State
        if isinstance(der_expr, State):
            self._expect_state(der_expr)
            obj = StateDerivative(der_expr, expr, dependent)

        else:

            # Create a DerivativeExpression in the present component
            obj = DerivativeExpression(der_expr, dep_var, expr, dependent)

        self._register_component_object(obj, dependent)

        return obj.sym
Ejemplo n.º 9
0
    def add_indexed_object(self, basename, indices, add_offset=False):
        """
        Add an indexed object using a basename and the indices

        Arguments
        ---------
        basename : str
            The basename of the indexed expression
        indices : int, tuple of int
            The fixed indices identifying the expression
        add_offset : bool
            Add offset to indices
        """
        timer = Timer("Add indexed object")  # noqa: F841

        indices = tuplewrap(indices)

        # Check that provided indices fit with the registered shape
        if len(self.shapes[basename]) > len(indices):
            error(
                "Shape mismatch between indices {0} and registered "
                "shape for {1}{2}".format(indices, basename, self.shapes[basename]),
            )

        for dim, (index, shape_ind) in enumerate(zip(indices, self.shapes[basename])):
            if index >= shape_ind:
                error(
                    "Indices must be smaller or equal to the shape. Mismatch "
                    "in dim {0}: {1}>={2}".format(dim + 1, index, shape_ind),
                )

        # Create IndexedObject
        obj = IndexedObject(
            basename,
            indices,
            self.shapes[basename],
            self._params.array,
            add_offset,
        )
        self._register_component_object(obj)

        # Return the sympy version of the object
        return obj.sym
Ejemplo n.º 10
0
    def add_parameter(self, name, init):
        """
        Add a parameter to the component

        Arguments
        ---------
        name : str
            The name of the parameter
        init : scalar, modelparameters.ScalarParam
            The initial value of the parameter
        """
        timer = Timer("Add parameters")  # noqa: F841

        param = Parameter(name, init)

        self._register_component_object(param)

        # Return the sympy version of the state
        return param.sym
Ejemplo n.º 11
0
    def add_state(self, name, init):
        """
        Add a state to the component

        Arguments
        ---------
        name : str
            The name of the state variable
        init : scalar, modelparameters.ScalarParam
            The initial value of the state
        """
        timer = Timer("Add states")  # noqa: F841

        # Create state
        state = State(name, init, self.time)

        self._register_component_object(state)

        # Return the sympy version of the state
        return state.sym
Ejemplo n.º 12
0
def gotran2cpp(filename, params):
    """
    Create a C++ header file from a gotran model
    """

    timer = Timer(f"Generate C++ code from {filename}")

    # Load Gotran model
    ode = load_ode(filename)

    # Create a C code generator
    gen = CppCodeGenerator(params)

    output = params.output

    if output:
        if not (output.endswith(".cpp") or output.endswith(".h")):
            output += ".h"
    else:
        output = filename.replace(".ode", "") + ".h"

    info("")
    info(f"Generating C++ code for the {ode.name} ode...")
    if params.class_code:
        code = gen.class_code(ode)
    else:
        code = gen.module_code(ode)
    info("  done.")

    with open(output, "w") as f:
        if params.system_headers:
            f.write("#include <cmath>\n")
            f.write("#include <cstring>\n")
            f.write("#include <stdexcept>\n")

        f.write(code)

    del timer

    if params.list_timings:
        list_timings()
Ejemplo n.º 13
0
    def add_intermediate(self, name, expr, dependent=None):
        """
        Register an intermediate math expression

        Arguments
        ---------
        name : str
            The name of the expression
        expr : sympy.Basic, scalar
            The expression
        dependent : gotran.ODEObject
            If given the count of this expression will follow as a
            fractional count based on the count of the dependent object
        """

        # Create an Intermediate in the present component
        timer = Timer("Add intermediate")  # noqa: F841
        expr = Intermediate(name, expr, dependent)

        self._register_component_object(expr, dependent)

        return expr.sym
Ejemplo n.º 14
0
def gotran2julia(filename, params):
    """
    Create a c header file from a gotran model
    """
    timer = Timer(f"Generate Julia code from {filename}")

    # Load Gotran model
    ode = load_ode(filename)

    # Collect monitored
    if params.functions.monitored.generate:
        monitored = [
            expr.name for expr in ode.intermediates + ode.state_expressions
        ]
    else:
        monitored = None

    # Create a C code generator
    gen = JuliaCodeGenerator(params)

    output = params.output

    if output:
        if not output.endswith(".jl"):
            output += ".jl"
    else:
        output = filename.replace(".ode", "") + ".jl"

    info("")
    info(f"Generating Julia code for the {ode.name} ode...")
    code = gen.module_code(ode, monitored=monitored)
    info("  done.")
    with open(output, "w") as f:
        f.write(code)

    del timer

    if params.list_timings:
        list_timings()
Ejemplo n.º 15
0
    def add_indexed_expression(
        self,
        basename,
        indices,
        expr,
        add_offset=False,
        dependent=None,
        enum=None,
    ):
        """
        Add an indexed expression using a basename and the indices

        Arguments
        ---------
        basename : str
            The basename of the indexed expression
        indices : int, tuple of int
            The fixed indices identifying the expression
        expr : sympy.Basic, scalar
            The expression.
        add_offset : bool
            Add offset to indices
        dependent : gotran.ODEObject
            If given the count of this expression will follow as a
            fractional count based on the count of the dependent object
        """
        # Create an IndexedExpression in the present component
        timer = Timer("Add indexed expression")  # noqa: F841
        indices = tuplewrap(indices)

        # Check that provided indices fit with the registered shape
        if len(self.shapes[basename]) > len(indices):
            error(
                "Shape mismatch between indices {0} and registered "
                "shape for {1}{2}".format(indices, basename, self.shapes[basename]),
            )

        for dim, (index, shape_ind) in enumerate(zip(indices, self.shapes[basename])):
            if index >= shape_ind:
                error(
                    "Indices must be smaller or equal to the shape. Mismatch "
                    "in dim {0}: {1}>={2}".format(dim + 1, index, shape_ind),
                )

        # Create the indexed expression
        if enum is None:
            expr = IndexedExpression(
                basename,
                indices,
                expr,
                self.shapes[basename],
                self._params.array,
                add_offset,
                dependent,
                enum=enum,
            )
        elif isinstance(enum, State):
            state = enum
            expr = StateIndexedExpression(
                basename,
                indices,
                expr,
                state,
                self.shapes[basename],
                self._params.array,
                add_offset,
                dependent,
            )
        elif isinstance(enum, Parameter):
            parameter = enum
            expr = ParameterIndexedExpression(
                basename,
                indices,
                expr,
                parameter,
                self.shapes[basename],
                self._params.array,
                add_offset,
                dependent,
            )
        else:
            error(f"enum must be State or Parameter. Was {type(enum)}")
        self._register_component_object(expr, dependent)

        return expr.sym
Ejemplo n.º 16
0
    def register_ode_object(self, obj, comp, dependent=None):
        """
        Register an ODE object in the root ODEComponent
        """

        from modelparameters.sympytools import symbols_from_expr

        if self._is_finalized_ode and isinstance(obj, StateExpression):
            error("Cannot register a StateExpression, the ODE is finalized")

        # Check for existing object in the ODE
        dup_obj = self.present_ode_objects.get(obj.name)

        # If object with same name is already registered in the ode we
        # need to figure out what to do
        if dup_obj:

            try:
                dup_comp = self.object_component[dup_obj]
            except KeyError:
                dup_comp = None

            # If a state is substituted by a state solution
            if isinstance(dup_obj, State) and isinstance(obj, StateSolution):
                debug(f"Reduce state '{dup_obj}' to {obj.expr}")

            # If duplicated object is an ODE Parameter and the added object is
            # either a State or a Parameter we replace the Parameter.
            elif (isinstance(dup_obj, Parameter) and dup_comp == self
                  and comp != self and isinstance(obj, (State, Parameter))):

                timer = Timer("Replace objects")  # noqa: F841

                # Remove the object
                self.ode_objects.remove(dup_obj)

                # FIXME: Do we need to recreate all expression the objects is used in?
                # Replace the object from the object_used_in dict and update
                # the correponding expressions
                subs = {dup_obj.sym: obj.sym}
                subs = {}

                # Recursively replace object dependencies
                self._replace_object(dup_obj, obj, subs)

                # for expr in self.object_used_in[dup_obj]:
                #    updated_expr = recreate_expression(expr, subs)
                #    self.object_used_in[obj].add(updated_expr)
                #
                #    # Exchange and update the dependencies
                #    self.expression_dependencies[expr].remove(dup_obj)
                #    self.expression_dependencies[expr].add(obj)
                #
                #    # FIXME: Do not remove the dependencies
                #    #self.expression_dependencies[updated_expr] = \
                #    #            self.expression_dependencies.pop(expr)
                #    self.expression_dependencies[updated_expr] = \
                #                self.expression_dependencies[expr]
                #
                #    # Find the index of old expression and exchange it with updated
                #    old_comp = self.object_component[expr]
                #    ind = old_comp.ode_objects.index(expr)
                #    old_comp.ode_objects[ind] = updated_expr
                #
                ## Remove information about the replaced objects
                # self.object_used_in.pop(dup_obj)

            # If duplicated object is an ODE Parameter and the added
            # object is an Intermediate we raise an error.
            elif (isinstance(dup_obj, Parameter) and dup_comp == self
                  and isinstance(obj, Expression)):
                error(
                    "Cannot replace an ODE parameter with an Expression, "
                    "only with Parameters and States.", )

            # If State, Parameter or DerivativeExpression we always raise an error
            elif any(
                    isinstance(
                        oo,
                        (
                            State,
                            Parameter,
                            Time,
                            Dt,
                            DerivativeExpression,
                            AlgebraicExpression,
                            StateSolution,
                        ),
                    ) for oo in [dup_obj, obj]):
                error(
                    "Cannot register {0}. A {1} with name '{2}' is "
                    "already registered in this ODE.".format(
                        type(obj).__name__,
                        type(dup_obj).__name__,
                        dup_obj.name,
                    ), )
            else:

                # Sanity check that both obj and dup_obj are Expressions
                assert all(
                    isinstance(oo, (Expression)) for oo in [dup_obj, obj])

                # Get list of duplicated objects or an empy list
                dup_objects = self.duplicated_expressions[obj.name]
                if len(dup_objects) == 0:
                    dup_objects.append(dup_obj)
                dup_objects.append(obj)

        # Update global information about ode object
        self.present_ode_objects[obj.name] = obj
        self.object_component[obj] = comp
        self.ns.update({obj.name: obj.sym})

        # If Expression
        if isinstance(obj, Expression):

            # Append the name to the list of all ordered components with
            # expressions. If the ODE is finalized we do not update components
            if not self._is_finalized_ode:
                self._handle_expr_component(comp, obj)

            # Expand and add any derivatives in the expressions
            expression_added = False
            replace_dict = {}
            derivative_expression_list = list(obj.expr.atoms(sp.Derivative))
            derivative_expression_list.sort(key=lambda e: e.sort_key())
            for der_expr in derivative_expression_list:
                expression_added |= self._expand_single_derivative(
                    comp,
                    obj,
                    der_expr,
                    replace_dict,
                    dependent,
                )

            # If expressions need to be re-created
            if replace_dict:
                obj.replace_expr(replace_dict)

            # If any expression was added we need to bump the count of the ODEObject
            if expression_added:
                obj._recount(dependent=dependent)

            # Add dependencies between the last registered comment and
            # expressions so they are carried over in Code components
            if comp._local_comments:
                self.object_used_in[comp._local_comments[-1]].add(obj)
                self.expression_dependencies[obj].add(comp._local_comments[-1])

            # Get expression dependencies
            for sym in symbols_from_expr(obj.expr, include_derivatives=True):

                dep_obj = self.present_ode_objects[sympycode(sym)]

                if dep_obj is None:
                    error(
                        "The symbol '{0}' is not declared within the '{1}' "
                        "ODE.".format(sym, self.name), )

                # Store object dependencies
                self.expression_dependencies[obj].add(dep_obj)
                self.object_used_in[dep_obj].add(obj)

            # If the expression is a StateSolution the state cannot have
            # been used previously
            if isinstance(obj, StateSolution) and self.object_used_in.get(
                    obj.state):
                used_in = self.object_used_in.get(obj.state)
                error(
                    "A state solution cannot have been used in "
                    "any previous expressions. {0} is used in: {1}".format(
                        obj.state,
                        used_in,
                    ), )
Ejemplo n.º 17
0
    def save(self, basename=None):
        """
        Save ODE to file

        Arguments
        ---------
        basename : str (optional)
            The basename of the file which the ode will be saved to, if not
            given the basename will be the same as the name of the ode.
        """

        timer = Timer("Save " + self.name)  # noqa: F841

        if not self._is_finalized_ode:
            error("ODE need to be finalized to be saved to file.")

        lines = ["# Saved Gotran model"]

        comp_names = dict()

        basename = basename or self.name

        for comp in self.components:
            if comp == self:
                comp_name = ""
            else:
                present_comp = comp
                comps = [present_comp.name]
                while present_comp.parent != self:
                    present_comp = present_comp.parent
                    comps.append(present_comp.name)
                comp_name = ", ".join(f'"{name}"' for name in reversed(comps))

            comp_names[comp] = comp_name

            states = [
                f"{obj.name}={obj.param.repr(include_name=False)},"
                for obj in comp.ode_objects if isinstance(obj, State)
            ]
            parameters = [
                f"{obj.name}={obj.param.repr(include_name=False)},"
                for obj in comp.ode_objects if isinstance(obj, Parameter)
            ]
            if states:
                lines.append("")
                if comp_name:
                    lines.append(f"states({comp_name},")
                else:
                    lines.append(f"states({states.pop(0)}")
                for state_code in states:
                    lines.append("       " + state_code)
                lines[-1] = lines[-1][:-1] + ")"

            if parameters:
                lines.append("")
                if comp_name:
                    lines.append(f"parameters({comp_name},")
                else:
                    lines.append(f"parameters({parameters.pop(0)}")
                for param_code in parameters:
                    lines.append("           " + param_code)
                lines[-1] = lines[-1][:-1] + ")"

        # Iterate over all components
        for comp_name in self.all_expr_components_ordered:

            comp = self.all_components[comp_name]

            comp_comment = f"Expressions for the {comp.name} component"

            # Iterate over all objects of the component and save only expressions
            # and comments
            for obj in comp.ode_objects:

                # If saving an expression
                if isinstance(obj, Expression):

                    # If the component is a Markov model
                    if comp.rates:

                        # Do not save State derivatives
                        if isinstance(obj, StateDerivative):
                            continue

                        # Save rate expressions slightly different
                        elif isinstance(obj, RateExpression):
                            lines.append(
                                "rates[{0}, {1}] = {2}".format(
                                    sympycode(obj.states[0]),
                                    sympycode(obj.states[1]),
                                    sympycode(obj.expr),
                                ), )
                            continue

                    # All other Expressions
                    lines.append(f"{obj.name} = {sympycode(obj.expr)}")

                # If saving a comment
                elif isinstance(obj, Comment):

                    # If comment is component comment
                    if str(obj) == comp_comment:
                        lines.append("")
                        comp_name = (comp_names[comp]
                                     if comp_names[comp] else f'"{basename}"')
                        lines.append(f"expressions({comp_name})")

                    # Just add the comment
                    else:
                        lines.append("")
                        lines.append(f'comment("{obj}")')

        lines.append("")
        # Use Python code generator to indent outputted code
        # Write to file
        from gotran.codegeneration.codegenerators import PythonCodeGenerator

        with open(basename + ".ode", "w") as f:
            f.write("\n".join(
                PythonCodeGenerator.indent_and_split_lines(lines)))
Ejemplo n.º 18
0
    def import_ode(self, ode, prefix="", components=None, **arguments):
        """
        Import a Model into the present Model

        Arguments
        ---------
        ode : str, gotran.ODE
            The ode which should be added. If ode is a str an
            ODE stored in that file will be loaded. If it is an ODE it will be
            added directly to the present ODE.
        prefix : str (optional)
            A prefix which all state, parameters and intermediates are
            prefixed with.
        components : list, tuple of str (optional)
            A list of components which will either be extracted or excluded
            from the imported model. If not given the whole ODE will be imported.
        arguments : dict (optional)
            Optional arguments which can control loading of model
        """

        timer = Timer("Import ode")  # noqa: F841

        components = components or []
        check_arg(ode, (str, Path, ODE), 0, context=ODE.import_ode)
        check_arg(prefix, str, 1, context=ODE.import_ode)
        check_arg(components, list, 2, context=ODE.import_ode, itemtypes=str)

        # If ode is given directly
        if isinstance(ode, (str, Path)):
            # If not load external ODE
            from gotran.model.loadmodel import load_ode

            ode = load_ode(ode, **arguments)

        # Postfix prefix with "_" if prefix is not ""
        if prefix and prefix[-1] != "_":
            prefix += "_"

        # If extracting only a certain components
        if components:
            ode = ode.extract_components(ode.name, *components)

        # Subs for expressions
        subs = {}
        old_new_map = {ode: self}

        def add_comp_and_children(added, comp):
            "Help function to recursively add components to ode"

            # Add states and parameters
            for obj in comp.ode_objects:

                # Check if obj already excists as a ODE parameter
                old_obj = self.present_ode_objects.get(str(obj))

                if old_obj and self.object_component[old_obj] == self:
                    new_name = obj.name
                else:
                    new_name = prefix + obj.name

                if isinstance(obj, State):
                    subs[obj.sym] = added.add_state(new_name, obj.param)

                elif isinstance(obj, Parameter):

                    # If adding an ODE parameter
                    if comp == ode:

                        # And parameter name already excists in the present ODE
                        if str(obj) in self.present_ode_objects:

                            # Skip it and add the registered symbol for
                            # substitution
                            subs[obj.sym] = self.present_ode_objects[str(
                                obj)].sym

                        else:

                            # If not already present just add unprefixed name
                            subs[obj.sym] = added.add_parameter(
                                obj.name, obj.param)

                    else:

                        subs[obj.sym] = added.add_parameter(
                            new_name, obj.param)

            # Add child components
            for child in list(comp.children.values()):
                added_child = added.add_component(child.name)

                # Get corresponding component in present ODE
                old_new_map[child] = added_child

                add_comp_and_children(added_child, child)

        # Recursively add states and parameters
        add_comp_and_children(self, ode)

        # Iterate over all components to add expressions
        for comp_name in ode.all_expr_components_ordered:

            comp = ode.all_components[comp_name]

            comp_comment = f"Expressions for the {comp.name} component"

            # Get corresponding component in new ODE
            added = old_new_map[comp]

            # Iterate over all objects of the component and save only expressions
            # and comments
            for obj in comp.ode_objects:

                # If saving an expression
                if isinstance(obj, Expression):

                    # The new sympy expression
                    new_expr = obj.expr.xreplace(subs)

                    # If the component is a Markov model
                    if comp.rates:

                        # Do not save State derivatives
                        if isinstance(obj, StateDerivative):
                            continue

                        # Save rate expressions slightly different
                        elif isinstance(obj, RateExpression):

                            states = obj.states
                            added._add_single_rate(
                                subs[states[0].sym],
                                subs[states[1].sym],
                                new_expr,
                            )
                            continue

                    # If no prefix we just add the expression by using setattr
                    # magic
                    if prefix == "":
                        setattr(added, str(obj), new_expr)
                        subs[obj.sym] = added.ode_objects.get(obj.name).sym

                    elif isinstance(obj, (StateExpression, StateSolution)):

                        state = subs[obj.state.sym]

                        if isinstance(obj, AlgebraicExpression):
                            subs[obj.sym] = added.add_algebraic(
                                state, new_expr)

                        elif isinstance(obj, StateDerivative):
                            subs[obj.sym] = added.add_derivative(
                                state,
                                added.t,
                                new_expr,
                            )

                        elif isinstance(obj, StateSolution):
                            subs[obj.sym] = added.add_state_solution(
                                state, new_expr)
                            print(repr(obj), obj.sym)
                        else:
                            error("Should not reach here...")

                    # Derivatives are tricky. Here the der expr and dep var
                    # need to be registered in the ODE already. But they can
                    # be registered with and without prefix, so we need to
                    # check that.
                    elif isinstance(obj, Derivatives):

                        # Get der_expr
                        der_expr = self.root.present_ode_objects.get(
                            obj.der_expr.name)

                        if der_expr is None:

                            # If not found try prefixed version
                            der_expr = self.root.present_ode_objects.get(
                                prefix + obj.der_expr.name, )

                            if der_expr is None:
                                if prefix:
                                    error(
                                        "Could not find expression: "
                                        "({0}){1} while adding "
                                        "derivative".format(
                                            prefix, obj.der_expr), )
                                else:
                                    error(
                                        "Could not find expression: "
                                        "{0} while adding derivative".format(
                                            obj.der_expr, ), )

                        dep_var = self.root.present_ode_objects.get(
                            obj.dep_var.name)

                        if isinstance(dep_var, Time):
                            dep_var = self._time

                        elif dep_var is None:

                            # If not found try prefixed version
                            dep_var = self.root.present_ode_objects.get(
                                prefix + obj.dep_var.name, )

                            if dep_var is None:
                                if prefix:
                                    error(
                                        "Could not find expression: "
                                        "({0}){1} while adding "
                                        "derivative".format(
                                            prefix, obj.dep_var), )
                                else:
                                    error(
                                        "Could not find expression: "
                                        "{0} while adding derivative".format(
                                            obj.dep_var, ), )

                        subs[obj.sym] = added.add_derivative(
                            der_expr,
                            dep_var,
                            new_expr,
                        )

                    elif isinstance(obj, Intermediate):

                        subs[obj.sym] = added.add_intermediate(
                            prefix + obj.name,
                            new_expr,
                        )

                    else:
                        error("Should not reach here!")

                # If saving a comment
                elif isinstance(obj, Comment) and str(obj) != comp_comment:
                    added.add_comment(str(obj))
    def __init__(
        self,
        ode,
        function_name="forward_rush_larsen",
        delta=1e-8,
        params=None,
    ):
        """
        Create a RushLarsen Solver component

        Arguments
        ---------
        ode : gotran.ODE
            The parent component of this ODEComponent
        function_name : str
            The name of the function which should be generated
        delta : float
            Value to safeguard the evaluation of the Rush-Larsen step.
        params : dict
            Parameters determining how the code should be generated
        """

        timer = Timer("Create RushLarsen expressions")
        check_arg(ode, ODE)

        if ode.is_dae:
            error("Cannot generate a Rush-Larsen forward step for a DAE.")

        # Call base class using empty result_expressions
        descr = f"Compute a forward step using the Rush-Larsen scheme to the {ode} ODE"
        super(RushLarsen, self).__init__(
            "RushLarsen",
            ode,
            function_name,
            descr,
            params=params,
            additional_arguments=["dt"],
        )

        # Recount the expressions if representation of states are "array" as
        # then the method is not full explcit
        recount = self._params.states.representation != "array"

        # Gather state expressions and states
        state_exprs = self.root.state_expressions
        states = self.root.full_states

        result_name = self._params.states.array_name
        self.shapes[result_name] = (len(states), )

        # Get time step and start creating the update algorithm
        if self._params.states.add_offset:
            offset_str = f"{result_name}_offset"
        else:
            offset_str = ""

        might_take_time = len(states) >= 10

        if might_take_time:
            info(
                f"Calculating derivatives of {ode.name}. Might take some time..."
            )
            sys.stdout.flush()

        dt = self.root._dt.sym
        for i, expr in enumerate(state_exprs):

            dependent = expr if recount else None

            # Diagonal jacobian value
            time_diff = Timer("Differentiate state_expressions for RushLarsen")
            expr_diff = expr.expr.diff(expr.state.sym)
            del time_diff

            # print expr.state.sym, expr_diff, expr_diff.args
            if expr_diff and expr.state.sym not in expr_diff.args:

                linearized_name = expr.name + "_linearized"
                linearized = self.add_intermediate(
                    linearized_name,
                    expr_diff,
                    dependent=dependent,
                )

                need_zero_div_check = not fraction_numerator_is_nonzero(
                    expr_diff)
                if not need_zero_div_check:
                    debug(
                        f"{linearized_name} cannot be zero. Skipping zero division check",
                    )

                RL_term = expr.sym / linearized * (sp.exp(linearized * dt) - 1)
                if need_zero_div_check:
                    RL_term = Conditional(
                        abs(linearized) > delta,
                        RL_term,
                        dt * expr.sym,
                    )

                # Solve "exact" using exp
                self.add_indexed_expression(
                    result_name,
                    (i, ),
                    expr.state.sym + RL_term,
                    offset_str,
                    dependent=dependent,
                    enum=expr.state,
                )

            else:

                # Explicit Euler step
                self.add_indexed_expression(
                    result_name,
                    (i, ),
                    expr.state.sym + dt * expr.sym,
                    offset_str,
                    dependent=dependent,
                    enum=expr.state,
                )

        if might_take_time:
            info(" done")

        # Call recreate body with the solver expressions as the result
        # expressions
        del timer
        results = {result_name: self.indexed_objects(result_name)}
        results, body_expressions = self._body_from_results(**results)
        self.body_expressions = self._recreate_body(body_expressions,
                                                    **results)
    def __init__(
        self,
        ode,
        function_name="compute_jacobian",
        result_name="jac",
        params=None,
    ):
        """
        Create a JacobianComponent

        Arguments
        ---------
        ode : gotran.ODE
            The parent component of this ODEComponent
        function_name : str
            The name of the function which should be generated
        result_name : str
            The name of the variable storing the jacobian result
        params : dict
            Parameters determining how the code should be generated
        """
        check_arg(ode, ODE)

        # Call base class using empty result_expressions
        descr = f"Compute the jacobian of the right hand side of the {ode} ODE"
        super(JacobianComponent, self).__init__(
            "Jacobian",
            ode,
            function_name,
            descr,
            params=params,
        )
        check_arg(result_name, str)

        timer = Timer("Computing jacobian")  # noqa:F841

        # Gather state expressions and states
        state_exprs = self.root.state_expressions
        states = self.root.full_states

        # Create Jacobian matrix
        N = len(states)
        self.jacobian = sp.Matrix(N, N, lambda i, j: 0.0)

        self.num_nonzero = 0

        self.shapes[result_name] = (N, N)

        state_dict = dict((state.sym, ind) for ind, state in enumerate(states))
        time_sym = states[0].time.sym

        might_take_time = N >= 10

        if might_take_time:
            info(
                f"Calculating Jacobian of {ode.name}. Might take some time...")
            sys.stdout.flush()

        for i, expr in enumerate(state_exprs):

            states_syms = sorted(
                (state_dict[sym], sym)
                for sym in ode_primitives(expr.expr, time_sym)
                if sym in state_dict)

            self.add_comment(
                f"Expressions for the sparse jacobian of state {expr.state.name}",
                dependent=expr,
            )

            for j, sym in states_syms:
                time_diff = Timer("Differentiate state_expressions")
                jac_ij = expr.expr.diff(sym)
                del time_diff
                self.num_nonzero += 1
                jac_ij = self.add_indexed_expression(
                    result_name,
                    (i, j),
                    jac_ij,
                    dependent=expr,
                )

                self.jacobian[i, j] = jac_ij

        if might_take_time:
            info(" done")

        # Call recreate body with the jacobian expressions as the result
        # expressions
        results = {result_name: self.indexed_objects(result_name)}
        results, body_expressions = self._body_from_results(**results)
        self.body_expressions = self._recreate_body(body_expressions,
                                                    **results)
    def __init__(self, jacobian, function_name="lu_factorize", params=None):
        """
        Create a FactorizedJacobianComponent

        Arguments
        ---------
        jacobian : gotran.JacobianComponent
            The Jacobian of the ODE
        function_name : str
            The name of the function which should be generated
        params : dict
            Parameters determining how the code should be generated
        """

        timer = Timer("Computing factorization of jacobian")  # noqa: F841
        check_arg(jacobian, JacobianComponent)
        descr = f"Symbolically factorize the jacobian of the {jacobian.root} ODE"
        super(FactorizedJacobianComponent, self).__init__(
            "FactorizedJacobian",
            jacobian.root,
            function_name,
            descr,
            params=params,
            use_default_arguments=False,
            additional_arguments=jacobian.results,
        )

        self.add_comment(f"Factorizing jacobian of {self.root.name}")

        jacobian_name = jacobian.results[0]

        # Recreate jacobian using only sympy Symbols
        jac_orig = jacobian.jacobian

        # Size of system
        n = jac_orig.rows
        jac = sp.Matrix(n, n, lambda i, j: sp.S.Zero)

        for i in range(n):
            for j in range(n):
                # print jac_orig[i,j]
                if not jac_orig[i, j].is_zero:
                    name = sympycode(jac_orig[i, j])
                    jac[i, j] = sp.Symbol(
                        name,
                        real=True,
                        imaginary=False,
                        commutative=True,
                        hermitian=True,
                        complex=True,
                    )
                    print(jac[i, j])
        p = []

        self.shapes[jacobian_name] = (n, n)

        def add_intermediate_if_changed(jac, jac_ij, i, j):
            # If item has changed
            if jac_ij != jac[i, j]:
                print("jac", i, j, jac_ij)
                jac[i,
                    j] = self.add_indexed_expression(jacobian_name, (i, j),
                                                     jac_ij)

        # Do the factorization
        for j in range(n):

            for i in range(j):

                # Get sympy expr of A_ij
                jac_ij = jac[i, j]

                # Build sympy expression
                for k in range(i):
                    jac_ij -= jac[i, k] * jac[k, j]

                add_intermediate_if_changed(jac, jac_ij, i, j)

            pivot = -1

            for i in range(j, n):

                # Get sympy expr of A_ij
                jac_ij = jac[i, j]

                # Build sympy expression
                for k in range(j):
                    jac_ij -= jac[i, k] * jac[k, j]

                add_intermediate_if_changed(jac, jac_ij, i, j)

                # find the first non-zero pivot, includes any expression
                if pivot == -1 and jac[i, j]:
                    pivot = i

            if pivot < 0:
                # this result is based on iszerofunc's analysis of the
                # possible pivots, so even though the element may not be
                # strictly zero, the supplied iszerofunc's evaluation gave
                # True
                error("No nonzero pivot found; symbolic inversion failed.")

            if pivot != j:  # row must be swapped
                jac.row_swap(pivot, j)
                p.append([pivot, j])
                print("Pivoting!!")

            # Scale with diagonal
            if not jac[j, j]:
                error("Diagonal element of the jacobian is zero. "
                      "Inversion failed")

            scale = 1 / jac[j, j]
            for i in range(j + 1, n):

                # Get sympy expr of A_ij
                jac_ij = jac[i, j]
                jac_ij *= scale
                add_intermediate_if_changed(jac, jac_ij, i, j)

        # Store factorized jacobian
        self.factorized_jacobian = jac
        self.num_nonzero = sum(not jac[i, j].is_zero for i in range(n)
                               for j in range(n))

        # No need to call recreate body expressions
        self.body_expressions = self.ode_objects

        self.used_states = set()
        self.used_parameters = set()
Ejemplo n.º 22
0
    def _body_from_cse(self, **results):

        timer = Timer(f"Compute common sub expressions for {self.name}")  # noqa: F841

        (
            orig_result_expressions,
            result_names,
            expanded_result_exprs,
        ) = self._expanded_result_expressions(**results)

        state_offset = self._params["states"]["add_offset"]

        # Collect results and body_expressions
        body_expressions = []
        new_results = defaultdict(list)

        might_take_time = len(orig_result_expressions) >= 40

        if might_take_time:
            info(
                "Computing common sub expressions for {0}. Might take "
                "some time...".format(self.name),
            )
            sys.stdout.flush()

        # Call sympy common sub expression reduction
        cse_exprs, cse_result_exprs = cse(
            expanded_result_exprs,
            symbols=sp.numbered_symbols("cse_"),
            optimizations=[],
        )

        # Map the cse_expr to an OrderedDict
        cse_exprs = OrderedDict(cse_expr for cse_expr in cse_exprs)

        # Extract the symbols into a set for fast comparison
        cse_syms = set((sym for sym in cse_exprs))

        # Create maps between cse_expr and result expressions trying
        # to optimized the code by weaving in the result expressions
        # in between the cse_expr

        # A map between result expr and name and indices so we can
        # construct IndexedExpressions
        result_expr_map = defaultdict(list)

        # A map between last cse_expr used in a particular result expr
        # so that we can put the result expression right after the
        # last cse_expr it uses.
        last_cse_expr_used_in_result_expr = defaultdict(list)

        # Result expressions that does not contain any cse_sym
        result_expr_without_cse_syms = []

        # A map between cse_sym and its substitutes
        cse_subs = {}

        for ind, (orig_result_expr, result_expr) in enumerate(
            zip(orig_result_expressions, cse_result_exprs),
        ):

            # Collect information so that we can recreate the result
            # expression from
            result_expr_map[result_expr].append(
                (
                    result_names[orig_result_expr],
                    orig_result_expr.indices
                    if isinstance(orig_result_expr, IndexedExpression)
                    else ind,
                ),
            )

            # If result_expr does not contain any cse_sym
            if not any(cse_sym in cse_syms for cse_sym in result_expr.atoms()):
                result_expr_without_cse_syms.append(result_expr)

            else:

                # Get last cse_sym used in result expression
                last_cse_sym = sorted(
                    (cse_sym for cse_sym in result_expr.atoms() if cse_sym in cse_syms),
                    key=cmp_to_key(lambda a, b: cmp(int(a.name[4:]), int(b.name[4:]))),
                )[-1]

                if result_expr not in last_cse_expr_used_in_result_expr[last_cse_sym]:
                    last_cse_expr_used_in_result_expr[last_cse_sym].append(result_expr)

        debug(
            "Found {0} result expressions without any cse_syms.".format(
                len(result_expr_without_cse_syms),
            ),
        )

        # print ""
        # print "LAST cse_syms:", last_cse_expr_used_in_result_expr.keys()

        cse_cnt = 0
        atoms = [state.sym for state in self.root.full_states]
        atoms.extend(param.sym for param in self.root.parameters)

        # Collecte what states and parameters has been used
        used_states = set()
        used_parameters = set()

        self.add_comment(
            "Common sub expressions for the body and the " "result expressions",
        )
        body_expressions.append(self.ode_objects[-1])

        # Register the common sub expressions as Intermediates
        for cse_sym, expr in list(cse_exprs.items()):

            # print cse_sym, expr

            # If the expression is just one of the atoms of the ODE we
            # skip the cse expressions but add a subs for the atom We
            # also skip Relationals and Piecewise as the type checking
            # in Piecewise otherwise kicks in and destroys things for
            # us.
            if expr in atoms or isinstance(
                expr,
                (sp.Piecewise, sp.relational.Relational, sp.relational.Boolean),
            ):
                cse_subs[cse_sym] = expr.xreplace(cse_subs)
            else:
                # Add body expression as an intermediate expression
                sym = self.add_intermediate(f"cse_{cse_cnt}", expr.xreplace(cse_subs))
                obj = self.ode_objects.get(sympycode(sym))
                for dep in self.root.expression_dependencies[obj]:
                    if isinstance(dep, State):
                        used_states.add(dep)
                    elif isinstance(dep, Parameter):
                        used_parameters.add(dep)
                cse_subs[cse_sym] = sym
                cse_cnt += 1
                body_expressions.append(obj)

            # Check if we should add a result expressions
            if last_cse_expr_used_in_result_expr[cse_sym]:

                # Iterate over all registered result expr for this cse_sym
                for result_expr in last_cse_expr_used_in_result_expr.pop(cse_sym):

                    for result_name, indices in result_expr_map[result_expr]:

                        # Replace pure state and param expressions
                        # print cse_subs, result_expr
                        exp_expr = result_expr.xreplace(cse_subs)

                        sym = self.add_indexed_expression(
                            result_name,
                            indices,
                            exp_expr,
                            add_offset=state_offset,
                        )

                        expr = self.ode_objects.get(sympycode(sym))

                        for dep in self.root.expression_dependencies[expr]:
                            if isinstance(dep, State):
                                used_states.add(dep)
                            elif isinstance(dep, Parameter):
                                used_parameters.add(dep)

                        # Register the new result expression
                        new_results[result_name].append(expr)
                        body_expressions.append(expr)

        if might_take_time:
            info(" done")

        # Sort used state, parameters and expr
        self.used_states = sorted(used_states)
        self.used_parameters = sorted(used_parameters)

        return new_results, body_expressions
Ejemplo n.º 23
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
Ejemplo n.º 24
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
Ejemplo n.º 25
0
    def __setitem__(self, name, value):
        """
        This is only for expressions
        """

        from modelparameters.sympytools import symbols_from_expr

        timer = Timer("Namespace dispatcher")  # noqa: F841
        # Set the attr of the ODE
        # If a scalar or a sympy number or it is a sympy.Basic consisting of
        # sp.Symbols

        if (
            isinstance(value, scalars)
            or isinstance(value, sp.Number)
            or (isinstance(value, sp.Basic) and symbols_from_expr(value))
        ):

            # Get source which triggers the insertion to the global namespace
            frame = inspect.currentframe().f_back
            lines, lnum = inspect.findsource(frame)
            # Try getting the code
            try:
                code = lines[frame.f_lineno - 1].strip()

                # Concatenate lines with line continuation symbols
                prev = 2
                while (
                    frame.f_lineno - prev >= 0
                    and len(lines[frame.f_lineno - prev]) >= 2
                    and lines[frame.f_lineno - prev][-2:] == "\\\n"
                ):
                    code = lines[frame.f_lineno - prev][:-2].strip() + code
                    prev += 1
            except Exception:
                code = ""

            # Check if the line includes a for statement
            # Here we strip op potiential code comments after the main for
            # statement.
            if re.search(_for_template, code.split("#")[0].strip()) or re.search(
                _no_intermediate_template,
                code,
            ):

                debug(f"Not registering '{name}' as an intermediate.")

                # If so just add the value to the namespace without
                # registering the intermediate
                dict.__setitem__(self, name, value)

            else:

                del timer

                # Add obj to the present component
                setattr(self.ode.present_component, name, value)

        else:
            debug(f"Not registering '{name}' as an intermediate.")

            # If no ode attr was generated we just add the value to the
            # namespace
            dict.__setitem__(self, name, value)
    def __init__(
        self,
        factorized,
        function_name="forward_backward_subst",
        result_name="dx",
        residual_name="F",
        params=None,
    ):
        """
        Create a JacobianForwardBackwardSubstComponent

        Arguments
        ---------
        factorized : gotran.FactorizedJacobianComponent
            The factorized jacobian of the ODE
        function_name : str
            The name of the function which should be generated
        result_name : str
            The name of the result (increment)
        residual_name : str
            The name of the residual
        params : dict
            Parameters determining how the code should be generated
        """
        timer = Timer(
            "Computing forward backward substituion component")  # noqa: F841
        check_arg(factorized, FactorizedJacobianComponent)
        jacobian_name = list(factorized.shapes.keys())[0]
        descr = ("Symbolically forward backward substitute linear system "
                 "of {0} ODE".format(factorized.root))
        super(ForwardBackwardSubstitutionComponent, self).__init__(
            "ForwardBackwardSubst",
            factorized.root,
            function_name,
            descr,
            params=params,
            use_default_arguments=False,
            additional_arguments=[residual_name],
        )

        self.add_comment(
            f"Forward backward substituting factorized linear system {self.root.name}",
        )

        # Recreate jacobian using only sympy Symbols
        jac_orig = factorized.factorized_jacobian

        # Size of system
        n = jac_orig.rows
        jac = sp.Matrix(n, n, lambda i, j: sp.S.Zero)

        for i in range(n):
            for j in range(n):
                # print jac_orig[i,j]
                if not jac_orig[i, j].is_zero:
                    name = sympycode(jac_orig[i, j])
                    jac[i, j] = sp.Symbol(
                        name,
                        real=True,
                        imaginary=False,
                        commutative=True,
                        hermitian=True,
                        complex=True,
                    )
                    print(jac[i, j])

        self.shapes[jacobian_name] = (n, n)
        self.shapes[residual_name] = (n, )
        self.shapes[result_name] = (n, )

        F = []
        dx = []
        # forward substitution, all diag entries are scaled to 1
        for i in range(n):

            F.append(self.add_indexed_object(residual_name, i))
            dx.append(self.add_indexed_expression(result_name, i, F[i]))

            for j in range(i):
                if jac[i, j].is_zero:
                    continue
                dx[i] = self.add_indexed_expression(
                    result_name,
                    i,
                    dx[i] - dx[j] * jac[i, j],
                )

        # backward substitution
        for i in range(n - 1, -1, -1):
            for j in range(i + 1, n):
                if jac[i, j].is_zero:
                    continue
                dx[i] = self.add_indexed_expression(
                    result_name,
                    i,
                    dx[i] - dx[j] * jac[i, j],
                )

            dx[i] = self.add_indexed_expression(result_name, i,
                                                dx[i] / jac[i, i])

        # No need to call recreate body expressions
        self.body_expressions = [
            obj for obj in self.ode_objects
            if isinstance(obj, (IndexedExpression, Comment))
        ]

        self.results = [result_name]
        self.used_states = set()
        self.used_parameters = set()
    def __init__(self, ode):
        check_arg(ode, ODE)
        assert ode.is_finalized

        timer = Timer("Extract common sub expressions")  # noqa: F841

        newname = ode.name + "_CSE"

        # Call super class
        super(CommonSubExpressionODE, self).__init__(newname, ode.ns)

        # Add states and parameters
        atoms = []
        for state in ode.full_states:
            atoms.append(self.add_state(state.name, state.param))

        for param in ode.parameters:
            atoms.append(self.add_parameter(param.name, param.param))

        # Collect all expanded state expressions
        org_state_expressions = ode.state_expressions
        expanded_state_exprs = [
            ode.expanded_expressions[obj.name] for obj in org_state_expressions
        ]

        # Call sympy common sub expression reduction
        cse_exprs, cse_state_exprs = cse(
            expanded_state_exprs,
            symbols=sp.numbered_symbols("cse_"),
            optimizations=[],
        )
        cse_cnt = 0
        cse_subs = {}

        # Register the common sub expressions as Intermediates
        for sub, expr in cse_exprs:

            # If the expression is just one of the atoms of the ODE we skip
            # the cse expressions but add a subs for the atom
            if expr in atoms:
                cse_subs[sub] = expr
            else:
                cse_subs[sub] = self.add_intermediate(
                    f"cse_{cse_cnt}",
                    expr.xreplace(cse_subs),
                )
                cse_cnt += 1

        # Register the state expressions
        for org_state_expr, state_expr in zip(org_state_expressions,
                                              cse_state_exprs):

            exp_expr = state_expr.xreplace(cse_subs)
            state = self.get_object(org_state_expr.state.name)[1]

            # If state derivative
            if isinstance(org_state_expr, StateDerivative):
                self.add_derivative(state, state.time.sym, exp_expr)

            # If algebraic
            elif isinstance(org_state_expr, AlgebraicExpression):
                self.add_algebraic(state, exp_expr)

            else:
                error("Should not come here!")

        self.finalize()