# -*- coding: utf-8 -*-
"""
This module collects object representing rate expressions. It is based
on the ``chemp.util._expr`` module. The API is somewhat cumbersome since
it tries to be compatible with pure python, SymPy and the underlying
units library of ChemPy (``quantities``). Consider the API to be provisional.
"""
from __future__ import (absolute_import, division, print_function)
import math
from ..util.pyutil import memoize, deprecated
from ..util._expr import Expr
[docs]class RateExpr(Expr):
""" Baseclass for rate expressions, see source code of e.g. MassAction & Radiolytic. """
kw = {'rxn': None, 'ref': None}
@property
def rxn(self):
return self._rxn
@rxn.setter
def rxn(self, value):
self._rxn = value
for arg in self.args:
if isinstance(arg, RateExpr):
arg.rxn = value
def _recursive_as_RateExpr(self):
new_args = []
for arg in self.args:
if isinstance(arg, Expr):
new_args.append(arg)
else:
if hasattr(arg, '_as_RateExpr'):
new_args.append(arg._as_RateExpr(self.rxn))
else:
new_args.append(arg)
if self.kw is None:
kw = {}
else:
kw = {k: getattr(self, k) for k in self.kw}
return self.__class__(new_args, self.unique_keys, **kw)
@classmethod
@deprecated(use_instead=Expr.from_callback)
[docs] def subclass_from_callback(cls, cb, cls_attrs=None):
""" Override RateExpr.__call__
Parameters
----------
cb : callback
With signature (variables, all_args, backend) -> scalar
where `variables` is a dict, `all_args` a tuple and `backend` a module.
cls_attrs : dict, optional
Attributes to set in subclass, e.g. parameter_keys, nargs
Examples
--------
>>> from chempy import Reaction
>>> rxn = Reaction({'O2': 1, 'H2': 1}, {'H2O2': 1}) # d[H2O2]/dt = p0*exp(-p1/T)*sqrt([O2])
>>> def cb(variables, all_args, backend):
... O2, T = variables['O2'], variables['temperature']
... p0, p1 = all_args
... return p0*backend.sqrt(O2)*backend.exp(-p1/T)
>>> MyRateExpr = RateExpr.subclass_from_callback(cb, dict(parameter_keys=('temperature',),nargs=2))
>>> k = MyRateExpr([1.3e9, 4317.2], rxn=rxn)
>>> print('%.5g' % k({'temperature': 298.15, 'O2': 1.1e-3}))
22.186
"""
class _RateExpr(cls):
def __call__(self, variables, backend=math):
return cb(variables, self.all_args(variables), backend=backend)
for k, v in (cls_attrs or {}).items():
setattr(_RateExpr, k, v)
return _RateExpr
[docs]class RadiolyticBase(RateExpr):
pass # for isinstance checks
@memoize(1)
[docs]def mk_Radiolytic(doserate_name='doserate'):
""" Create a Radiolytic rate expression
Note that there is no mass-action dependence in the resulting
class, i.e. the rates does not depend on any concentrations.
Examples
--------
>>> RadiolyticAlpha = mk_Radiolytic('doserate_alpha')
>>> RadiolyticGamma = mk_Radiolytic('doserate_gamma')
>>> dihydrogen_alpha = RadiolyticAlpha([0.8e-7])
>>> dihydrogen_gamma = RadiolyticGamma([0.45e-7])
"""
class _Radiolytic(RadiolyticBase):
argument_names = ('radiolytic_yield',) # [amount/energy]
parameter_keys = (doserate_name, 'density')
print_name = 'Radiolytic' if doserate_name == 'doserate' else ('Radiolytic{'+doserate_name+'}')
def g_value(self, variables, backend=math): # for subclasses
return self.arg(variables, 0, backend=backend)
def __call__(self, variables, backend=math):
return self.g_value(variables, 0)*variables[doserate_name]*variables['density']
return _Radiolytic
Radiolytic = mk_Radiolytic()
[docs]class MassAction(RateExpr):
""" Rate-expression of mass-action type
Examples
--------
>>> ma = MassAction([3.14])
>>> ma.rate_coeff({})
3.14
>>> from chempy import Reaction
>>> r = Reaction.from_string('3 A -> B', param=ma)
>>> r.rate({'A': 2}) == {'A': -75.36, 'B': 25.12}
True
"""
argument_names = ('rate_constant',)
[docs] def rate_coeff(self, variables, backend=math): # for subclasses
return self.arg(variables, 0, backend=backend)
def __call__(self, variables, backend=math):
prod = self.rate_coeff(variables, backend=backend)
for k, v in self.rxn.reac.items():
prod *= variables[k]**v
return prod
@classmethod
[docs] def subclass_from_callback(cls, cb, cls_attrs=None):
""" Override MassAction.rate_coeff
Parameters
----------
cb : callback
With signature (variables, all_args, backend) -> scalar
where `variables` is a dict, `all_args` a tuple and `backend` a module.
cls_attrs : dict, optional
Attributes to set in subclass, e.g. parameter_keys, nargs
Examples
--------
>>> from functools import reduce
>>> from operator import add
>>> from chempy import Reaction # d[H2]/dt = 10**(p0 + p1/T + p2/T**2)*[e-]**2
>>> rxn = Reaction({'e-': 2}, {'OH-': 2, 'H2': 1}, None, {'H2O': 2})
>>> def cb(variables, all_args, backend):
... T = variables['temperature']
... return 10**reduce(add, [p*T**-i for i, p in enumerate(all_args)])
>>> MyMassAction = MassAction.subclass_from_callback(cb, dict(parameter_keys=('temperature',), nargs=-1))
>>> k = MyMassAction([9, 300, -75000], rxn=rxn)
>>> print('%.5g' % k({'temperature': 293., 'e-': 1e-10}))
1.4134e-11
"""
class _MassAction(cls):
def rate_coeff(self, variables, backend=math):
return cb(variables, self.all_args(variables), backend=backend)
for k, v in (cls_attrs or {}).items():
setattr(_MassAction, k, v)
return _MassAction
[docs] def as_mass_action(self, variables, backend=math):
return MassAction([self.rate_coeff(variables, backend=backend)], self.unique_keys, **self.kwargs)
[docs]class ArrheniusMassAction(MassAction):
""" Rate expression for a Arrhenius-type of rate
Examples
--------
>>> from math import exp
>>> from chempy import Reaction
>>> from chempy.units import allclose, default_units as u
>>> A = 1e11 / u.second
>>> Ea_over_R = 42e3/8.3145 * u.K**-1
>>> ratex = ArrheniusMassAction([A, Ea_over_R])
>>> rxn = Reaction({'R'}, {'P'}, ratex)
>>> dRdt = rxn.rate({'R': 3*u.M, 'temperature': 298.15*u.K})['R']
>>> allclose(dRdt, -3*1e11*exp(-42e3/8.3145/298.15)*u.M/u.s)
True
"""
argument_names = ('A', 'Ea_over_R')
parameter_keys = ('temperature',)
[docs] def rate_coeff(self, variables, backend=math):
A, Ea_over_R = self.all_args(variables, backend=backend)
return A*backend.exp(-Ea_over_R/variables['temperature'])
[docs]class EyringMassAction(ArrheniusMassAction):
argument_names = ('kB_h_times_exp_dS_R', 'dH_over_R')
[docs] def rate_coeff(self, variables, backend=math):
kB_h_times_exp_dS_R, dH_over_R = self.all_args(variables, backend=backend)
T = variables['temperature']
return T * kB_h_times_exp_dS_R * backend.exp(-dH_over_R/T)