diff --git a/docs/source/tutorials/10_Process.ipynb b/docs/source/tutorials/10_Process.ipynb index 73b8659b..2a4817a6 100644 --- a/docs/source/tutorials/10_Process.ipynb +++ b/docs/source/tutorials/10_Process.ipynb @@ -20,7 +20,7 @@ " \n", "- **Video demo:**\n", "\n", - " - [Joy Zhang](https://qsdsan.readthedocs.io/en/latest/authors/Joy_Zhang.html)\n", + " - [Joy Zhang](https://qsdsan.readthedocs.io/en/latest/CONTRIBUTING.html)\n", " \n", "To run tutorials in your browser, go to this [Binder page](https://mybinder.org/v2/gh/QSD-Group/QSDsan/main?filepath=%2Fdocs%2Fsource%2Ftutorials).\n", " \n", diff --git a/docs/source/tutorials/11_Dynamic_Simulation.ipynb b/docs/source/tutorials/11_Dynamic_Simulation.ipynb index 5c224833..4f990179 100644 --- a/docs/source/tutorials/11_Dynamic_Simulation.ipynb +++ b/docs/source/tutorials/11_Dynamic_Simulation.ipynb @@ -23,11 +23,11 @@ " \n", "- **Video demo:**\n", "\n", - " - To be posted\n", + " - [Yalin Li](https://qsdsan.readthedocs.io/en/latest/CONTRIBUTING.html)\n", " \n", "To run tutorials in your browser, go to this [Binder page](https://mybinder.org/v2/gh/QSD-Group/QSDsan/main?filepath=%2Fdocs%2Fsource%2Ftutorials).\n", " \n", - "You can also watch a video demo on YouTube (link to be posted) (subscriptions & likes appreciated!)." + "You can also watch a video demo on [YouTube](https://youtu.be/1Rr1QxUiE5k) (subscriptions & likes appreciated!)." ] }, { @@ -35,7 +35,7 @@ "id": "2bc790e7", "metadata": { "slideshow": { - "slide_type": "slide" + "slide_type": "subslide" } }, "source": [ @@ -103,7 +103,7 @@ "id": "a1c82016", "metadata": { "slideshow": { - "slide_type": "slide" + "slide_type": "fragment" } }, "outputs": [ @@ -255,10 +255,10 @@ "\n", "\n", "\n", + "Flat bottom circular clarifier->77295603618 -->\n", "\n", "C1\n", - "Flat bottom circular clarifier:c->174448760881:w\n", + "Flat bottom circular clarifier:c->77295603618:w\n", "\n", "\n", " effluent\n", @@ -266,20 +266,20 @@ "\n", "\n", "\n", + "Flat bottom circular clarifier->77295603938 -->\n", "\n", "C1\n", - "Flat bottom circular clarifier:c->174448759921:w\n", + "Flat bottom circular clarifier:c->77295603938:w\n", "\n", "\n", " WAS\n", "\n", "\n", "\n", - "\n", "\n", - "174448655746:e->A1\n", + "<title>77280229462:e->A1\n", "CSTR:c\n", "\n", "\n", "\n", "\n", - "\n", + "\n", "\n", - "174448655746\n", + "77280229462\n", "\n", "\n", - "\n", + "\n", "\n", - "174448760881\n", + "77295603618\n", "\n", "\n", - "\n", + "\n", "\n", - "174448759921\n", + "77295603938\n", "\n", "\n", "\n", @@ -390,8 +390,8 @@ "source": [ "# The BSM1 system is composed of 5 CSTRs in series, \n", "# followed by a flat-bottom circular clarifier.\n", - "sys.diagram()\n", - "# sys.units" + "# sys.units\n", + "sys.diagram()" ] }, { @@ -456,30 +456,14 @@ "slide_type": "slide" } }, - "outputs": [ - { - "data": { - "text/plain": [ - "{: True,\n", - " : True,\n", - " : True,\n", - " : True,\n", - " : True,\n", - " : True}" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# This is because the system contains at least one dynamic SanUnit\n", - "{u: u.isdynamic for u in sys.units}\n", + "# {u: u.isdynamic for u in sys.units}\n", "\n", "# If we disable dynamic simulation, then `simulate` would work as usual\n", - "# sys.isdynamic = False\n", - "# sys.simulate()" + "sys.isdynamic = False\n", + "sys.simulate()" ] }, { @@ -493,16 +477,36 @@ "source": [ "To perform a dynamic simulation of the system, we need to provide at least one additional keyword argument, i.e., `t_span`, as suggested in the error message. `t_span` is a 2-tuple indicating the simulation period.\n", "\n", - ">**Note**: Whether `t_span = (0,10)` means 0-10 days or 0-10 hours/minutes/months depends entirely on units of the parameters in the system's ODEs. For BSM1, it'd mean 0-10 days because all parameters in the ODEs express time in the unit of \"day\".\n", - "\n", + ">**Note**: Whether `t_span = (0,10)` means 0-10 days or 0-10 hours/minutes/months depends entirely on units of the parameters in the system's ODEs. For BSM1, it'd mean 0-10 days because all parameters in the ODEs express time in the unit of \"day\"." + ] + }, + { + "cell_type": "markdown", + "id": "0c111c81", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ "Other often-used keyword arguments include:\n", "\n", "- `t_eval`: a 1d array to specify the output time points\n", "- `method`: a string specifying the ODE solver\n", "- `state_reset_hook`: specifies how to reset the simulation\n", "\n", - "`t_span`, `t_eval`, and `method` are essentially passed to [scipy.integrate.solve_ivp](https://docs.scipy.org/doc/scipy/reference/generated/scipy.integrate.solve_ivp.html) function as keyword arguments. See [documentation](https://biosteam.readthedocs.io/en/latest/API/System.html#biosteam.System.dynamic_run) for a complete list of keyword arguments. You may notice that `scipy.integrate.solve_ivp` also requires input of `fun` (i.e., the ODEs) and `y0` (i.e., the initial condition). We'll learn later how `System.simulate` automates the compilation of these inputs.\n", - "\n", + "`t_span`, `t_eval`, and `method` are essentially passed to [scipy.integrate.solve_ivp](https://docs.scipy.org/doc/scipy/reference/generated/scipy.integrate.solve_ivp.html) function as keyword arguments. See [documentation](https://biosteam.readthedocs.io/en/latest/API/System.html#biosteam.System.dynamic_run) for a complete list of keyword arguments. You may notice that `scipy.integrate.solve_ivp` also requires input of `fun` (i.e., the ODEs) and `y0` (i.e., the initial condition). We'll learn later how `System.simulate` automates the compilation of these inputs." + ] + }, + { + "cell_type": "markdown", + "id": "9c8b4556", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ "---\n", "### Tip\n", "For systems that are expected to converge to some sort of \"steady state\", it is usually faster to simulate with implicit ODE solvers (e.g., `method = BDF` or `method = LSODA`) than with explicit ones. In case of one solver fails to complete integration through the entire specified simulation period, always try with alternative ones.\n", @@ -515,6 +519,7 @@ "execution_count": 7, "id": "45ef4032", "metadata": { + "scrolled": true, "slideshow": { "slide_type": "slide" } @@ -566,6 +571,7 @@ ], "source": [ "# Let's try simulating the BSM1 system from day 0 to day 50\n", + "sys.isdynamic = True\n", "sys.simulate(t_span=(0, 50), method='BDF', state_reset_hook='reset_cache')\n", "sys.show()" ] @@ -680,19 +686,7 @@ { "data": { "text/plain": [ - "array([[3.000e+01, 5.000e+00, 1.000e+03, ..., 4.155e-11, 9.379e-05,\n", - " 9.223e+04],\n", - " [3.000e+01, 5.000e+00, 1.000e+03, ..., 4.197e-09, 9.473e-03,\n", - " 9.223e+04],\n", - " [3.000e+01, 5.000e+00, 1.000e+03, ..., 8.352e-09, 1.885e-02,\n", - " 9.223e+04],\n", - " ...,\n", - " [3.000e+01, 2.811e+00, 1.147e+03, ..., 2.500e+01, 9.978e+05,\n", - " 9.223e+04],\n", - " [3.000e+01, 2.810e+00, 1.148e+03, ..., 2.501e+01, 9.978e+05,\n", - " 9.223e+04],\n", - " [3.000e+01, 2.810e+00, 1.148e+03, ..., 2.501e+01, 9.978e+05,\n", - " 9.223e+04]])" + "array([], shape=(0, 1), dtype=float64)" ] }, "execution_count": 11, @@ -702,7 +696,10 @@ ], "source": [ "# Raw time-series data are stored in\n", - "A1.scope.record" + "# A1.scope.record\n", + "A2 = sys.flowsheet.unit.A2\n", + "A2.scope\n", + "A2.scope.record" ] }, { @@ -915,402 +912,61 @@ "slideshow": { "slide_type": "slide" } - }, - "source": [ - "So far we've learned how to simulate any dynamic system developed with QSDsan. \n", - "A complete list of existing unit operations within QSDsan is available [here](https://qsdsan.readthedocs.io/en/latest/api/sanunits/_index.html). The column \"Dynamic\" indicates whether the unit is enabled for dynamic simulations. Any system composed of the enabled units can be simulated dynamically as we learned above.\n", - "\n", - "[Back to top](#top)" - ] - }, - { - "cell_type": "markdown", - "id": "3d13e036", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, - "source": [ - "### 1.2. What makes a system \"dynamic\"?\n", - "It's ultimately the user's decision whether a system should be run dynamically. This section will cover the essentials to switch to the dynamic mode for system simulation.\n", - "\n", - "#### `System.isdynamic` vs. `SanUnit.isdynamic` vs. `SanUnit.hasode` \n", - "\n", - "- Simply speaking, when the `.isdynamic == True`, the program will attempt dynamic simulation. Users can directly enable/disable the dynamic mode by setting the `isdynamic` property of a `System` object.\n", - "\n", - "- The program will deduct the value of `.isdynamic` when it's not specified by users. `.isdynamic` is considered `True` in all cases except when `.isdynamic == False` for all units.\n", - "\n", - "- Setting `.isdynamic = True` does not gaurantee the unit can be simulated dynamically. Just like how the `_run` method must be defined for static simulation, a series of additional methods must be defined to enable dynamic simulation.\n", - "\n", - "- `.hasode == True` means a unit has the fundamental methods to compile ODEs. It is a **sufficient but not necessary** condition for dynamic simulation, because a unit doesn't have to be described with ODEs to be capable of dynamic simulations." - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "id": "c130f36f", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "{: True,\n", - " : True,\n", - " : True,\n", - " : True,\n", - " : True,\n", - " : True}" - ] - }, - "execution_count": 17, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# All units in the BSM1 system above have ODEs\n", - "{u: u.hasode for u in sys.units}" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "id": "b6a0612a", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, - "outputs": [ - { - "data": { - "image/svg+xml": [ - "\n", - "\n", - "\n", - "\n", - "A1\n", - "CSTR:c->A2\n", - "CSTR:c\n", - "\n", - "\n", - "\n", - " ws1\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "A2\n", - "CSTR:c->O1\n", - "CSTR:c\n", - "\n", - "\n", - "\n", - " ws3\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "O1\n", - "CSTR:c->O2\n", - "CSTR:c\n", - "\n", - "\n", - "\n", - " ws5\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "O2\n", - "CSTR:c->O3\n", - "CSTR:c\n", - "\n", - "\n", - "\n", - " ws7\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "O3\n", - "CSTR:c->A1\n", - "CSTR:c\n", - "\n", - "\n", - "\n", - " RWW\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "O3\n", - "CSTR:c->C1\n", - "Flat bottom circular clarifier:c\n", - "\n", - "\n", - "\n", - " treated\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "C1\n", - "Flat bottom circular clarifier:c->A1\n", - "CSTR:c\n", - "\n", - "\n", - "\n", - " RAS\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "C1\n", - "Flat bottom circular clarifier:c->174448760881:w\n", - "\n", - "\n", - " effluent\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "C1\n", - "Flat bottom circular clarifier:c->174448759921:w\n", - "\n", - "\n", - " WAS\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "174448655746:e->A1\n", - "CSTR:c\n", - "\n", - "\n", - " wastewater\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "A1\n", - "CSTR\n", - "\n", - "\n", - "A1\n", - "CSTR\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "A2\n", - "CSTR\n", - "\n", - "\n", - "A2\n", - "CSTR\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "O1\n", - "CSTR\n", - "\n", - "\n", - "O1\n", - "CSTR\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "O2\n", - "CSTR\n", - "\n", - "\n", - "O2\n", - "CSTR\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "O3\n", - "CSTR\n", - "\n", - "\n", - "O3\n", - "CSTR\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "C1\n", - "Flat bottom circular clarifier\n", - "\n", - "\n", - "C1\n", - "Flat bottom circular clarifier\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "174448655746\n", - "\n", - "\n", - "\n", - "\n", - "174448760881\n", - "\n", - "\n", - "\n", - "\n", - "174448759921\n", - "\n", - "\n", - "\n", - "" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + }, "source": [ - "# Units without ODEs can also be simulated dynamically as long as \n", - "# the fundamental methods are defined. Here is an example.\n", - "from exposan import bsm1\n", - "bsm1.load()\n", - "bsm1.sys.diagram()" + "So far we've learned how to simulate any dynamic system developed with QSDsan. \n", + "A complete list of existing unit operations within QSDsan is available [here](https://qsdsan.readthedocs.io/en/latest/api/sanunits/_index.html). The column \"Dynamic\" indicates whether the unit is enabled for dynamic simulations. Any system composed of the enabled units can be simulated dynamically as we learned above." ] }, { - "cell_type": "code", - "execution_count": 19, - "id": "f2a81479", + "cell_type": "markdown", + "id": "497d72b8", "metadata": { "slideshow": { - "slide_type": "slide" + "slide_type": "skip" } }, - "outputs": [ - { - "data": { - "text/plain": [ - "{: True,\n", - " : True,\n", - " : True,\n", - " : True,\n", - " : True,\n", - " : True}" - ] - }, - "execution_count": 19, - "metadata": {}, - "output_type": "execute_result" - } - ], "source": [ - "{u: u.hasode for u in bsm1.sys.units}\n", - "# bsm1.sys.isdynamic" + "[Back to top](#top)" ] }, { - "cell_type": "code", - "execution_count": 20, - "id": "be199e8d", + "cell_type": "markdown", + "id": "3d13e036", "metadata": { "slideshow": { "slide_type": "slide" } }, - "outputs": [ - { - "data": { - "text/plain": [ - "(, )" - ] - }, - "execution_count": 20, - "metadata": {}, - "output_type": "execute_result" + "source": [ + "### 1.2. When is a system \"dynamic\"?\n", + "It's ultimately the user's decision whether a system should be run dynamically. This section will cover the essentials to switch to the dynamic mode for system simulation." + ] + }, + { + "cell_type": "markdown", + "id": "94eab6a5", + "metadata": { + "slideshow": { + "slide_type": "slide" } - ], + }, "source": [ - "uf = bsm1.sys.flowsheet.unit\n", - "bsm1.sys.simulate(t_span=(0,3), method='BDF', state_reset_hook='reset_cache')\n", - "bsm1.sys.scope.subjects" + "#### `System.isdynamic` vs. `SanUnit.isdynamic` vs. `SanUnit.hasode` \n", + "\n", + "- Simply speaking, when the `.isdynamic == True`, the program will attempt dynamic simulation. Users can directly enable/disable the dynamic mode by setting the `isdynamic` property of a `System` object.\n", + "\n", + "- The program will set the value of `.isdynamic` when it's not specified by users. `.isdynamic` is considered `True` in all cases except when `.isdynamic == False` for all units.\n", + "\n", + "- Setting `.isdynamic = True` does not gaurantee the unit can be simulated dynamically. Just like how the `_run` method must be defined for static simulation, a series of additional methods must be defined to enable dynamic simulation.\n", + "\n", + "- `.hasode == True` means a unit has the fundamental methods to compile ODEs. It is a **sufficient but not necessary** condition for dynamic simulation, because a unit doesn't have to be described with ODEs to be capable of dynamic simulations." ] }, { "cell_type": "code", - "execution_count": 21, - "id": "ff2ea29e", + "execution_count": 17, + "id": "c130f36f", "metadata": { "slideshow": { "slide_type": "slide" @@ -1319,17 +975,23 @@ "outputs": [ { "data": { - "image/png": "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Njz00EPmWbNmVXz33Xclffv2VceOHRuVm5vLX/YgVFJSIn379lVffPHF8toef+qpp0wLFiwwLViwoHzDhg0lUVFRWmpqqqWsrKylp4parF692pCWlla5fv36ki+++KLU5XLJmDFjLMXFxdVjpk+fbv7ss88M7733XtmqVatKcnJylAkTJkQGcNo4RYcOHbSnnnqqYtOmTSWZmZkll1xyiWfChAmWHTt26ES4fqFk48aNuldeecXUu3dv9dTjXMPQ0KNHD/Xw4cPF3o9169aVeh8LxmtIGbQgM3DgQMsFF1ygLly4sFxExOPxSIcOHaxTp06tnDVrVmWg54e6KYpi++CDD8quueYat0jV6m/btm2t9957b+UjjzxSKSJSUFAgdrvd9uqrr5bddNNN7sDOGKc7evSoYrfbratWrSodMWKEp6CgQBITE21vvvlm2fXXX+8WEcnKytL16tUrau3ataXDhw/3BHrOOFN8fLztqaeeKr/++utdXL/QUFRUJAMGDIj6xz/+Uf7EE09E9OvXz/Piiy9W8HcwNMyaNStiyZIlhu3bt5ec/liwXkNWgINIRUWFfP/99/qRI0dWByO9Xi+XXnqpe+PGjfr6novgs3fvXuXo0aPKqFGjqq9nbGysDBw40LNhwwauZxByOp0iIpKQkKCJiGzatEnvcrlk9OjR1dewZ8+eaocOHbT169dzDYOM2+2Wt956y1BSUiLDhw/3cP1CR1pamvmyyy5zjxkzpkYY4hqGjl9++UXncDisnTp1st5www2R+/btU0SC9xoaGh6ClnLs2DHF4/FIUlKSdurxxMREjX2joScnJ0cnImK328+4nrm5uVzPIOPxeGT69OnmoUOHevr27auKiOTm5iomk0ni4uJqjD15DdmWFCS2bdumGz58eFR5eblYrVb54IMPynr37q1+//33Bq5f8HvrrbcM33//vf677747Y/WQv4OhYciQIe5XX33V0717d/XIkSPK448/HnHRRRdF7dy5szhYryEBGACkagUqKytLv2bNmjO+CSO4de/eXd2yZUtxQUGB8v777xtvu+0289dff13a8DMRaPv371dmzJhh/vLLL0sjI9nWG6quuOKK6pX7/v37y9ChQ0tTUlJs77zzjtFisWj1PTdQWIUKIm3atNH0er0cPXq0xv+I8vLylNNXhRH8HA5H9Sriqcfz8vIU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\n", "text/plain": [ - "
" + "{: True,\n", + " : True,\n", + " : True,\n", + " : True,\n", + " : True,\n", + " : True}" ] }, + "execution_count": 17, "metadata": {}, - "output_type": "display_data" + "output_type": "execute_result" } ], "source": [ - "fig, ax = uf.A1.scope.plot_time_series(('X_BH', ))" + "# All units in the BSM1 system above have ODEs\n", + "{u: u.hasode for u in sys.units}" ] }, { @@ -1337,7 +999,7 @@ "id": "7839f0e2", "metadata": { "slideshow": { - "slide_type": "slide" + "slide_type": "skip" } }, "source": [ @@ -1373,14 +1035,24 @@ "\n", "In comparison, during dynamic simulations, all information are stored as `_state` and `_dstate` attributes of the relevant `SanUnit` obejcts as well as `state` and `dstate` properties of `WasteStream` objects. These information won't be translated to mass or energy flows until dynamic simulation is completed.\n", "\n", - "- `WasteStream.state` is a 1d `numpy.array` of length $n+1$, $n$ is the length of the components associated with the `thermo`. Each element of the array represents value of one state variable.\n", + "- `WasteStream.state` is a 1d `numpy.array` of length $n+1$, $n$ is the length of the components associated with the `thermo`. Each element of the array represents value of one state variable." + ] + }, + { + "cell_type": "markdown", + "id": "b1529db3", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "---\n", + "#### Tip\n", "\n", - " ---\n", - " #### Tip\n", - " \n", - " Typically for a liquid `WasteStream`, the first $n$ element represents the component concentrations \\[mg/L\\], while the last element represents the total volumetric flow \\[m3/d\\]. For a gaseous `WasteStream`, the first $n$ state variables can simply be the mass flows \\[g/d\\] of the components if the last element is fixed at 1. This is because after completing dynamic simulations, the `WasteStream`'s mass flow is defined as the first $n$ element of this array multiplied by the last element.\n", + "Typically for a liquid `WasteStream`, the first $n$ element represents the component concentrations \\[mg/L\\], while the last element represents the total volumetric flow \\[m3/d\\]. For a gaseous `WasteStream`, the first $n$ state variables can simply be the mass flows \\[g/d\\] of the components if the last element is fixed at 1. This is because after completing dynamic simulations, the `WasteStream`'s mass flow is defined as the first $n$ element of this array multiplied by the last element.\n", "\n", - " ---" + "---" ] }, { @@ -1388,7 +1060,7 @@ "id": "d997b05d", "metadata": { "slideshow": { - "slide_type": "slide" + "slide_type": "subslide" } }, "source": [ @@ -1397,7 +1069,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 18, "id": "a8ae235a", "metadata": { "slideshow": { @@ -1408,12 +1080,12 @@ { "data": { "text/plain": [ - "array([3.000e+01, 1.131e+00, 5.300e+00, 1.865e-01, 7.595e+00, 5.658e-01,\n", - " 6.902e-01, 8.559e-01, 1.289e+01, 2.380e+00, 8.718e-01, 1.268e-02,\n", - " 4.815e+01, 2.134e+01, 9.925e+05, 1.806e+04])" + "array([3.000e+01, 8.899e-01, 4.389e+00, 1.886e-01, 9.784e+00, 5.720e-01,\n", + " 1.722e+00, 4.897e-01, 1.038e+01, 1.747e+00, 6.884e-01, 1.349e-02,\n", + " 4.954e+01, 2.751e+01, 9.978e+05, 1.806e+04])" ] }, - "execution_count": 22, + "execution_count": 18, "metadata": {}, "output_type": "execute_result" } @@ -1425,7 +1097,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 19, "id": "ab1496fd", "metadata": { "slideshow": { @@ -1436,12 +1108,12 @@ { "data": { "text/plain": [ - "sparse([3.000e+01, 1.131e+00, 5.300e+00, 1.865e-01, 7.595e+00, 5.658e-01,\n", - " 6.902e-01, 8.559e-01, 1.289e+01, 2.380e+00, 8.718e-01, 1.268e-02,\n", - " 4.815e+01, 2.134e+01, 9.981e+05])" + "sparse([3.000e+01, 8.899e-01, 4.389e+00, 1.886e-01, 9.784e+00, 5.720e-01,\n", + " 1.722e+00, 4.897e-01, 1.038e+01, 1.747e+00, 6.884e-01, 1.349e-02,\n", + " 4.954e+01, 2.751e+01, 9.981e+05])" ] }, - "execution_count": 23, + "execution_count": 19, "metadata": {}, "output_type": "execute_result" } @@ -1453,7 +1125,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 20, "id": "825050c1", "metadata": { "slideshow": { @@ -1467,7 +1139,7 @@ "True" ] }, - "execution_count": 24, + "execution_count": 20, "metadata": {}, "output_type": "execute_result" } @@ -1485,6 +1157,7 @@ } }, "source": [ + "\n", "- `SanUnit._state` is also a 1d `numpy.array`, but the length of the array is not assumed, because the state variables relevant for a `SanUnit` is entirely dependent on the unit operation itself. Therefore, there is no predefined units of measure or order for state variables of a unit operation.\n", "\n", "- `SanUnit._dstate`, similarly, must have the exact same shape as the `_state` array, as each element corresponds to the time derivative of a state variable." @@ -1492,7 +1165,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 21, "id": "956dbc0f", "metadata": { "slideshow": { @@ -1506,18 +1179,19 @@ "False" ] }, - "execution_count": 25, + "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "C1._state.shape == A1._state.shape" + "C1._state.shape == A1._state.shape\n", + "# C1._state.shape == C1._dstate.shape" ] }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 22, "id": "561a5589", "metadata": { "slideshow": { @@ -1529,24 +1203,24 @@ "data": { "text/plain": [ "{'S_I': 30.0,\n", - " 'S_S': 4.129030921988002,\n", - " 'X_I': 1110.2944324666043,\n", - " 'X_S': 71.69610729246752,\n", - " 'X_BH': 1589.6911658043132,\n", - " 'X_BA': 117.35150600812317,\n", - " 'X_P': 143.47641700988382,\n", - " 'S_O': 0.00915708699329117,\n", - " 'S_NO': 7.781709512753603,\n", - " 'S_NH': 8.271234391268205,\n", - " 'S_ND': 1.5210842727765221,\n", - " 'X_ND': 4.390004643234116,\n", - " 'S_ALK': 57.499885318050694,\n", - " 'S_N2': 19.70535663905416,\n", - " 'H2O': 994443.4883529523,\n", + " 'S_S': 2.8098296544615704,\n", + " 'X_I': 1147.8970757884535,\n", + " 'X_S': 82.14996504835973,\n", + " 'X_BH': 2551.1712941951987,\n", + " 'X_BA': 148.18576250649838,\n", + " 'X_P': 447.1086242830684,\n", + " 'S_O': 0.004288622012845044,\n", + " 'S_NO': 5.33892893863284,\n", + " 'S_NH': 7.928812844268634,\n", + " 'S_ND': 1.216680910568711,\n", + " 'X_ND': 5.285760801254182,\n", + " 'S_ALK': 59.158219028756534,\n", + " 'S_N2': 25.008073542375985,\n", + " 'H2O': 997794.331078558,\n", " 'Q': 92229.99999999996}" ] }, - "execution_count": 26, + "execution_count": 22, "metadata": {}, "output_type": "execute_result" } @@ -1562,7 +1236,7 @@ "id": "68f067f1", "metadata": { "slideshow": { - "slide_type": "slide" + "slide_type": "skip" } }, "source": [ @@ -1579,8 +1253,18 @@ }, "source": [ "### 2.2. Fundamental methods\n", - "In addition to proper `__init__` and `_run` methods ([recap](https://qsdsan.readthedocs.io/en/latest/tutorials/5_SanUnit_advanced.html#2.1.-Fundamental-methods)), a few more methods are required in a `SanUnit` subclass for dynamic simulation. Users typically won't interact with these methods but they will be called by `System.simulate` to manipulate the values of the arrays mentioned [above](#s2.1) (i.e., `._state`, `._dstate`, `.state`, and `.dstate`).\n", - "\n", + "In addition to proper `__init__` and `_run` methods ([recap](https://qsdsan.readthedocs.io/en/latest/tutorials/5_SanUnit_advanced.html#2.1.-Fundamental-methods)), a few more methods are required in a `SanUnit` subclass for dynamic simulation. Users typically won't interact with these methods but they will be called by `System.simulate` to manipulate the values of the arrays mentioned [above](#s2.1) (i.e., `._state`, `._dstate`, `.state`, and `.dstate`)." + ] + }, + { + "cell_type": "markdown", + "id": "976dabeb", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ "- `_init_state`, called after `_run` to generate an initial condition for the unit, i.e., defining shape and values of the `_state` and `_dstate` arrays. For example:\n", "```python\n", "import numpy as np\n", @@ -1589,9 +1273,18 @@ " self._state = np.ones(len(inf.components)+1)\n", " self._dstate = self._state * 0.\n", "```\n", - "This method (not saying it makes sense) assumes $n+1$ state variables and gives an initial value of 1 to all of them. Then it also sets the initial time derivatives to be 0. \n", - "\n", - "\n", + "This method (not saying it makes sense) assumes $n+1$ state variables and gives an initial value of 1 to all of them. Then it also sets the initial time derivatives to be 0. " + ] + }, + { + "cell_type": "markdown", + "id": "3a3de71d", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ "- `_update_state`, to update effluent streams' state arrays based on current state (and maybe dstate) of the SanUnit. For example:\n", "```python\n", "def _update_state(self):\n", @@ -1599,8 +1292,18 @@ " eff, = self.outs # assuming this SanUnit has one outlet only\n", " eff.state[:] = arr # assume arr has the same shape as WasteStream.state\n", "```\n", - "The goal of this method is to update the values in `.state` for each `WasteStream` in `.outs`.\n", - "\n", + "The goal of this method is to update the values in `.state` for each `WasteStream` in `.outs`." + ] + }, + { + "cell_type": "markdown", + "id": "8deec2a2", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ "- `_update_dstate`, to update effluent streams' `dstate` arrays based on current `_state` and `_dstate` of the SanUnit. The signiture and often the algorithm are similar to `_update_state`.\n", "\n", "\n", @@ -1611,7 +1314,18 @@ " if self._ODE is None:\n", " self._compile_ODE()\n", " return self._ODE \n", - "```\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "a431142f", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ "```python\n", "def _compile_ODE(self):\n", " _dstate = self._dstate\n", @@ -1620,14 +1334,36 @@ " _dstate[:] = some_algorithm(t, y_ins, y, dy_ins)\n", " _update_dstate()\n", " self._ODE = dy_dt\n", - "```\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "83c50a89", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ "```python\n", "@property\n", "def AE(self):\n", " if self._AE is None:\n", " self._compile_AE()\n", " return self._AE\n", - "```\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "dd66c263", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ "```python\n", "def _compile_AE(self):\n", " _state = self._state\n", @@ -1640,8 +1376,18 @@ " _update_state()\n", " _update_dstate()\n", " self._AE = y_t\n", - "```\n", - "\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "a144502d", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ "> **Note**: Within the `dy_dt` or `y_t` functions, `._state[:] = ` rather than `._state = ` because it's generally faster to update values in an existing array than overwriting this array with a newly created array.\n", "\n", "We'll learn more about these two methods in the next subsections." @@ -1675,7 +1421,7 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 23, "id": "c38b235a", "metadata": { "slideshow": { @@ -1713,7 +1459,7 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 24, "id": "9b5ce52d", "metadata": { "slideshow": { @@ -1737,7 +1483,7 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 25, "id": "12aa03d9", "metadata": { "scrolled": false, @@ -1780,7 +1526,7 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 26, "id": "4ff4c667", "metadata": { "scrolled": true, @@ -1868,9 +1614,10 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": 27, "id": "72151a1e", "metadata": { + "scrolled": true, "slideshow": { "slide_type": "slide" } @@ -1895,23 +1642,44 @@ "Since the mixer-splitter mixes and splits instantly, we can express this process with a set of algebraic equations (AEs). Assume its array of state variables follow the \"concentration-volumetric flow\" convention. In mathematical forms, state variables of the mixer-splitter ($C_m$, component concentrations; $Q_m$, total volumetric flow) follow:\n", "$$Q_m = \\sum_{i \\in ins} Q_i \\tag{1}$$\n", "$$Q_mC_m = \\sum_{i \\in ins} Q_iC_i$$\n", - "$$\\therefore C_m = \\frac{\\sum_{i \\in ins} Q_iC_i}{Q_m} \\tag{2}$$\n", + "$$\\therefore C_m = \\frac{\\sum_{i \\in ins} Q_iC_i}{Q_m} \\tag{2}$$" + ] + }, + { + "cell_type": "markdown", + "id": "a37f98d9", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ "Therefore, the time derivatives $\\dot{Q_m}$ follow:\n", "$$\\dot{Q_m} = \\sum_{i \\in ins} \\dot{Q_i} \\tag{3}$$\n", "$$Q_m\\dot{C_m} + C_m\\dot{Q_m} = \\sum_{i \\in ins} (Q_i\\dot{C_i} + C_i\\dot{Q_i})$$\n", - "$$\\therefore \\dot{C_m} = \\frac{1}{Q_m}\\cdot(\\sum_{i \\in ins}Q_i\\dot{C_i} + \\sum_{i \\in ins}C_i\\dot{Q_i} - C_m\\dot{Q_m}) \\tag{4}$$\n", + "$$\\therefore \\dot{C_m} = \\frac{1}{Q_m}\\cdot(\\sum_{i \\in ins}Q_i\\dot{C_i} + \\sum_{i \\in ins}C_i\\dot{Q_i} - C_m\\dot{Q_m}) \\tag{4}$$" + ] + }, + { + "cell_type": "markdown", + "id": "7578a12e", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ "For any effluent `WasteStream` $j$:\n", "$$Q_j = \\frac{Q_m}{n_{outs}} \\tag{5}$$\n", "$$C_j = C_m \\tag{6}$$\n", "$$\\therefore \\dot{Q_j} = \\frac{\\dot{Q_m}}{n_{outs}} \\tag{7}$$\n", "$$\\dot{C_j} = \\dot{C_m} \\tag{8}$$\n", - "Now, let's try to implement this algorithm in methods for dynamic simulation.\n", - "\n" + "Now, let's try to implement this algorithm in methods for dynamic simulation." ] }, { "cell_type": "code", - "execution_count": 32, + "execution_count": 28, "id": "38abf7cb", "metadata": { "slideshow": { @@ -1987,7 +1755,7 @@ }, { "cell_type": "code", - "execution_count": 33, + "execution_count": 29, "id": "ba8c9001", "metadata": { "slideshow": { @@ -2005,7 +1773,7 @@ }, { "cell_type": "code", - "execution_count": 34, + "execution_count": 30, "id": "a4f65bf6", "metadata": { "slideshow": { @@ -2085,7 +1853,7 @@ }, { "cell_type": "code", - "execution_count": 35, + "execution_count": 31, "id": "c4706ed2", "metadata": { "slideshow": { @@ -2169,7 +1937,7 @@ }, { "cell_type": "code", - "execution_count": 36, + "execution_count": 32, "id": "493239c1", "metadata": { "slideshow": { @@ -2192,7 +1960,7 @@ }, { "cell_type": "code", - "execution_count": 37, + "execution_count": 33, "id": "c3df8f02", "metadata": { "slideshow": { @@ -2207,7 +1975,7 @@ " )" ] }, - "execution_count": 37, + "execution_count": 33, "metadata": {}, "output_type": "execute_result" }, @@ -2266,7 +2034,7 @@ }, { "cell_type": "code", - "execution_count": 38, + "execution_count": 34, "id": "6e8b6a32", "metadata": { "slideshow": { @@ -2294,20 +2062,41 @@ "1. __Define the variable as a function of time.__ Let's say we want to create a variable to represent the changing reaction temperature. Assume the temperature value \\[K\\] can be expressed as $T = 298.15 + 5\\cdot \\sin(t)$, indicating that the temperatue fluctuacts around $25^{\\circ}C$ by $\\pm 5^{\\circ}C$. Then simply,\n", "```python\n", "T = EDV('T', function=lambda t: 298.15+5*np.sin(t))\n", - "```\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "e2885b32", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ "2. __Provide time-series data to describe the dynamics of the variable.__ For demonstration purpose, we'll just make up the data. In practice, this is convenient if you have real data.\n", "```python\n", "t_arr = np.linspace(0, 5)\n", "y_arr = 298.15+5*np.sin(t_arr)\n", "T = EDV('T', t=t_arr, y=y_arr)\n", - "```\n", - "\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "a89fa738", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ "For convenience, `ExogenousDynamicVariable` also has a `classmethod` that enables batch creation of multiple variables at once. We just need to provide a file of the time-series data, including a column `t` for time points and additional columns of the variable values. See the [documentation](https://qsdsan.readthedocs.io/en/latest/api/utils/dynamics.html#qsdsan.utils.ExogenousDynamicVariable.batch_init) of `ExogenousDynamicVariable.batch_init` for detailed usage." ] }, { "cell_type": "code", - "execution_count": 39, + "execution_count": 35, "id": "b6401d1c", "metadata": { "slideshow": { @@ -2333,7 +2122,7 @@ }, { "cell_type": "code", - "execution_count": 40, + "execution_count": 36, "id": "2639a8b7", "metadata": { "slideshow": { @@ -2355,7 +2144,7 @@ "(,)" ] }, - "execution_count": 40, + "execution_count": 36, "metadata": {}, "output_type": "execute_result" } @@ -2370,7 +2159,7 @@ }, { "cell_type": "code", - "execution_count": 41, + "execution_count": 37, "id": "a7e11837", "metadata": { "slideshow": { @@ -2384,7 +2173,7 @@ "[295.15]" ] }, - "execution_count": 41, + "execution_count": 37, "metadata": {}, "output_type": "execute_result" } @@ -2400,7 +2189,7 @@ "id": "0d1b7290", "metadata": { "slideshow": { - "slide_type": "slide" + "slide_type": "skip" } }, "source": [ @@ -2422,7 +2211,7 @@ }, { "cell_type": "code", - "execution_count": 42, + "execution_count": 38, "id": "3ea577e7", "metadata": { "slideshow": { @@ -2437,9 +2226,10 @@ }, { "cell_type": "code", - "execution_count": 43, + "execution_count": 39, "id": "7c2c9521", "metadata": { + "scrolled": false, "slideshow": { "slide_type": "slide" } @@ -2452,7 +2242,7 @@ " )" ] }, - "execution_count": 43, + "execution_count": 39, "metadata": {}, "output_type": "execute_result" }, @@ -2481,7 +2271,7 @@ "id": "786d6034", "metadata": { "slideshow": { - "slide_type": "slide" + "slide_type": "skip" } }, "source": [ diff --git a/docs/source/tutorials/12_Anaerobic_Digestion_Model_No_1.ipynb b/docs/source/tutorials/12_Anaerobic_Digestion_Model_No_1.ipynb index 692f852c..ca9f713e 100644 --- a/docs/source/tutorials/12_Anaerobic_Digestion_Model_No_1.ipynb +++ b/docs/source/tutorials/12_Anaerobic_Digestion_Model_No_1.ipynb @@ -17,13 +17,7 @@ " - [2. System Setup](#s2)\n", " - [3. System Simulation](#s3)\n", " \n", - "- **Video demo:**\n", - "\n", - " - To be posted\n", - " \n", - "To run tutorials in your browser, go to this [Binder page](https://mybinder.org/v2/gh/QSD-Group/QSDsan/main?filepath=%2Fdocs%2Fsource%2Ftutorials).\n", - " \n", - "You can also watch a video demo on YouTube (link to be posted) (subscriptions & likes appreciated!)." + "To run tutorials in your browser, go to this [Binder page](https://mybinder.org/v2/gh/QSD-Group/QSDsan/main?filepath=%2Fdocs%2Fsource%2Ftutorials)." ] }, {