예제 #1
0
def _generate_ode_problem():
    # Create an ODEProblem from dimer instance
    c_0 = Symbol('c_0')
    c_1 = Symbol('c_1')
    c_2 = Symbol('c_2')

    constants = [c_0, c_1, c_2]

    y_0 = Symbol('y_0')

    yx1 = Symbol('yx1')

    right_hand_side = MutableDenseMatrix(
        [[
            -2 * c_0 * y_0 * (y_0 - 1) - 2 * c_0 * yx1 + 2 * c_1 *
            (Float('0.5', prec=15) * c_2 - Float('0.5', prec=15) * y_0)
        ],
         [
             Float('4.0', prec=15) * c_0 * y_0**2 -
             Float('4.0', prec=15) * c_0 * y_0 +
             Float('2.0', prec=15) * c_1 * c_2 -
             Float('2.0', prec=15) * c_1 * y_0 - yx1 *
             (Float('8.0', prec=15) * c_0 * y_0 - Float('8.0', prec=15) * c_0 +
              Float('2.0', prec=15) * c_1)
         ]])

    ode_lhs_terms = [
        Moment(np.array([1]), symbol=y_0),
        Moment(np.array([2]), symbol=yx1)
    ]

    dimer_problem = ODEProblem('MEA', ode_lhs_terms, right_hand_side,
                               constants)

    return dimer_problem
