from example_models import get_linear_chain_1v, get_linear_chain_2v
from mxlpy.meta import (
generate_latex_code,
generate_model_code_py,
generate_model_code_rs,
generate_mxlpy_code,
to_tex_export,
)
Metaprogramming¶
For models only containing pure Python functions, mxlpy
contains advanced meta-programming features, such as code generation of LaTeX
, Python
and rust
code.
code generation¶
mxlpy
can generate own source code from a model.
print(generate_mxlpy_code(get_linear_chain_1v()))
from mxlpy import Model def constant(k_in: float) -> float: return k_in def mass_action_1s(k_out: float, x: float) -> float: return k_out*x def create_model() -> Model: return ( Model() .add_parameters({'k_in': 1.0, 'k_out': 1.0}) .add_variables({'x': Variable(initial_value=1.0, unit=None, source=None)}) .add_reaction( "v_in", fn=constant, args=['k_in'], stoichiometry={"x": 1}, ) .add_reaction( "v_out", fn=mass_action_1s, args=['k_out', 'x'], stoichiometry={"x": -1}, ) )
mxlpy
can also generate a generic python function from the source code.
The plan here is to generalise this to be able to export models into other programming languages as well.
print(generate_model_code_py(get_linear_chain_2v()))
from collections.abc import Iterable def model(time: float, variables: Iterable[float]) -> Iterable[float]: x, y = variables k1 = 1.0 k2 = 2.0 k3 = 1.0 v1 = k1 v2 = k2*x v3 = k3*y dxdt = v1 - v2 dydt = v2 - v3 return dxdt, dydt
print(generate_model_code_rs(get_linear_chain_2v()))
fn model(time: f64, variables: &[f64; 2]) -> [f64; 2] { let [x, y] = *variables; let k1: f64 = 1.0; let k2: f64 = 2.0; let k3: f64 = 1.0; let v1: f64 = k1; let v2: f64 = k2*x; let v3: f64 = k3*y; let dxdt: f64 = v1 - v2; let dydt: f64 = v2 - v3; return [dxdt, dydt] }
Free parameters¶
In case you want to perform further analyses like parameter scans, you can also define free parameters which then will be additional model inputs
print(generate_model_code_py(get_linear_chain_2v(), free_parameters=["k1"]))
from collections.abc import Iterable def model(time: float, variables: Iterable[float], k1: float) -> Iterable[float]: x, y = variables k2 = 2.0 k3 = 1.0 v1 = k1 v2 = k2*x v3 = k3*y dxdt = v1 - v2 dydt = v2 - v3 return dxdt, dydt
print(generate_model_code_rs(get_linear_chain_2v(), free_parameters=["k1"]))
fn model(time: f64, variables: &[f64; 2], k1: f64) -> [f64; 2] { let [x, y] = *variables; let k2: f64 = 2.0; let k3: f64 = 1.0; let v1: f64 = k1; let v2: f64 = k2*x; let v3: f64 = k3*y; let dxdt: f64 = v1 - v2; let dydt: f64 = v2 - v3; return [dxdt, dydt] }
LaTeX export¶
mxlpy
supports writing out your model as LaTeX
.
You can provide a mapping of model names to latex names using the gls
argument.
This export will automatically shorten long names exceeding the long_name_cutoff
parameter.
print(generate_latex_code(get_linear_chain_1v()))
\documentclass[fleqn]{article} \usepackage[english]{babel} \usepackage[a4paper,top=2cm,bottom=2cm,left=2cm,right=2cm,marginparwidth=1.75cm]{geometry} \usepackage{amsmath, amssymb, array, booktabs, breqn, caption, longtable, mathtools, placeins, ragged2e, tabularx, titlesec, titling, xcolor} \newcommand{\sectionbreak}{\clearpage} \setlength{\parindent}{0pt} \allowdisplaybreaks \title{Model construction} \date{} % clear date \author{mxlpy} \begin{document} \maketitle \FloatBarrier\subsection*{Variables} \begin{longtable}{c|c} Model name & Initial concentration \\ \hline \endhead x & 1.00e+00 \\ \caption[Model variables]{Model variables} \label{table:model-vars} \end{longtable} \FloatBarrier\subsection*{Parameters} \begin{longtable}{c|c} Parameter name & Parameter value \\ \hline \endhead k\_in & 1.00e+00 \\ k\_out & 1.00e+00 \\ \caption[Model parameters]{Model parameters} \label{table:model-pars} \end{longtable} \FloatBarrier\subsection*{Reactions} \begin{align*} \mathrm{v\_in} &= \mathrm{k\_in} \\ \mathrm{v\_out} &= \mathrm{k\_out} \cdot \mathrm{x} \\ \end{align*} \FloatBarrier\subsection*{Differential\_equations} \begin{align*} \frac{d\left(\mathrm{x}\right)}{dt} &= v\_{in} - v\_{out} \\ \end{align*} \end{document}
Exporting only parts¶
In case you only need parts of the LaTeX
export, you can also directly create it
tex = to_tex_export(get_linear_chain_1v())
# Optionally add glossary here
tex = tex.rename_with_glossary({"x": r"\mu"})
print(tex.export_variables())
\begin{longtable}{c|c} Model name & Initial concentration \\ \hline \endhead \mu & 1.00e+00 \\ \caption[Model variables]{Model variables} \label{table:model-vars} \end{longtable}
print(tex.export_parameters())
\begin{longtable}{c|c} Parameter name & Parameter value \\ \hline \endhead k\_in & 1.00e+00 \\ k\_out & 1.00e+00 \\ \caption[Model parameters]{Model parameters} \label{table:model-pars} \end{longtable}
print(tex.export_derived(long_name_cutoff=10))
\begin{align*} \end{align*}
print(tex.export_reactions(long_name_cutoff=10))
\begin{align*} \mathrm{v\_in} &= \mathrm{k\_in} \\ \mathrm{v\_out} &= \mathrm{\mu} \cdot \mathrm{k\_out} \\ \end{align*}
print(tex.export_diff_eqs(long_name_cutoff=10))
\begin{align*} \frac{d\left(\mathrm{\acrfull{\mu}}\right)}{dt} &= v\_{in} - v\_{out} \\ \end{align*}
First finish line
With that you now know most of what you will need from a day-to-day basis about meta programming in mxlpy.Congratulations!
Placeholders / error handling¶
In case one of your model functions cannot be parsed by, MxlPy
will insert a red warning instead into the LaTeX
export.
That way, you can still re-use the remainder of the it.
import numpy as np
from mxlpy import Model
def numpy_fn() -> float:
return np.exp(1.0)
tex = to_tex_export(Model().add_derived("d1", numpy_fn, args=[]))
print(tex.export_derived(long_name_cutoff=10))
WARNING:mxlpy.meta.source_tools:Failed parsing function of d1
\begin{align*} \mathrm{d1} &= \textcolor{red}{d1} \\ \end{align*}
This is not the case for automatic code generation, where we will throw errors instead.
As these generated models might be part of bigger pipelines, it is important for them not to produce wrong output silently.
try:
generate_model_code_py(Model().add_derived("d1", numpy_fn, args=[]))
except ValueError as e:
print("Errored:", e)
WARNING:mxlpy.meta.source_tools:Failed parsing function of d1
Errored: Unable to parse fn for derived value 'd1'
try:
generate_model_code_rs(Model().add_derived("d1", numpy_fn, args=[]))
except ValueError as e:
print("Errored:", e)
WARNING:mxlpy.meta.source_tools:Failed parsing function of d1
Errored: Unable to parse fn for derived value 'd1'