# Photokinetic Simulator

This notebook allows you to create a compartmental models from simple text-based input. The system of ordinary differential equations (ODEs) can be displayed and steady state approximation for transient species can be performed symbolically. Symbolic manipulations are performed with [Sympy](https://www.sympy.org/en/index.html) module. The compartmental model can be simulated for different input parameters and initial concentrations.

### Rules for the Input Text Model

Expected format of the text-based input is single or multiline, forward reactions and absorptions are denoted with `-->` and `-hv->` signs, respecively. Names of compartments are case sensitive. It is possible to denote the sub- and/or superscript with $\LaTeX$ notation (for details, see the corresponding [tutorial](https://cs.overleaf.com/learn/latex/Mathematical_expressions)):

* `^1S` $\rightarrow$ $\mathrm{^1S}$
* `^1O_2`  $\rightarrow$   $\mathrm{^1O_2}$
*  `RB^{\bullet 3-}` $\rightarrow$   $\mathrm{RB^{\bullet 3-}}$

Names of the rate constants for individual reactions can be taken from the text input. They can be denoted at the end of the each line after `//` characters and are separated by semicolon `;`. It means, that semicolon cannot appear in the name of the rate constant (this could be improved in the future). If the rate constant name is not specified, default name using the reactants and products will be used. Comments are denoted by `#`. All characters after this symbol will be ignored. The rate constants can be specified in $\LaTeX$ notation:

* `k_r` $\rightarrow$ $k_r$
* `k_{isc}` $\rightarrow$ $k_{isc}$
* `k_d^{\prime}` $\rightarrow$ $k_d^{\prime}$

*Note: The backslash in the $\LaTeX$ notation (e.g. \prime, \bullet) have to be escaped in the text model (see later).*

### Simulation of the Model

The rate constants and intial concentrations has to be supplied in the order they are stored in the model. To see the correct order, print the following attributes of the instantiated model:

* `symbols['rate_constants']`
* `symbols['compartments']`

In the case of making substitutions during the symbolic steady state approximation, the substituted parameters has to be supplied to the model as well. Only expressions composed of rate_constants can be substituted. The order of the substituted parameters can be found in following attribute:

* `symbols['substitutions']`

### Units

In default, the concentrations are denoted in $\mathrm{M = mol\ L^{-1}}$. The concentration is specified in a model as `concentration_unit`. Time is always supplied in seconds. For simulation, the automatic unit conversion is performed, but this can be disabled with option `auto_convert_time_units` in `simulate_model` function. The units of rate constants are automatically determined from the model, but the time unit is always in seconds:

$$[k] = \mathrm{M^{1 - [\text{rate order}]}\ s^{-1}}$$

In the case of photoreactions, the symbol $J$ is the "concentration of photons" that hits the sample. Usual unit is $[J] = \mathrm{M \ s^{-1}}$:

$$J = \frac{q_0}{V}$$

Here, $q_0$ is the incident photon flux (usually in $\mathrm{mol\ s^{-1}}$) and $V$ is the volume of the sample in the cuvette (usually in $\mathrm L$).

Molar absorption coefficient $\varepsilon$ at the wavelength of irradiation is denoted usually in $\mathrm{M^{-1}\ cm^{-1}}$ and $l$, length of the cuvette, in $\mathrm{cm}$. The "concentration of light" absorbed by the compartment is then:

$$J^{\prime} = J\left(1 - 10^{-lc\varepsilon}\right)$$

where $c$ is the concetration of the compartment. For the usage of the symbolic model, see the examples below.

```python
#@title Install and Load Packages { display-mode: "form" }

#@markdown Please execute this cell by pressing the _Play_ button
#@markdown on the left to download and import the required packages
#@markdown in this Colab notebook.

#@markdown **Note**: This installs the sympy package on the Colab
#@markdown notebook in the cloud and not on your computer.

try:
    import google.colab
    IN_COLAB = True
except ImportError:
    IN_COLAB = False

try:
    import sympy
except ImportError:
    %shell pip install sympy

from sympy import symbols, Symbol, print_latex, Eq
from IPython.display import display, Latex, HTML
import matplotlib.pyplot as plt
import numpy as np

try:
    import kin_model
except ImportError:
    %shell wget -q "https://raw.githubusercontent.com/dmadea/Photo-Kinetic-Simulator/main/kin_model.py"

from kin_model import PhotoKineticSymbolicModel
```

    <IPython.core.display.Javascript object>

```python
#@title Fontsize of the Output Cells and Figures { display-mode: "form", run: "auto" }

#@markdown Set the fontsize of the text in output cells and graphs,
#@markdown default value is 18px and 14, respectively.

#@markdown **Note**: In order for the change to have an effect
#@markdown you have to rerun the cells.

font_size = 18#@param {type:"integer"}
figures_font_size = 14#@param {type:"integer"}

# from https://stackoverflow.com/questions/61957742/how-to-increase-font-size-of-google-colab-cell-output
def increase_font():
    # set font size and ticks widths of matplotlib figures
    plt.rcParams.update({'font.size': figures_font_size})
    plt.rcParams.update({'xtick.major.size': 5, 'ytick.major.size': 5})
    plt.rcParams.update({'xtick.minor.size': 2.5, 'ytick.minor.size': 2.5})
    plt.rcParams.update({'xtick.major.width': 1, 'ytick.major.width': 1})
    plt.rcParams.update({'xtick.minor.width': 0.8, 'ytick.minor.width': 0.8})

    from IPython.display import Javascript
    display(Javascript(f'''
    for (rule of document.styleSheets[0].cssRules){{
        if (rule.selectorText=='body') {{
            rule.style.fontSize = '{font_size}px'
            break
        }}
    }}'''))

# from https://colab.research.google.com/gist/blois/2e6a1299b8fd5704974dc67f47323deb/copy-of-latex-render-fail.ipynb
# def typeset():
#     """MathJax initialization for the current cell.

#     This installs and configures MathJax for the current output.
#     """
#     display(HTML("""
#
#
#         """))

if IN_COLAB:
    # set it to run in every cell
    get_ipython().events.register('pre_run_cell', increase_font)
    # get_ipython().events.register('pre_run_cell', typeset)

    # disable
    # get_ipython().events.unregister('pre_run_cell', increase_font)
```

    <IPython.core.display.Javascript object>

### Simple Sequential Kinetic Model

In the following example, the simulation of simple sequential model is shown. The individual operations are explained by the code comments.

```python
# define a sequential kinetic model
text_model = """
A --> B --> C --> D
"""

# instantiate the model
model = PhotoKineticSymbolicModel.from_text(text_model)
le = model.print_model()  # print the model
model.pprint_equations()  # print the ODEs
```

    <IPython.core.display.Javascript object>

    <IPython.core.display.Javascript object>

$$\mathrm{A} \xrightarrow{k_{AB}} \mathrm{B}$$

$$\mathrm{B} \xrightarrow{k_{BC}} \mathrm{C}$$

$$\mathrm{C} \xrightarrow{k_{CD}} \mathrm{D}$$

$\displaystyle \frac{d}{d t} \operatorname{c_{A}}{\left(t \right)} = - k_{AB} \operatorname{c_{A}}{\left(t \right)}$

$\displaystyle \frac{d}{d t} \operatorname{c_{B}}{\left(t \right)} = k_{AB} \operatorname{c_{A}}{\left(t \right)} - k_{BC} \operatorname{c_{B}}{\left(t \right)}$

$\displaystyle \frac{d}{d t} \operatorname{c_{C}}{\left(t \right)} = k_{BC} \operatorname{c_{B}}{\left(t \right)} - k_{CD} \operatorname{c_{C}}{\left(t \right)}$

$\displaystyle \frac{d}{d t} \operatorname{c_{D}}{\left(t \right)} = k_{CD} \operatorname{c_{C}}{\left(t \right)}$

```python
# print the order of rate constants and the compartment names in which they appear in the model
print(f"Rate constants: {model.symbols['rate_constants']}")
print(f"Compartments: {model.symbols['compartments']}")

# set the input parameters
rate_constants = [ 1, 0.5, 0.25 ]  # in the order of k_{AB}, k_{BC}, k_{CD}
initial_concentrations = [ 1, 0, 0, 0 ] # in the order of A, B, C, D

# simulate the model, t_max is the last time point of the simulation
model.simulate_model(rate_constants, initial_concentrations, t_max=20, plot_separately=False)
```

### Simple Photokinetic Model

In the following example, the ground state of a compound absorbs the light and forms a singlet state. It mainly decays back to the ground state, but some will lead to photoreaction to form a product. The purpose is to simulate the time profile of the ground state. Because singlet state lifetime is much shorter than the time changes of the overall steady state reaction, we will make the steady state approximation for the singlet state.

```python
text_model = """
GS -hv-> ^1S --> GS  // k_s  # absorption and decay back to ground state
^1S --> P            // k_r  # formation of the photoproduct from the singlet state
"""

# instantiate the model
model = PhotoKineticSymbolicModel.from_text(text_model)
model.print_model()  # print the model
model.pprint_equations()  # print the ODEs
print('\n')

# make steady state approximation for the singlet state
model.steady_state_approx(['^1S'])

print('\nTo make things easier, we will make the following substitutions:')

ks, kr = model.symbols['rate_constants']
phi_r, tau_F = symbols('\\phi_r, \\tau_F')

print('\n')
# Fluorescence lifetime is inverse of total decay rate of the singlet state
# Quantum yield of the photoreaction is then k_r * fluorescence lifetime
subs=[(1/(ks+kr), tau_F), (kr * tau_F, phi_r)]
for old, new in subs:
    display(Eq(old, new))

print('\nSteady state approximation for singlet state with applied substitutions:')

# perform the steady state approximation for singlet state with applied substitutions
model.steady_state_approx(['^1S'], subs=subs)
```

    <IPython.core.display.Javascript object>

    <IPython.core.display.Javascript object>

$$\mathrm{GS} \xrightarrow{h\nu} \mathrm{^1S}$$

$$\mathrm{^1S} \xrightarrow{k_s} \mathrm{GS}$$

$$\mathrm{^1S} \xrightarrow{k_r} \mathrm{P}$$

$\displaystyle \frac{d}{d t} \operatorname{c_{GS}}{\left(t \right)} = - J \left(1 - 10^{- \epsilon l \operatorname{c_{GS}}{\left(t \right)}}\right) + k_{s} \operatorname{c_{^1S}}{\left(t \right)}$

$\displaystyle \frac{d}{d t} \operatorname{c_{^1S}}{\left(t \right)} = J \left(1 - 10^{- \epsilon l \operatorname{c_{GS}}{\left(t \right)}}\right) - k_{r} \operatorname{c_{^1S}}{\left(t \right)} - k_{s} \operatorname{c_{^1S}}{\left(t \right)}$

$\displaystyle \frac{d}{d t} \operatorname{c_{P}}{\left(t \right)} = k_{r} \operatorname{c_{^1S}}{\left(t \right)}$

$\displaystyle \frac{d}{d t} \operatorname{c_{GS}}{\left(t \right)} = - \frac{J k_{r} \left(1 - 10^{- \epsilon l \operatorname{c_{GS}}{\left(t \right)}}\right)}{k_{r} + k_{s}}$

$\displaystyle \operatorname{c_{^1S}}{\left(t \right)} = \frac{J \left(1 - 10^{- \epsilon l \operatorname{c_{GS}}{\left(t \right)}}\right)}{k_{r} + k_{s}}$

$\displaystyle \frac{d}{d t} \operatorname{c_{P}}{\left(t \right)} = \frac{J k_{r} \left(1 - 10^{- \epsilon l \operatorname{c_{GS}}{\left(t \right)}}\right)}{k_{r} + k_{s}}$

    To make things easier, we will make the following substitutions:

$\displaystyle \frac{1}{k_{r} + k_{s}} = \tau_{F}$

$\displaystyle \tau_{F} k_{r} = \phi_{r}$

    Steady state approximation for singlet state with applied substitutions:

$\displaystyle \frac{d}{d t} \operatorname{c_{GS}}{\left(t \right)} = - J \phi_{r} \left(1 - 10^{- \epsilon l \operatorname{c_{GS}}{\left(t \right)}}\right)$

$\displaystyle \operatorname{c_{^1S}}{\left(t \right)} = J \tau_{F} \left(1 - 10^{- \epsilon l \operatorname{c_{GS}}{\left(t \right)}}\right)$

$\displaystyle \frac{d}{d t} \operatorname{c_{P}}{\left(t \right)} = J \phi_{r} \left(1 - 10^{- \epsilon l \operatorname{c_{GS}}{\left(t \right)}}\right)$

```python
# print the order of rate constants and the compartment names in which they appear in the model
print(f"Rate constants: {model.symbols['rate_constants']}")
print(f"Compartments: {model.symbols['compartments']}")
print(f"Substitutions: {model.symbols['substitutions']}")

# set the input parameters
# as we don't have rate constants in the SS equations anymore, the values
# will not have an effect
rate_constants = [ 1e9, 1e8 ]  # in the order of k_s, k_r
initial_concentrations = [ 2e-5, 0, 0 ] # in the order of GS, ^1S, P
subs = [ 1e8/(1e9+1e8), 1/(1e9 + 1e8) ]  # in the order of tau_F, phi_r

# simulate the model, flux is the 'concentration of photons', it is the J parameter
# l is the length of the cuvette = 1 and epsilon = 1e5 is molar abs. coefficient
# as we start with c=2e-5 M, the initial absorbance A = 2
model.simulate_model(rate_constants, initial_concentrations, substitutions=subs, t_max=600, flux=1e-6, l=1, epsilon=1e5, plot_separately=True)
```

### SIR Epidemic Model

In the following example, the SIR model for epidemic is simulated.

```python
str_model = """
Susceptible + Infected --> 2Infected // k_t   # transmission rate
Infected --> Recovered               // k_r   # recovery rate
Infected --> Dead                    // k_d   # death rate
"""

# instantiate the model
model = PhotoKineticSymbolicModel.from_text(str_model)

model.print_model()
model.pprint_equations()
```

    <IPython.core.display.Javascript object>

    <IPython.core.display.Javascript object>

$$\mathrm{Susceptible} + \mathrm{Infected} \xrightarrow{k_t} \mathrm{Infected} + \mathrm{Infected}$$

$$\mathrm{Infected} \xrightarrow{k_r} \mathrm{Recovered}$$

$$\mathrm{Infected} \xrightarrow{k_d} \mathrm{Dead}$$

$\displaystyle \frac{d}{d t} \operatorname{c_{Susceptible}}{\left(t \right)} = - k_{t} \operatorname{c_{Infected}}{\left(t \right)} \operatorname{c_{Susceptible}}{\left(t \right)}$

$\displaystyle \frac{d}{d t} \operatorname{c_{Infected}}{\left(t \right)} = - k_{d} \operatorname{c_{Infected}}{\left(t \right)} - k_{r} \operatorname{c_{Infected}}{\left(t \right)} + k_{t} \operatorname{c_{Infected}}{\left(t \right)} \operatorname{c_{Susceptible}}{\left(t \right)}$

$\displaystyle \frac{d}{d t} \operatorname{c_{Recovered}}{\left(t \right)} = k_{r} \operatorname{c_{Infected}}{\left(t \right)}$

$\displaystyle \frac{d}{d t} \operatorname{c_{Dead}}{\left(t \right)} = k_{d} \operatorname{c_{Infected}}{\left(t \right)}$

```python
# print the order of rate constants and the compartment names in which they appear in the model
print(f"Rate constants: {model.symbols['rate_constants']}")
print(f"Compartments: {model.symbols['compartments']}")

# set the input parameters, use a range of transmission rate constants
rate_constants = [ np.logspace(-0.3, -0.9, 5), 0.05, 0.005 ]  # in the order of k_t, k_r, k_d
initial_concentrations = [ 1, 1e-4, 0, 0 ] # in the order of Susceptible, Infected, Recovered, Dead

# simulate the model, the model will simulated 5 times for 5 different transmission rates
# that were set in the rate_constants variable
model.simulate_model(rate_constants, initial_concentrations, t_max=2e2, plot_separately=False)
model.simulate_model(rate_constants, initial_concentrations, t_max=2e2, plot_separately=True)  # plot separately the same model
```

### Transient Kinetics of Rose Bengal

In the following example, the transient kinetics of Rose Bengal under degassed conditions will be simulated. The kinetic model is based on our [publication](https://pubs.rsc.org/en/content/articlelanding/2016/cp/c6cp01710j#!divAbstract). The rate data for MeCN were used. Because the singlet state lifetime is much shorter that those of a triplet and radical pairs, we will make a steady state approximation for the singlet state. We need to include the singlet state to the model due to its formation by triplet-triplet annihilation process.

```python
# escape the backslash characters
str_model = """
^1RB^{2-*} --> RB^{2-}                                        // k_s  # singlet decay to ground state
^1RB^{2-*} --> ^3RB^{2-*} --> RB^{2-}                         // k_{isc} ; k_d
^3RB^{2-*} + RB^{2-} --> 2RB^{2-}                             // k_{sq}
^3RB^{2-*} + RB^{2-} --> RB^{\\bullet -} + RB^{\\bullet 3-}   // k_{redox*}
2^3RB^{2-*} --> ^1RB^{2-*}  + RB^{2-}                         // k_{TT}  # triplet-triplet annihilation
2^3RB^{2-*} --> RB^{\\bullet -}  + RB^{\\bullet 3-}           // k_{redox**}
RB^{\\bullet -}  + RB^{\\bullet 3-} --> 2 RB^{2-}             // k_{-eT}  # back electron transfer
"""

# instantiate the model
model = PhotoKineticSymbolicModel.from_text(str_model)

model.print_model()
model.pprint_equations()
```

    <IPython.core.display.Javascript object>

    <IPython.core.display.Javascript object>

$$\mathrm{^1RB^{2-*}} \xrightarrow{k_s} \mathrm{RB^{2-}}$$

$$\mathrm{^1RB^{2-*}} \xrightarrow{k_{isc}} \mathrm{^3RB^{2-*}}$$

$$\mathrm{^3RB^{2-*}} \xrightarrow{k_d} \mathrm{RB^{2-}}$$

$$\mathrm{^3RB^{2-*}} + \mathrm{RB^{2-}} \xrightarrow{k_{sq}} \mathrm{RB^{2-}} + \mathrm{RB^{2-}}$$

$$\mathrm{^3RB^{2-*}} + \mathrm{RB^{2-}} \xrightarrow{k_{redox*}} \mathrm{RB^{\bullet-}} + \mathrm{RB^{\bullet3-}}$$

$$\mathrm{^3RB^{2-*}} + \mathrm{^3RB^{2-*}} \xrightarrow{k_{TT}} \mathrm{^1RB^{2-*}} + \mathrm{RB^{2-}}$$

$$\mathrm{^3RB^{2-*}} + \mathrm{^3RB^{2-*}} \xrightarrow{k_{redox**}} \mathrm{RB^{\bullet-}} + \mathrm{RB^{\bullet3-}}$$

$$\mathrm{RB^{\bullet-}} + \mathrm{RB^{\bullet3-}} \xrightarrow{k_{-eT}} \mathrm{RB^{2-}} + \mathrm{RB^{2-}}$$

$\displaystyle \frac{d}{d t} \operatorname{c_{^1RB^{2-*}}}{\left(t \right)} = - k_{s} \operatorname{c_{^1RB^{2-*}}}{\left(t \right)} + k_{TT} \left(\operatorname{c_{^3RB^{2-*}}}\right)^{2}{\left(t \right)} - k_{isc} \operatorname{c_{^1RB^{2-*}}}{\left(t \right)}$

$\displaystyle \frac{d}{d t} \operatorname{c_{RB^{2-}}}{\left(t \right)} = k_{d} \operatorname{c_{^3RB^{2-*}}}{\left(t \right)} + k_{s} \operatorname{c_{^1RB^{2-*}}}{\left(t \right)} + 2 k_{-eT} \operatorname{c_{RB^{\bullet-}}}{\left(t \right)} \operatorname{c_{RB^{\bullet3-}}}{\left(t \right)} + k_{TT} \left(\operatorname{c_{^3RB^{2-*}}}\right)^{2}{\left(t \right)} - k_{redox*} \operatorname{c_{RB^{2-}}}{\left(t \right)} \operatorname{c_{^3RB^{2-*}}}{\left(t \right)} + k_{sq} \operatorname{c_{RB^{2-}}}{\left(t \right)} \operatorname{c_{^3RB^{2-*}}}{\left(t \right)}$

$\displaystyle \frac{d}{d t} \operatorname{c_{^3RB^{2-*}}}{\left(t \right)} = - k_{d} \operatorname{c_{^3RB^{2-*}}}{\left(t \right)} - 2 k_{TT} \left(\operatorname{c_{^3RB^{2-*}}}\right)^{2}{\left(t \right)} + k_{isc} \operatorname{c_{^1RB^{2-*}}}{\left(t \right)} - 2 k_{redox**} \left(\operatorname{c_{^3RB^{2-*}}}\right)^{2}{\left(t \right)} - k_{redox*} \operatorname{c_{RB^{2-}}}{\left(t \right)} \operatorname{c_{^3RB^{2-*}}}{\left(t \right)} - k_{sq} \operatorname{c_{RB^{2-}}}{\left(t \right)} \operatorname{c_{^3RB^{2-*}}}{\left(t \right)}$

$\displaystyle \frac{d}{d t} \operatorname{c_{RB^{\bullet-}}}{\left(t \right)} = - k_{-eT} \operatorname{c_{RB^{\bullet-}}}{\left(t \right)} \operatorname{c_{RB^{\bullet3-}}}{\left(t \right)} + k_{redox**} \left(\operatorname{c_{^3RB^{2-*}}}\right)^{2}{\left(t \right)} + k_{redox*} \operatorname{c_{RB^{2-}}}{\left(t \right)} \operatorname{c_{^3RB^{2-*}}}{\left(t \right)}$

$\displaystyle \frac{d}{d t} \operatorname{c_{RB^{\bullet3-}}}{\left(t \right)} = - k_{-eT} \operatorname{c_{RB^{\bullet-}}}{\left(t \right)} \operatorname{c_{RB^{\bullet3-}}}{\left(t \right)} + k_{redox**} \left(\operatorname{c_{^3RB^{2-*}}}\right)^{2}{\left(t \right)} + k_{redox*} \operatorname{c_{RB^{2-}}}{\left(t \right)} \operatorname{c_{^3RB^{2-*}}}{\left(t \right)}$

```python
# make substitutions, create phi_isc and fluorescence lifetime
ks, kisc = model.symbols['rate_constants'][:2]
phi_isc, tau_F = symbols('\\phi_{isc} \\tau_F')

# perform the steady state approximation for singlet state
# substitutions are sequential, first entry of tuple is old expression and the latter the new expression
model.steady_state_approx(['^1RB^{2-*}'], subs=[(1/(ks+kisc), tau_F), (tau_F*kisc, phi_isc), (tau_F*ks, 1 - phi_isc)])
```

    <IPython.core.display.Javascript object>

    <IPython.core.display.Javascript object>

$\displaystyle \operatorname{c_{^1RB^{2-*}}}{\left(t \right)} = \tau_{F} k_{TT} \left(\operatorname{c_{^3RB^{2-*}}}\right)^{2}{\left(t \right)}$

$\displaystyle \frac{d}{d t} \operatorname{c_{RB^{2-}}}{\left(t \right)} = - \phi_{isc} k_{TT} \left(\operatorname{c_{^3RB^{2-*}}}\right)^{2}{\left(t \right)} + k_{d} \operatorname{c_{^3RB^{2-*}}}{\left(t \right)} + 2 k_{-eT} \operatorname{c_{RB^{\bullet-}}}{\left(t \right)} \operatorname{c_{RB^{\bullet3-}}}{\left(t \right)} + 2 k_{TT} \left(\operatorname{c_{^3RB^{2-*}}}\right)^{2}{\left(t \right)} - k_{redox*} \operatorname{c_{RB^{2-}}}{\left(t \right)} \operatorname{c_{^3RB^{2-*}}}{\left(t \right)} + k_{sq} \operatorname{c_{RB^{2-}}}{\left(t \right)} \operatorname{c_{^3RB^{2-*}}}{\left(t \right)}$

$\displaystyle \frac{d}{d t} \operatorname{c_{^3RB^{2-*}}}{\left(t \right)} = \left(\phi_{isc} k_{TT} \operatorname{c_{^3RB^{2-*}}}{\left(t \right)} - k_{d} - 2 k_{TT} \operatorname{c_{^3RB^{2-*}}}{\left(t \right)} - 2 k_{redox**} \operatorname{c_{^3RB^{2-*}}}{\left(t \right)} - k_{redox*} \operatorname{c_{RB^{2-}}}{\left(t \right)} - k_{sq} \operatorname{c_{RB^{2-}}}{\left(t \right)}\right) \operatorname{c_{^3RB^{2-*}}}{\left(t \right)}$

$\displaystyle \frac{d}{d t} \operatorname{c_{RB^{\bullet-}}}{\left(t \right)} = - k_{-eT} \operatorname{c_{RB^{\bullet-}}}{\left(t \right)} \operatorname{c_{RB^{\bullet3-}}}{\left(t \right)} + k_{redox**} \left(\operatorname{c_{^3RB^{2-*}}}\right)^{2}{\left(t \right)} + k_{redox*} \operatorname{c_{RB^{2-}}}{\left(t \right)} \operatorname{c_{^3RB^{2-*}}}{\left(t \right)}$

$\displaystyle \frac{d}{d t} \operatorname{c_{RB^{\bullet3-}}}{\left(t \right)} = - k_{-eT} \operatorname{c_{RB^{\bullet-}}}{\left(t \right)} \operatorname{c_{RB^{\bullet3-}}}{\left(t \right)} + k_{redox**} \left(\operatorname{c_{^3RB^{2-*}}}\right)^{2}{\left(t \right)} + k_{redox*} \operatorname{c_{RB^{2-}}}{\left(t \right)} \operatorname{c_{^3RB^{2-*}}}{\left(t \right)}$

```python
# print the order of rate constants and the compartment names in which they appear in the model
print(f"Rate constants: {model.symbols['rate_constants']}")
print(f"Compartments: {model.symbols['compartments']}")
print(f"Substitutions: {model.symbols['substitutions']}")

# parameters in MeCN, taken from https://pubs.rsc.org/en/content/articlelanding/2016/cp/c6cp01710j#!divAbstract
fl_lifetime = 2380e-12  # fluorescence lifetime
k_s_tot = 1/fl_lifetime  # total decay rate of the singlet state
k_isc = 2.77e8  # rate of intersystem crossing
phi_isc = k_isc / k_s_tot  # intersystem crossing quantum yield
# k_s_GS = (1 - phi_isc) * k_s_tot  # decay rate of singlet state to ground state

# ks and k_isc are not necessary to fill as they are not present in SS equations
rate_constants = [ None, None, 1.01e4, 7.63e8, 3.89e7, 9.98e8, 1.86e8, 1.03e10]

c_tot = 75e-6  # total concentration of RB
c_triplet = np.linspace(10e-6, c_tot, 8)  # various concentrations of triplet
c_GS = c_tot - c_triplet
initial_concentrations = [ 0, c_GS, c_triplet, 0, 0 ]
subs = [ fl_lifetime , phi_isc]  # define substitutions

# simulate the model, the model will simulated 8 times for 8 different GS and triplet
# initial concentrations, total concentration of RB is kept constant
model.simulate_model(rate_constants, initial_concentrations, substitutions=subs, t_max=2e-4, plot_separately=True)
```

### Decomposition of Endoperoxide

Here, the decomposition of aryl endoperoxide is shown. It produces singlet oxygen which can be traped with some substrate S. Singlet oxygen in solution has quite short lifetime and also, it can be physically quenched by all species contained in a solution. This is simulated in the following example.

```python
str_model = """
ArO_2 --> Ar + ^1O_2                 // k_1  # production of singlet oxygen
^1O_2 --> ^3O_2                      // k_d  # decay rate of singlet oxygen through solvent
# ^1O_2 + Ar --> Ar + ^3O_2          // k_{q,Ar}     # quenching of singlet ox. by naphthalene
^1O_2 + ArO_2 --> ArO_2 + ^3O_2      // k_{q}^{\\prime}  # quenching of singlet ox. by endoperoxide
^1O_2 + S --> S + ^3O_2              // k_{q}  # quenching of singlet ox. by substrate
S + ^1O_2 -->                        // k_r    # reaction of substrate with singlet ox.
"""

model = PhotoKineticSymbolicModel.from_text(str_model)

model.print_model()
model.steady_state_approx(['^1O_2'])  # make SS approximation for singlet oxygen
```

    <IPython.core.display.Javascript object>

    <IPython.core.display.Javascript object>

$$\mathrm{ArO_2} \xrightarrow{k_1} \mathrm{Ar} + \mathrm{^1O_2}$$

$$\mathrm{^1O_2} \xrightarrow{k_d} \mathrm{^3O_2}$$

$$\mathrm{^1O_2} + \mathrm{ArO_2} \xrightarrow{k_{q}^{\prime}} \mathrm{ArO_2} + \mathrm{^3O_2}$$

$$\mathrm{^1O_2} + \mathrm{S} \xrightarrow{k_{q}} \mathrm{S} + \mathrm{^3O_2}$$

$$\mathrm{S} + \mathrm{^1O_2} \xrightarrow{k_r} $$

$\displaystyle \frac{d}{d t} \operatorname{c_{ArO_2}}{\left(t \right)} = - k_{1} \operatorname{c_{ArO_2}}{\left(t \right)}$

$\displaystyle \frac{d}{d t} \operatorname{c_{Ar}}{\left(t \right)} = k_{1} \operatorname{c_{ArO_2}}{\left(t \right)}$

$\displaystyle \operatorname{c_{^1O_2}}{\left(t \right)} = \frac{k_{1} \operatorname{c_{ArO_2}}{\left(t \right)}}{k_{d} + k_{r} \operatorname{c_{S}}{\left(t \right)} + k_{q} \operatorname{c_{S}}{\left(t \right)} + k_{q}^{\prime} \operatorname{c_{ArO_2}}{\left(t \right)}}$

$\displaystyle \frac{d}{d t} \operatorname{c_{^3O_2}}{\left(t \right)} = \frac{k_{1} \left(k_{d} + k_{q} \operatorname{c_{S}}{\left(t \right)} + k_{q}^{\prime} \operatorname{c_{ArO_2}}{\left(t \right)}\right) \operatorname{c_{ArO_2}}{\left(t \right)}}{k_{d} + k_{r} \operatorname{c_{S}}{\left(t \right)} + k_{q} \operatorname{c_{S}}{\left(t \right)} + k_{q}^{\prime} \operatorname{c_{ArO_2}}{\left(t \right)}}$

$\displaystyle \frac{d}{d t} \operatorname{c_{S}}{\left(t \right)} = - \frac{k_{1} k_{r} \operatorname{c_{ArO_2}}{\left(t \right)} \operatorname{c_{S}}{\left(t \right)}}{k_{d} + k_{r} \operatorname{c_{S}}{\left(t \right)} + k_{q} \operatorname{c_{S}}{\left(t \right)} + k_{q}^{\prime} \operatorname{c_{ArO_2}}{\left(t \right)}}$

```python
# print the order of rate constants and the compartment names in which they appear in the model
print(f"Rate constants: {model.symbols['rate_constants']}")
print(f"Compartments: {model.symbols['compartments']}")

# set the input parameters, use a range of transmission rate constants
rate_constants = [ 3.4e-5, 1/9.5e-6, 1e4, 1e5, 1e9 ]
initial_concentrations = [1e-2, 0, 0, 0, np.linspace(1e-5, 1e-4, 8) ]

model.simulate_model(rate_constants, initial_concentrations,
                     constant_compartments=['^3O_2'], t_max=1e3, plot_separately=True)
```

### Degradation of a Sensitizer

Photosensitizer produces singlet oxygen by TT annihilation. Singlet oxygen then slowly destroys the photosensitizer. The rate laws are generated in the following example.

```python
str_model = """
PS -hv-> ^1PS --> PS          // k_s  # population of singlet state and decay to GS with
^1PS --> ^3PS --> PS          // k_{isc} ; k_T
^3PS + ^3O_2 --> ^1O_2 + PS   // k_{TT}
^1O_2 --> ^3O_2               // k_d
^1O_2 + PS --> PS + ^3O_2     // k_q
PS + ^1O_2 -->                // k_r
"""

model = PhotoKineticSymbolicModel.from_text(str_model)

model.print_model()
print('\n')

# make substitutions, create phi_isc and fluorescence lifetime
ks, kisc = model.symbols['rate_constants'][:2]
phi_isc, tau_F = symbols('\\phi_{isc} tau_F')

# perform the steady state approximation for singlet, triplet and singlet oxygen
# substitutions are sequential, first entry of tuple is old expression and the latter the new expression
model.steady_state_approx(['^1PS', '^3PS', '^1O_2'], subs=[(1/(ks+kisc), tau_F), (tau_F*kisc, phi_isc)])
```

    <IPython.core.display.Javascript object>

    <IPython.core.display.Javascript object>

$$\mathrm{PS} \xrightarrow{h\nu} \mathrm{^1PS}$$

$$\mathrm{^1PS} \xrightarrow{k_s} \mathrm{PS}$$

$$\mathrm{^1PS} \xrightarrow{k_{isc}} \mathrm{^3PS}$$

$$\mathrm{^3PS} \xrightarrow{k_T} \mathrm{PS}$$

$$\mathrm{^3PS} + \mathrm{^3O_2} \xrightarrow{k_{TT}} \mathrm{^1O_2} + \mathrm{PS}$$

$$\mathrm{^1O_2} \xrightarrow{k_d} \mathrm{^3O_2}$$

$$\mathrm{^1O_2} + \mathrm{PS} \xrightarrow{k_q} \mathrm{PS} + \mathrm{^3O_2}$$

$$\mathrm{PS} + \mathrm{^1O_2} \xrightarrow{k_r} $$

$\displaystyle \frac{d}{d t} \operatorname{c_{PS}}{\left(t \right)} = - \frac{J \phi_{isc} k_{r} k_{TT} \left(1 - 10^{- \epsilon l \operatorname{c_{PS}}{\left(t \right)}}\right) \operatorname{c_{PS}}{\left(t \right)} \operatorname{c_{^3O_2}}{\left(t \right)}}{\left(k_{T} + k_{TT} \operatorname{c_{^3O_2}}{\left(t \right)}\right) \left(k_{d} + k_{q} \operatorname{c_{PS}}{\left(t \right)} + k_{r} \operatorname{c_{PS}}{\left(t \right)}\right)}$

$\displaystyle \operatorname{c_{^1PS}}{\left(t \right)} = J \tau_{F} \left(1 - 10^{- \epsilon l \operatorname{c_{PS}}{\left(t \right)}}\right)$

$\displaystyle \operatorname{c_{^3PS}}{\left(t \right)} = \frac{J \phi_{isc} \left(1 - 10^{- \epsilon l \operatorname{c_{PS}}{\left(t \right)}}\right)}{k_{T} + k_{TT} \operatorname{c_{^3O_2}}{\left(t \right)}}$

$\displaystyle \frac{d}{d t} \operatorname{c_{^3O_2}}{\left(t \right)} = - \frac{J \phi_{isc} k_{r} k_{TT} \left(1 - 10^{- \epsilon l \operatorname{c_{PS}}{\left(t \right)}}\right) \operatorname{c_{PS}}{\left(t \right)} \operatorname{c_{^3O_2}}{\left(t \right)}}{\left(k_{T} + k_{TT} \operatorname{c_{^3O_2}}{\left(t \right)}\right) \left(k_{d} + k_{q} \operatorname{c_{PS}}{\left(t \right)} + k_{r} \operatorname{c_{PS}}{\left(t \right)}\right)}$

$\displaystyle \operatorname{c_{^1O_2}}{\left(t \right)} = \frac{J \phi_{isc} k_{TT} \left(1 - 10^{- \epsilon l \operatorname{c_{PS}}{\left(t \right)}}\right) \operatorname{c_{^3O_2}}{\left(t \right)}}{\left(k_{T} + k_{TT} \operatorname{c_{^3O_2}}{\left(t \right)}\right) \left(k_{d} + k_{q} \operatorname{c_{PS}}{\left(t \right)} + k_{r} \operatorname{c_{PS}}{\left(t \right)}\right)}$

### The Dependence of Chemical Quantum Yields of Visible Light Photoredox Catalysis on the Irradiation Power

In the following example, the steady state kinetics of complex photoredox reaction is shown. The kinetic model and rate constants are taken from [ChemPhotoChem Paper (Open Access)](https://doi.org/10.1002/cptc.202100090). Due to the high nonlinearity of the differential equations, only stiff [BDF](https://docs.scipy.org/doc/scipy/reference/generated/scipy.integrate.BDF.html#ra064ca079e93-1) solver was found to simulate the model with sufficient precision.

```python
# species = OrCat, Oct, Ena, H_2O, Imin = Imin^+, DOM, Ru = Ru(bpy)_3^{2+},
# RuT = ^3Ru(bpy)_3^{2+}, RuC = Ru(bpy)_3^+, Mal. = Mal\\bullet, BrMal,
# AmiR = Ami\\bullet

str_model = """
OrCat + Oct --> H_2O + Ena                                 // k_1
H_2O + Ena --> OrCat + Oct                                 // k_{-1}

# photoredox initiation
Ru(bpy)_3^{2+} -hv-> ^3Ru(bpy)_3^{2+} --> Ru(bpy)_3^{2+}   // k_2
^3Ru(bpy)_3^{2+} + Ena --> Ru(bpy)_3^+                     // k_3
Ru(bpy)_3^+ + BrMal --> Mal\\bullet + Ru(bpy)_3^{2+}       // k_4

# reactions
Ena + Mal\\bullet --> Ami\\bullet                          // k_5
Ami\\bullet + BrMal --> Imin^+ + Mal\\bullet   // k_6
Ami\\bullet + ^3Ru(bpy)_3^{2+} --> Imin^+ + Ru(bpy)_3^+    // k_7
Imin^+ + H_2O --> DOM + OrCat                              // k_8
DOM + OrCat --> Imin^+ + H_2O                              // k_{-8}
Mal\\bullet -->                                            // k_9
"""

# instantiate the model
model = PhotoKineticSymbolicModel.from_text(str_model)

model.print_model()
model.pprint_equations()
```

    <IPython.core.display.Javascript object>

    <IPython.core.display.Javascript object>

$$\mathrm{OrCat} + \mathrm{Oct}\ \overset{k_1}{\underset{k_{-1}}\rightleftharpoons}\ \mathrm{H_2O} + \mathrm{Ena}$$

$$\mathrm{Ru(bpy)_3^{2+}} \xrightarrow{h\nu} \mathrm{^3Ru(bpy)_3^{2+}}$$

$$\mathrm{^3Ru(bpy)_3^{2+}} \xrightarrow{k_2} \mathrm{Ru(bpy)_3^{2+}}$$

$$\mathrm{^3Ru(bpy)_3^{2+}} + \mathrm{Ena} \xrightarrow{k_3} \mathrm{Ru(bpy)_3^+}$$

$$\mathrm{Ru(bpy)_3^+} + \mathrm{BrMal} \xrightarrow{k_4} \mathrm{Mal\bullet} + \mathrm{Ru(bpy)_3^{2+}}$$

$$\mathrm{Ena} + \mathrm{Mal\bullet} \xrightarrow{k_5} \mathrm{Ami\bullet}$$

$$\mathrm{Ami\bullet} + \mathrm{BrMal} \xrightarrow{k_6} \mathrm{Imin^+} + \mathrm{Mal\bullet}$$

$$\mathrm{Ami\bullet} + \mathrm{^3Ru(bpy)_3^{2+}} \xrightarrow{k_7} \mathrm{Imin^+} + \mathrm{Ru(bpy)_3^+}$$

$$\mathrm{Imin^+} + \mathrm{H_2O}\ \overset{k_8}{\underset{k_{-8}}\rightleftharpoons}\ \mathrm{DOM} + \mathrm{OrCat}$$

$$\mathrm{Mal\bullet} \xrightarrow{k_9} $$

$\displaystyle \frac{d}{d t} \operatorname{c_{OrCat}}{\left(t \right)} = - k_{1} \operatorname{c_{Oct}}{\left(t \right)} \operatorname{c_{OrCat}}{\left(t \right)} + k_{8} \operatorname{c_{H_2O}}{\left(t \right)} \operatorname{c_{Imin^+}}{\left(t \right)} + k_{-1} \operatorname{c_{Ena}}{\left(t \right)} \operatorname{c_{H_2O}}{\left(t \right)} - k_{-8} \operatorname{c_{DOM}}{\left(t \right)} \operatorname{c_{OrCat}}{\left(t \right)}$

$\displaystyle \frac{d}{d t} \operatorname{c_{Oct}}{\left(t \right)} = - k_{1} \operatorname{c_{Oct}}{\left(t \right)} \operatorname{c_{OrCat}}{\left(t \right)} + k_{-1} \operatorname{c_{Ena}}{\left(t \right)} \operatorname{c_{H_2O}}{\left(t \right)}$

$\displaystyle \frac{d}{d t} \operatorname{c_{H_2O}}{\left(t \right)} = k_{1} \operatorname{c_{Oct}}{\left(t \right)} \operatorname{c_{OrCat}}{\left(t \right)} - k_{8} \operatorname{c_{H_2O}}{\left(t \right)} \operatorname{c_{Imin^+}}{\left(t \right)} - k_{-1} \operatorname{c_{Ena}}{\left(t \right)} \operatorname{c_{H_2O}}{\left(t \right)} + k_{-8} \operatorname{c_{DOM}}{\left(t \right)} \operatorname{c_{OrCat}}{\left(t \right)}$

$\displaystyle \frac{d}{d t} \operatorname{c_{Ena}}{\left(t \right)} = k_{1} \operatorname{c_{Oct}}{\left(t \right)} \operatorname{c_{OrCat}}{\left(t \right)} - k_{3} \operatorname{c_{Ena}}{\left(t \right)} \operatorname{c_{^3Ru(bpy)_3^{2+}}}{\left(t \right)} - k_{5} \operatorname{c_{Ena}}{\left(t \right)} \operatorname{c_{Mal\bullet}}{\left(t \right)} - k_{-1} \operatorname{c_{Ena}}{\left(t \right)} \operatorname{c_{H_2O}}{\left(t \right)}$

$\displaystyle \frac{d}{d t} \operatorname{c_{Ru(bpy)_3^{2+}}}{\left(t \right)} = - J \left(1 - 10^{- \epsilon l \operatorname{c_{Ru(bpy)_3^{2+}}}{\left(t \right)}}\right) + k_{2} \operatorname{c_{^3Ru(bpy)_3^{2+}}}{\left(t \right)} + k_{4} \operatorname{c_{BrMal}}{\left(t \right)} \operatorname{c_{Ru(bpy)_3^+}}{\left(t \right)}$

$\displaystyle \frac{d}{d t} \operatorname{c_{^3Ru(bpy)_3^{2+}}}{\left(t \right)} = J \left(1 - 10^{- \epsilon l \operatorname{c_{Ru(bpy)_3^{2+}}}{\left(t \right)}}\right) - k_{2} \operatorname{c_{^3Ru(bpy)_3^{2+}}}{\left(t \right)} - k_{3} \operatorname{c_{Ena}}{\left(t \right)} \operatorname{c_{^3Ru(bpy)_3^{2+}}}{\left(t \right)} - k_{7} \operatorname{c_{Ami\bullet}}{\left(t \right)} \operatorname{c_{^3Ru(bpy)_3^{2+}}}{\left(t \right)}$

$\displaystyle \frac{d}{d t} \operatorname{c_{Ru(bpy)_3^+}}{\left(t \right)} = k_{3} \operatorname{c_{Ena}}{\left(t \right)} \operatorname{c_{^3Ru(bpy)_3^{2+}}}{\left(t \right)} - k_{4} \operatorname{c_{BrMal}}{\left(t \right)} \operatorname{c_{Ru(bpy)_3^+}}{\left(t \right)} + k_{7} \operatorname{c_{Ami\bullet}}{\left(t \right)} \operatorname{c_{^3Ru(bpy)_3^{2+}}}{\left(t \right)}$

$\displaystyle \frac{d}{d t} \operatorname{c_{BrMal}}{\left(t \right)} = - k_{4} \operatorname{c_{BrMal}}{\left(t \right)} \operatorname{c_{Ru(bpy)_3^+}}{\left(t \right)} - k_{6} \operatorname{c_{Ami\bullet}}{\left(t \right)} \operatorname{c_{BrMal}}{\left(t \right)}$

$\displaystyle \frac{d}{d t} \operatorname{c_{Mal\bullet}}{\left(t \right)} = k_{4} \operatorname{c_{BrMal}}{\left(t \right)} \operatorname{c_{Ru(bpy)_3^+}}{\left(t \right)} - k_{5} \operatorname{c_{Ena}}{\left(t \right)} \operatorname{c_{Mal\bullet}}{\left(t \right)} + k_{6} \operatorname{c_{Ami\bullet}}{\left(t \right)} \operatorname{c_{BrMal}}{\left(t \right)} - k_{9} \operatorname{c_{Mal\bullet}}{\left(t \right)}$

$\displaystyle \frac{d}{d t} \operatorname{c_{Ami\bullet}}{\left(t \right)} = k_{5} \operatorname{c_{Ena}}{\left(t \right)} \operatorname{c_{Mal\bullet}}{\left(t \right)} - k_{6} \operatorname{c_{Ami\bullet}}{\left(t \right)} \operatorname{c_{BrMal}}{\left(t \right)} - k_{7} \operatorname{c_{Ami\bullet}}{\left(t \right)} \operatorname{c_{^3Ru(bpy)_3^{2+}}}{\left(t \right)}$

$\displaystyle \frac{d}{d t} \operatorname{c_{Imin^+}}{\left(t \right)} = k_{6} \operatorname{c_{Ami\bullet}}{\left(t \right)} \operatorname{c_{BrMal}}{\left(t \right)} + k_{7} \operatorname{c_{Ami\bullet}}{\left(t \right)} \operatorname{c_{^3Ru(bpy)_3^{2+}}}{\left(t \right)} - k_{8} \operatorname{c_{H_2O}}{\left(t \right)} \operatorname{c_{Imin^+}}{\left(t \right)} + k_{-8} \operatorname{c_{DOM}}{\left(t \right)} \operatorname{c_{OrCat}}{\left(t \right)}$

$\displaystyle \frac{d}{d t} \operatorname{c_{DOM}}{\left(t \right)} = k_{8} \operatorname{c_{H_2O}}{\left(t \right)} \operatorname{c_{Imin^+}}{\left(t \right)} - k_{-8} \operatorname{c_{DOM}}{\left(t \right)} \operatorname{c_{OrCat}}{\left(t \right)}$

```python
# print the order of rate constants and the compartment names in which they appear in the model
print(f"Rate constants: {model.symbols['rate_constants']}")
print(f"Compartments: {model.symbols['compartments']}")

rate_constants = [ 8e-4, 0.1, 1e6, 1e7, 2.7e7, 1e8, 0.08, 8e9, 1, 0.3, 103 ]
init = [ 0.1, 1, 0,  0, 2.5e-3, 0, 0, 1, 0, 0, 0, 0]

model.simulate_model(rate_constants, init, t_max=2e4, flux=np.linspace(1e-7, 1e-5, 8), l=1, epsilon=1.5e4, plot_separately=True, yscale='linear',
                    constant_compartments=['Ru(bpy)_3^{2+}'], ODE_solver='BDF', t_points=1e4, precise_simulation=True)
```