예제 #2
0
def _sample_problem():
    lhs_terms = [
        Moment(np.array([1, 0, 0]), symbol='y_0'),
        Moment(np.array([0, 1, 0]), symbol='y_1'),
        Moment(np.array([0, 0, 1]), symbol='y_2'),
        Moment(np.array([0, 0, 2]), symbol='yx1'),
        Moment(np.array([0, 1, 1]), symbol='yx2'),
        Moment(np.array([0, 2, 0]), symbol='yx3'),
        Moment(np.array([1, 0, 1]), symbol='yx4'),
        Moment(np.array([1, 1, 0]), symbol='yx5'),
        Moment(np.array([2, 0, 0]), symbol='yx6')
    ]
    constants = ['c_0', 'c_1', 'c_2', 'c_3', 'c_4', 'c_5', 'c_6']

    c_0 = Symbol('c_0')
    c_1 = Symbol('c_1')
    y_0 = Symbol('y_0')
    c_2 = Symbol('c_2')
    y_2 = Symbol('y_2')
    c_6 = Symbol('c_6')
    yx4 = Symbol('yx4')
    yx6 = Symbol('yx6')
    c_3 = Symbol('c_3')
    c_4 = Symbol('c_4')
    y_1 = Symbol('y_1')
    c_5 = Symbol('c_5')
    yx2 = Symbol('yx2')
    yx1 = Symbol('yx1')
    yx3 = Symbol('yx3')
    yx5 = Symbol('yx5')
    rhs = MutableDenseMatrix([
        [
            c_0 - c_1 * y_0 - c_2 * y_0 * y_2 / (c_6 + y_0) + yx4 *
            (c_2 * y_0 / (c_6 + y_0)**2 - c_2 / (c_6 + y_0)) + yx6 *
            (-c_2 * y_0 * y_2 / (c_6 + y_0)**3 + c_2 * y_2 / (c_6 + y_0)**2)
        ], [c_3 * y_0 - c_4 * y_1], [c_4 * y_1 - c_5 * y_2],
        [
            2 * c_4 * y_1 * y_2 + c_4 * y_1 + 2 * c_4 * yx2 -
            2 * c_5 * y_2**2 + c_5 * y_2 - 2 * c_5 * yx1 - 2 * y_2 *
            (c_4 * y_1 - c_5 * y_2)
        ],
        [
            c_3 * y_0 * y_2 + c_3 * yx4 + c_4 * y_1**2 - c_4 * y_1 * y_2 -
            c_4 * y_1 + c_4 * yx3 - c_5 * y_1 * y_2 - y_1 *
            (c_4 * y_1 - c_5 * y_2) - y_2 * (c_3 * y_0 - c_4 * y_1) + yx2 *
            (-c_4 - c_5)
        ],
        [
            2 * c_3 * y_0 * y_1 + c_3 * y_0 + 2 * c_3 * yx5 -
            2 * c_4 * y_1**2 + c_4 * y_1 - 2 * c_4 * yx3 - 2 * y_1 *
            (c_3 * y_0 - c_4 * y_1)
        ],
        [
            c_0 * y_2 - c_1 * y_0 * y_2 - c_2 * y_0 * y_2**2 / (c_6 + y_0) -
            c_2 * y_0 * yx1 / (c_6 + y_0) + c_4 * y_0 * y_1 + c_4 * yx5 -
            c_5 * y_0 * y_2 - y_0 * (c_4 * y_1 - c_5 * y_2) - y_2 *
            (c_0 - c_1 * y_0 - c_2 * y_0 * y_2 / (c_6 + y_0)) + yx4 *
            (-c_1 + 2 * c_2 * y_0 * y_2 / (c_6 + y_0)**2 - 2 * c_2 * y_2 /
             (c_6 + y_0) - c_5 - y_2 * (c_2 * y_0 / (c_6 + y_0)**2 - c_2 /
                                        (c_6 + y_0))) + yx6 *
            (-c_2 * y_0 * y_2**2 / (c_6 + y_0)**3 + c_2 * y_2**2 /
             (c_6 + y_0)**2 - y_2 *
             (-c_2 * y_0 * y_2 / (c_6 + y_0)**3 + c_2 * y_2 / (c_6 + y_0)**2))
        ],
        [
            c_0 * y_1 - c_1 * y_0 * y_1 - c_2 * y_0 * y_1 * y_2 / (c_6 + y_0) -
            c_2 * y_0 * yx2 / (c_6 + y_0) + c_3 * y_0**2 - c_4 * y_0 * y_1 -
            y_0 * (c_3 * y_0 - c_4 * y_1) - y_1 *
            (c_0 - c_1 * y_0 - c_2 * y_0 * y_2 / (c_6 + y_0)) + yx4 *
            (c_2 * y_0 * y_1 / (c_6 + y_0)**2 - c_2 * y_1 / (c_6 + y_0) - y_1 *
             (c_2 * y_0 / (c_6 + y_0)**2 - c_2 / (c_6 + y_0))) + yx5 *
            (-c_1 + c_2 * y_0 * y_2 / (c_6 + y_0)**2 - c_2 * y_2 /
             (c_6 + y_0) - c_4) + yx6 *
            (-c_2 * y_0 * y_1 * y_2 / (c_6 + y_0)**3 + c_2 * y_1 * y_2 /
             (c_6 + y_0)**2 + c_3 - y_1 *
             (-c_2 * y_0 * y_2 / (c_6 + y_0)**3 + c_2 * y_2 / (c_6 + y_0)**2))
        ],
        [
            2 * c_0 * y_0 + c_0 - 2 * c_1 * y_0**2 + c_1 * y_0 -
            2 * c_2 * y_0**2 * y_2 / (c_6 + y_0) + c_2 * y_0 * y_2 /
            (c_6 + y_0) - 2 * y_0 * (c_0 - c_1 * y_0 - c_2 * y_0 * y_2 /
                                     (c_6 + y_0)) + yx4 *
            (2 * c_2 * y_0**2 / (c_6 + y_0)**2 - 4 * c_2 * y_0 /
             (c_6 + y_0) - c_2 * y_0 / (c_6 + y_0)**2 + c_2 /
             (c_6 + y_0) - 2 * y_0 * (c_2 * y_0 / (c_6 + y_0)**2 - c_2 /
                                      (c_6 + y_0))) + yx6 *
            (-2 * c_1 - 2 * c_2 * y_0**2 * y_2 /
             (c_6 + y_0)**3 + 4 * c_2 * y_0 * y_2 /
             (c_6 + y_0)**2 + c_2 * y_0 * y_2 /
             (c_6 + y_0)**3 - 2 * c_2 * y_2 / (c_6 + y_0) - c_2 * y_2 /
             (c_6 + y_0)**2 - 2 * y_0 *
             (-c_2 * y_0 * y_2 / (c_6 + y_0)**3 + c_2 * y_2 / (c_6 + y_0)**2))
        ]
    ])

    problem = ODEProblem(method='MEA',
                         left_hand_side_descriptors=lhs_terms,
                         right_hand_side=rhs,
                         parameters=constants)
    return problem
