def _finalize_markov_model(self):
        """
        Finalize a markov model
        """

        # Derivatives
        states = self.states
        derivatives = defaultdict(lambda: sp.sympify(0.0))
        rate_check = defaultdict(lambda: 0)

        # Build rate information and check that each rate is added in a
        # symetric way
        used_states = [0] * self.num_states
        for rate in self.rate_expressions:

            # Get the states
            to_state, from_state = rate.states

            # Add to derivatives of the two states
            derivatives[from_state] -= rate.sym * from_state.sym
            derivatives[to_state] += rate.sym * from_state.sym

            if isinstance(from_state, StateSolution):
                from_state = from_state.state

            if isinstance(to_state, StateSolution):
                to_state = to_state.state

            # Register rate
            ind_from = states.index(from_state)
            ind_to = states.index(to_state)
            ind_tuple = (min(ind_from, ind_to), max(ind_from, ind_to))
            rate_check[ind_tuple] += 1

            used_states[ind_from] = 1
            used_states[ind_to] = 1

        # Check used states
        if 0 in used_states:
            error(
                f"No rate registered for state {states[used_states.find(0)]}")

        # Check rate symetry
        for (ind_from, ind_to), times in list(rate_check.items()):
            if times != 2:
                error(
                    "Only one rate between the states {0} and {1} was "
                    "registered, expected two.".format(
                        states[ind_from],
                        states[ind_to],
                    ), )

        # Add derivatives
        for state in states:

            # Skip solved states
            if not isinstance(state, State) or state.is_solved:
                continue

            self.add_derivative(state, state.time.sym, derivatives[state])
    def _add_single_rate(self, to_state, from_state, expr):
        """
        Add a single rate expression
        """

        if self.state_expressions:
            error(
                "A component cannot have both state expressions (derivative "
                "and algebraic expressions) and rate expressions.", )

        check_arg(expr, scalars + (sp.Basic, ), 2,
                  ODEComponent._add_single_rate)

        expr = sp.sympify(expr)

        to_state = self._expect_state(
            to_state,
            allow_state_solution=True,
            only_local_states=True,
        )
        from_state = self._expect_state(
            from_state,
            allow_state_solution=True,
            only_local_states=True,
        )

        if to_state == from_state:
            error("The two states cannot be the same.")

        if (to_state.sym, from_state.sym) in self.rates:
            error(
                f"Rate between state {from_state} and {to_state} is already registered.",
            )

        # FIXME: It should also not be possible to include other
        # states in the markov model, right?
        syms_expr = symbols_from_expr(expr)
        if to_state.sym in syms_expr or from_state.sym in syms_expr:
            error(
                "The rate expression cannot be dependent on the "
                "states it connects.", )

        # Create a RateExpression
        obj = RateExpression(to_state, from_state, expr)

        self._register_component_object(obj)

        # Store the rate sym in the rate dict
        self.rates._register_single_rate(to_state.sym, from_state.sym, obj.sym)
Exemple #3
0
def _iszero(x):
    """Returns True if x is zero."""
    x = sp.sympify(x)
    return x.is_zero
    def __init__(self, name, expr, dependent=None):
        """
        Create an Expression with an associated name

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

        from modelparameters.sympytools import symbols_from_expr

        # Check arguments
        check_arg(expr, scalars + (sp.Basic,), 1, Expression)

        expr = sp.sympify(expr)

        # Deal with Subs in sympy expression
        for sub_expr in expr.atoms(sp.Subs):

            # deal with one Subs at a time
            subs = dict(
                (key, value) for key, value in zip(sub_expr.variables, sub_expr.point)
            )

            expr = expr.subs(sub_expr, sub_expr.expr.xreplace(subs))

        # Deal with im and re
        im_exprs = expr.atoms(sp.im)
        re_exprs = expr.atoms(sp.re)
        if im_exprs or re_exprs:
            replace_dict = {}
            for im_expr in im_exprs:
                replace_dict[im_expr] = sp.S.Zero
            for re_expr in re_exprs:
                replace_dict[re_expr] = re_expr.args[0]
            expr = expr.xreplace(replace_dict)

        if not symbols_from_expr(expr, include_numbers=True):
            error(
                "expected the expression to contain at least one " "Symbol or Number.",
            )

        # Call super class with expression as the "value"
        super(Expression, self).__init__(name, expr, dependent)

        # Collect dependent symbols
        dependent = tuple(
            sorted(
                symbols_from_expr(expr),
                key=cmp_to_key(lambda a, b: cmp(sympycode(a), sympycode(b))),
            ),
        )

        if dependent:
            self._sym = self._param.sym(*dependent)
            self._sym._assumptions["real"] = True
            self._sym._assumptions["commutative"] = True
            self._sym._assumptions["imaginary"] = False
            self._sym._assumptions["hermitian"] = True
            self._sym._assumptions["complex"] = True
        else:
            self._sym = self.param.sym

        self._dependent = set(dependent)