{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Continuously stirred tank reactor (CSTR)\n", "This notebook shows how to solve chemical kintics problems for a continuously stirred tank reactor using ChemPy." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from collections import defaultdict\n", "import numpy as np\n", "from IPython.display import Latex\n", "import matplotlib.pyplot as plt\n", "from pyodesys.symbolic import SymbolicSys\n", "from chempy import Substance, ReactionSystem\n", "from chempy.kinetics.ode import get_odesys\n", "from chempy.units import SI_base_registry, default_units as u\n", "from chempy.util.graph import rsys2graph\n", "%matplotlib inline" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "rsys = ReactionSystem.from_string(\"A -> B; 'k'\", substance_factory=lambda k: Substance(k))\n", "rsys" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "odesys, extra = get_odesys(rsys, include_params=False)\n", "odesys.names, odesys.param_names" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can change the expressions of the ODE system manually to account for source and sink terms from the flux:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "t, c1, c2, IA, IB, f, k, fc_A, fc_B = map(odesys.be.Symbol, 't c1 c2 I_A I_B f k phi_A phi_B'.split())\n", "newp = f, fc_A, fc_B\n", "c_feed = {'A': fc_A, 'B': fc_B}\n", "cstr = SymbolicSys(\n", " [\n", " (dep, expr - f*dep + f*c_feed[name]) for name, dep, expr\n", " in zip(odesys.names, odesys.dep, odesys.exprs)\n", " ],\n", " params=list(odesys.params) + list(newp),\n", " names=odesys.names,\n", " param_names=list(odesys.param_names) + [p.name for p in newp],\n", " dep_by_name=True,\n", " par_by_name=True,\n", ")\n", "Latex('$' + r'\\\\ '.join(map(lambda x: ':'.join(x), zip(map(lambda x: r'\\frac{d%s}{dt}' % x, cstr.names),\n", " map(cstr.be.latex, cstr.exprs)))) + '$')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "cstr.param_names" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "init_c, pars = {'A': .15, 'B': .1}, {'k': 0.8, 'f': 0.3, 'phi_A': .7, 'phi_B': .1}\n", "res = cstr.integrate(10, init_c, pars, integrator='cvode')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "res.plot()" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "We can derive an analytic solution to the ODE system:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "k = cstr.params[0]\n", "e = cstr.be.exp\n", "exprs = [\n", " fc_A*f/(f + k) + c1 * e(-t*(f + k)),\n", " (fc_A*k + fc_B*(f + k))/(f + k) - c1*e(-f*t)*(e(-t*k) - 1) + c2*e(-f*t)\n", "]\n", "cstr.be.init_printing()\n", "exprs" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "exprs0 = [expr.subs(t, 0) for expr in exprs]\n", "exprs0" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sol = cstr.be.solve([expr - c0 for expr, c0 in zip(exprs0, (IA, IB))], (c1, c2))\n", "sol" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "exprs2 = [expr.subs(sol) for expr in exprs]\n", "exprs2" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "IA" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import sympy as sp\n", "cses, expr_cse = sp.cse([expr.subs({fc_A: sp.Symbol('fr'), fc_B: sp.Symbol('fp'), f: sp.Symbol('fv'),\n", " IA: sp.Symbol('r'), IB: sp.Symbol('p')}) for expr in exprs2])\n", "s = '\\n'.join(['%s = %s' % (lhs, rhs) for lhs, rhs in cses] + [str(tuple(expr_cse))])\n", "print(s.replace('exp', 'be.exp').replace('\\n(', '\\nreturn ('))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "exprs2_0 = [expr.subs(t, 0).simplify() for expr in exprs2]\n", "exprs2_0" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "_cb = cstr.be.lambdify([t, IA, IB, k, f, fc_A, fc_B], exprs2)\n", "def analytic(x, c0, params):\n", " return _cb(x, c0['A'], c0['B'], params['k'], params['f'], params['phi_A'], params['phi_B'])" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def get_ref(result, parameters=None):\n", " drctn = -1 if result.odesys.names[0] == 'B' else 1\n", " return np.array(analytic(\n", " result.xout,\n", " {k: result.named_dep(k)[0] for k in result.odesys.names},\n", " parameters or {k: result.named_param(k) for k in result.odesys.param_names})).T[:, ::drctn]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "yref = get_ref(res)\n", "yref.shape" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Plotting the error (by comparison to the analytic solution):" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fig, axes = plt.subplots(1, 2, figsize=(14, 4))\n", "res.plot(ax=axes[0])\n", "res.plot(ax=axes[0], y=yref)\n", "res.plot(ax=axes[1], y=res.yout - yref)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Automatically generating CSTR expressions using chempy\n", "ChemPy has support for generating the CSTR expressions directly in ``ReactionSystem.rates`` & ``get_odesys``. This simplifies the code the user needs to write considerably:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "cstr2, extra2 = get_odesys(rsys, include_params=False, cstr=True)\n", "cstr2.exprs" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note how we only needed to pass ``cstr=True`` to ``get_odesys`` to get the right expressions." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "cstr2.param_names" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "renamed_pars = {'k': pars['k'], 'fc_A': pars['phi_A'], 'fc_B': pars['phi_B'], 'feedratio': pars['f']}\n", "res2 = cstr2.integrate(10, init_c, renamed_pars, integrator='cvode')\n", "ref2 = get_ref(res2, pars)\n", "res2.plot(y=res2.yout - ref2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The analytic solution of a unary reaction in a CSTR is also available in ChemPy:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from chempy.kinetics.integrated import unary_irrev_cstr\n", "help(unary_irrev_cstr)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The symbols of the feedratio and feed concentrations are available in the second output of ``get_odesys`` (the extra dictionary):" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fr, fc = extra2['cstr_fr_fc']" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def get_ref2(result):\n", " drctn = -1 if result.odesys.names[0] == 'B' else 1\n", " return np.array(unary_irrev_cstr(\n", " result.xout,\n", " result.named_param('k'),\n", " result.named_dep('A')[0],\n", " result.named_dep('B')[0], \n", " result.named_param(fc['A']),\n", " result.named_param(fc['B']),\n", " result.named_param(fr))).T[:, ::drctn]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "res2.plot(y=res2.yout - get_ref2(res2))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "assert np.allclose(res2.yout, get_ref2(res2))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.0+" } }, "nbformat": 4, "nbformat_minor": 2 }