예제 #3
0
    def test_ode_problem_lna_serialisation_works(self):

        c_0 = Symbol('c_0')
        c_1 = Symbol('c_1')
        y_0 = Symbol('y_0')
        c_2 = Symbol('c_2')
        y_2 = Symbol('y_2')
        c_6 = Symbol('c_6')
        c_3 = Symbol('c_3')
        c_4 = Symbol('c_4')
        y_1 = Symbol('y_1')
        c_5 = Symbol('c_5')
        V_00 = Symbol('V_00')
        V_02 = Symbol('V_02')
        V_20 = Symbol('V_20')
        V_01 = Symbol('V_01')
        V_21 = Symbol('V_21')
        V_22 = Symbol('V_22')
        V_10 = Symbol('V_10')
        V_12 = Symbol('V_12')
        V_11 = Symbol('V_11')
        right_hand_side = MutableDenseMatrix(
            [[c_0 - c_1 * y_0 - c_2 * y_0 * y_2 / (c_6 + y_0)],
             [c_3 * y_0 - c_4 * y_1], [c_4 * y_1 - c_5 * y_2],
             [
                 2 * V_00 * (-c_1 + c_2 * y_0 * y_2 /
                             (c_6 + y_0)**2 - c_2 * y_2 /
                             (c_6 + y_0)) - V_02 * c_2 * y_0 / (c_6 + y_0) -
                 V_20 * c_2 * y_0 / (c_6 + y_0) + c_0**Float('1.0', prec=15) +
                 (c_1 * y_0)**Float('1.0', prec=15) +
                 (c_2 * y_0 * y_2 / (c_6 + y_0))**Float('1.0', prec=15)
             ],
             [
                 V_00 * c_3 - V_01 * c_4 + V_01 *
                 (-c_1 + c_2 * y_0 * y_2 / (c_6 + y_0)**2 - c_2 * y_2 /
                  (c_6 + y_0)) - V_21 * c_2 * y_0 / (c_6 + y_0)
             ],
             [
                 V_01 * c_4 - V_02 * c_5 + V_02 *
                 (-c_1 + c_2 * y_0 * y_2 / (c_6 + y_0)**2 - c_2 * y_2 /
                  (c_6 + y_0)) - V_22 * c_2 * y_0 / (c_6 + y_0)
             ],
             [
                 V_00 * c_3 - V_10 * c_4 + V_10 *
                 (-c_1 + c_2 * y_0 * y_2 / (c_6 + y_0)**2 - c_2 * y_2 /
                  (c_6 + y_0)) - V_12 * c_2 * y_0 / (c_6 + y_0)
             ],
             [
                 V_01 * c_3 + V_10 * c_3 - 2 * V_11 * c_4 +
                 (c_3 * y_0)**Float('1.0', prec=15) +
                 (c_4 * y_1)**Float('1.0', prec=15)
             ],
             [
                 V_02 * c_3 + V_11 * c_4 - V_12 * c_4 - V_12 * c_5 -
                 (c_4 * y_1)**Float('1.0', prec=15)
             ],
             [
                 V_10 * c_4 - V_20 * c_5 + V_20 *
                 (-c_1 + c_2 * y_0 * y_2 / (c_6 + y_0)**2 - c_2 * y_2 /
                  (c_6 + y_0)) - V_22 * c_2 * y_0 / (c_6 + y_0)
             ],
             [
                 V_11 * c_4 + V_20 * c_3 - V_21 * c_4 - V_21 * c_5 -
                 (c_4 * y_1)**Float('1.0', prec=15)
             ],
             [
                 V_12 * c_4 + V_21 * c_4 - 2 * V_22 * c_5 +
                 (c_4 * y_1)**Float('1.0', prec=15) +
                 (c_5 * y_2)**Float('1.0', prec=15)
             ]])

        ode_lhs_terms = [
            Moment(np.array([1, 0, 0]), symbol=y_0),
            Moment(np.array([0, 1, 0]), symbol=y_1),
            Moment(np.array([0, 0, 1]), symbol=y_2),
            VarianceTerm((0, 0), V_00),
            VarianceTerm((0, 1), V_01),
            VarianceTerm((0, 2), V_02),
            VarianceTerm((1, 0), V_10),
            VarianceTerm((1, 1), V_11),
            VarianceTerm((1, 2), V_12),
            VarianceTerm((2, 0), V_20),
            VarianceTerm((2, 1), V_21),
            VarianceTerm((2, 2), V_22)
        ]

        constants = ['c_0', 'c_1', 'c_2', 'c_3', 'c_4', 'c_5', 'c_6']

        problem = ODEProblem('LNA', ode_lhs_terms, right_hand_side, constants)
        self._roundtrip(problem)

        # Now make sure to access problem.right_hand_side_as_function as this sometimes breaks pickle
        f = problem.right_hand_side_as_function
        # Do roundtrip again
        self._roundtrip(problem)
예제 #4
0
def balance_stoichiometry(reactants, products, substances=None,
                          substance_factory=Substance.from_formula,
                          parametric_symbols=None, underdetermined=True, allow_duplicates=False):
    """ Balances stoichiometric coefficients of a reaction

    Parameters
    ----------
    reactants : iterable of reactant keys
    products : iterable of product keys
    substances : OrderedDict or string or None
        Mapping reactant/product keys to instances of :class:`Substance`.
    substance_factory : callback
    parametric_symbols : generator of symbols
        Used to generate symbols for parametric solution for
        under-determined system of equations. Default is numbered "x-symbols" starting
        from 1.
    underdetermined : bool
        Allows to find a non-unique solution (in addition to a constant factor
        across all terms). Set to ``False`` to disallow (raise ValueError) on
        e.g. "C + O2 -> CO + CO2". Set to ``None`` if you want the symbols replaced
        so that the coefficients are the smallest possible positive (non-zero) integers.
    allow_duplicates : bool
        If False: raises an excpetion if keys appear in both ``reactants`` and ``products``.

    Examples
    --------
    >>> ref = {'C2H2': 2, 'O2': 3}, {'CO': 4, 'H2O': 2}
    >>> balance_stoichiometry({'C2H2', 'O2'}, {'CO', 'H2O'}) == ref
    True
    >>> ref2 = {'H2': 1, 'O2': 1}, {'H2O2': 1}
    >>> balance_stoichiometry('H2 O2'.split(), ['H2O2'], 'H2 O2 H2O2') == ref2
    True
    >>> reac, prod = 'CuSCN KIO3 HCl'.split(), 'CuSO4 KCl HCN ICl H2O'.split()
    >>> Reaction(*balance_stoichiometry(reac, prod)).string()
    '4 CuSCN + 7 KIO3 + 14 HCl -> 4 CuSO4 + 7 KCl + 4 HCN + 7 ICl + 5 H2O'
    >>> balance_stoichiometry({'Fe', 'O2'}, {'FeO', 'Fe2O3'}, underdetermined=False)
    Traceback (most recent call last):
        ...
    ValueError: The system was under-determined
    >>> r, p = balance_stoichiometry({'Fe', 'O2'}, {'FeO', 'Fe2O3'})
    >>> list(set.union(*[v.free_symbols for v in r.values()]))
    [x1]
    >>> b = balance_stoichiometry({'Fe', 'O2'}, {'FeO', 'Fe2O3'}, underdetermined=None)
    >>> b == ({'Fe': 3, 'O2': 2}, {'FeO': 1, 'Fe2O3': 1})
    True
    >>> d = balance_stoichiometry({'C', 'CO'}, {'C', 'CO', 'CO2'}, underdetermined=None, allow_duplicates=True)
    >>> d == ({'CO': 2}, {'C': 1, 'CO2': 1})
    True

    Returns
    -------
    balanced reactants : dict
    balanced products : dict

    """
    import sympy
    from sympy import (
        MutableDenseMatrix, gcd, zeros, linsolve, numbered_symbols, Wild, Symbol,
        Integer, Tuple, preorder_traversal as pre
    )

    _intersect = sorted(set.intersection(*map(set, (reactants, products))))
    if _intersect:
        if allow_duplicates:
            if underdetermined is not None:
                raise NotImplementedError("allow_duplicates currently requires underdetermined=None")
            if set(reactants) == set(products):
                raise ValueError("cannot balance: reactants and products identical")

            # For each duplicate, try to drop it completely:
            for dupl in _intersect:
                try:
                    result = balance_stoichiometry(
                        [sp for sp in reactants if sp != dupl],
                        [sp for sp in products if sp != dupl],
                        substances=substances, substance_factory=substance_factory,
                        underdetermined=underdetermined, allow_duplicates=True)
                except Exception:
                    continue
                else:
                    return result
            for perm in product(*[(False, True)]*len(_intersect)):  # brute force (naive)
                r = set(reactants)
                p = set(products)
                for remove_reac, dupl in zip(perm, _intersect):
                    if remove_reac:
                        r.remove(dupl)
                    else:
                        p.remove(dupl)
                try:
                    result = balance_stoichiometry(
                        r, p, substances=substances, substance_factory=substance_factory,
                        parametric_symbols=parametric_symbols, underdetermined=underdetermined,
                        allow_duplicates=False)
                except ValueError:
                    continue
                else:
                    return result
            else:
                raise ValueError("Failed to remove duplicate keys: %s" % _intersect)
        else:
            raise ValueError("Substances on both sides: %s" % str(_intersect))
    if substances is None:
        substances = OrderedDict([(k, substance_factory(k)) for k in chain(reactants, products)])
    if isinstance(substances, str):
        substances = OrderedDict([(k, substance_factory(k)) for k in substances.split()])
    if type(reactants) == set:  # we don't want isinstance since it might be "OrderedSet"
        reactants = sorted(reactants)
    if type(products) == set:
        products = sorted(products)
    subst_keys = list(reactants) + list(products)

    cks = Substance.composition_keys(substances.values())

    if parametric_symbols is None:
        parametric_symbols = numbered_symbols('x', start=1, integer=True, positive=True)

    # ?C2H2 + ?O2 -> ?CO + ?H2O
    # Ax = 0
    #   A:                    x:
    #
    #   C2H2   O2  CO  H2O
    # C -2     0    1   0      x0    =   0
    # H -2     0    0   2      x1        0
    # O  0    -2    1   1      x2        0
    #                          x3

    def _get(ck, sk):
        return substances[sk].composition.get(ck, 0) * (-1 if sk in reactants else 1)

    for ck in cks:  # check that all components are present on reactant & product sides
        for rk in reactants:
            if substances[rk].composition.get(ck, 0) != 0:
                break
        else:
            any_pos = any(substances[pk].composition.get(ck, 0) > 0 for pk in products)
            any_neg = any(substances[pk].composition.get(ck, 0) < 0 for pk in products)
            if any_pos and any_neg:
                pass  # negative and positive parts among products, no worries
            else:
                raise ValueError("Component '%s' not among reactants" % ck)

        for pk in products:
            if substances[pk].composition.get(ck, 0) != 0:
                break
        else:
            any_pos = any(substances[pk].composition.get(ck, 0) > 0 for pk in reactants)
            any_neg = any(substances[pk].composition.get(ck, 0) < 0 for pk in reactants)
            if any_pos and any_neg:
                pass  # negative and positive parts among reactants, no worries
            else:
                raise ValueError("Component '%s' not among products" % ck)

    A = MutableDenseMatrix([[_get(ck, sk) for sk in subst_keys] for ck in cks])
    symbs = list(reversed([next(parametric_symbols) for _ in range(len(subst_keys))]))
    sol, = linsolve((A, zeros(len(cks), 1)), symbs)
    wi = Wild('wi', properties=[lambda k: not k.has(Symbol)])
    cd = reduce(gcd, [1] + [1/m[wi] for m in map(
        lambda n: n.match(symbs[-1]/wi), pre(sol)) if m is not None])
    sol = sol.func(*[arg/cd for arg in sol.args])

    def remove(cont, symb, remaining):
        subsd = dict(zip(remaining/symb, remaining))
        cont = cont.func(*[(arg/symb).expand().subs(subsd) for arg in cont.args])
        if cont.has(symb):
            raise ValueError("Bug, please report an issue at https://github.com/bjodah/chempy")
        return cont

    done = False
    for idx, symb in enumerate(symbs):
        for expr in sol:
            iterable = expr.args if expr.is_Add else [expr]
            for term in iterable:
                if term.is_number:
                    done = True
                    break
            if done:
                break
        if done:
            break
        for expr in sol:
            if (expr/symb).is_number:
                sol = remove(sol, symb, MutableDenseMatrix(symbs[idx+1:]))
                break
    for symb in symbs:
        cd = 1
        for expr in sol:
            iterable = expr.args if expr.is_Add else [expr]
            for term in iterable:
                if term.is_Mul and term.args[0].is_number and term.args[1] == symb:
                    cd = gcd(cd, term.args[0])
        if cd != 1:
            sol = sol.func(*[arg.subs(symb, symb/cd) for arg in sol.args])
    integer_one = 1  # need 'is' check, SyntaxWarning when checking against literal
    if underdetermined is integer_one:
        from ._release import __version__
        if int(__version__.split('.')[1]) > 6:
            warnings.warn(  # deprecated because comparison with ``1`` problematic (True==1)
                ("Pass underdetermined == None instead of ``1`` (depreacted since 0.7.0,"
                 " will_be_missing_in='0.9.0')"), ChemPyDeprecationWarning)
        underdetermined = None
    if underdetermined is None:
        sol = Tuple(*[Integer(x) for x in _solve_balancing_ilp_pulp(A)])

    fact = gcd(sol)
    sol = MutableDenseMatrix([e/fact for e in sol]).reshape(len(sol), 1)
    sol /= reduce(gcd, sol)
    if 0 in sol:
        raise ValueError("Superfluous species given.")
    if underdetermined:
        if any(x == sympy.nan for x in sol):
            raise ValueError("Failed to balance reaction")
    else:
        for x in sol:
            if len(x.free_symbols) != 0:
                raise ValueError("The system was under-determined")
        if not all(residual == 0 for residual in A * sol):
            raise ValueError("Failed to balance reaction")

    def _x(k):
        coeff = sol[subst_keys.index(k)]
        return int(coeff) if underdetermined is None else coeff

    return (
        OrderedDict([(k, _x(k)) for k in reactants]),
        OrderedDict([(k, _x(k)) for k in products])
    )
예제 #5
0
 def setup(self):
     self.M1 = MutableDenseMatrix.zeros(5, 5)
     self.M2 = ImmutableDenseMatrix.zeros(5, 5)
     self.M3 = MutableSparseMatrix.zeros(5, 5)
     self.M4 = ImmutableSparseMatrix.zeros(5, 5)