{ "cells": [ { "cell_type": "markdown", "id": "c0cd125d", "metadata": {}, "source": [ "# `WasteStream` \n", "\n", "*Click the badge below to try this tutorial interactively in your browser:*\n", "\n", "[![Launch Binder](../images/custom_binder_logo.svg)](https://mybinder.org/v2/gh/QSD-Group/QSDsan-env/main?urlpath=git-pull%3Frepo%3Dhttps%253A%252F%252Fgithub.com%252FQSD-group%252FQSDsan%26urlpath%3Dlab%252Ftree%252FQSDsan%252Fdocs%252Fsource%252Ftutorials%26branch%3Dmain)\n", "\n", "*You can also run this tutorial in [Google Colab](https://colab.research.google.com). It takes a one-time setup per session: follow the [Colab instructions](https://qsdsan.readthedocs.io/en/latest/tutorials/index.html#run-in-colab).*\n", "\n", "- **Prepared by:**\n", "\n", " - [Yalin Li](https://github.com/yalinli2)\n", " - [Joy Zhang](https://github.com/joyxyz1994/)\n", "\n", "- **Learning objectives.** After this tutorial, you will be able to:\n", "\n", " - Create a `WasteStream` and set its composition\n", " - Read derived flow properties and concentrations\n", " - Convert between mass-flow and concentration representations\n", "\n", "- **Prerequisites:** [2. Component](https://qsdsan.readthedocs.io/en/latest/tutorials/2_Component.html)\n", "\n", "- **Covered topics:**\n", "\n", " - 1. Creating WasteStream\n", " - 2. Major attributes\n", "\n", "> **Companion video.** A walkthrough of this tutorial is available on [YouTube](https://youtu.be/yCOZ0F6E1Sw), presented by [Hannah Lohman](https://github.com/haclohman). Recorded against `QSDsan` v1.2.0. The concepts still apply, but if the code on screen differs from this notebook, follow the notebook.\n" ] }, { "cell_type": "markdown", "id": "3e788acbcc35", "metadata": {}, "source": [ "\n", "\n", "## Setup\n", "\n", "Import `QSDsan` and confirm the installed version.\n" ] }, { "cell_type": "code", "execution_count": 1, "id": "ea65fc2e", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "This tutorial was made with qsdsan v1.5.2.\n" ] } ], "source": [ "import qsdsan as qs\n", "print(f'This tutorial was made with qsdsan v{qs.__version__}.')" ] }, { "cell_type": "markdown", "id": "eef115d3", "metadata": {}, "source": [ "## 1. Creating `WasteStream` \n", "A `WasteStream` object can be created by defining flow rate of each `Component` or through built-in influent characterization models (e.g., by specifying total volumetric flowrate, concentrations of total COD, TKN, etc. together with COD fractions)." ] }, { "cell_type": "code", "execution_count": 2, "id": "1b5fe7a6", "metadata": {}, "outputs": [], "source": [ "# Before using `WasteStream`, we need to tell qsdsan what components we will be working with\n", "# let's load the default components for the demo purpose\n", "cmps = qs.Components.load_default()\n", "qs.set_thermo(cmps)" ] }, { "cell_type": "code", "execution_count": 3, "id": "aac56679", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "CompiledComponents([\n", " S_H2, S_CH4, S_CH3OH, S_Ac, \n", " S_Prop, S_F, S_U_Inf, S_U_E, \n", " C_B_Subst, C_B_BAP, C_B_UAP, C_U_Inf, \n", " X_B_Subst, X_OHO_PHA, X_GAO_PHA, X_PAO_PHA,\n", " X_GAO_Gly, X_PAO_Gly, X_OHO, X_AOO, \n", " X_NOO, X_AMO, X_PAO, X_MEOLO, \n", " X_FO, X_ACO, X_HMO, X_PRO, \n", " X_U_Inf, X_U_OHO_E, X_U_PAO_E, X_Ig_ISS, \n", " X_MgCO3, X_CaCO3, X_MAP, X_HAP, \n", " X_HDP, X_FePO4, X_AlPO4, X_AlOH, \n", " X_FeOH, X_PAO_PP_Lo, X_PAO_PP_Hi, S_NH4, \n", " S_NO2, S_NO3, S_PO4, S_K, \n", " S_Ca, S_Mg, S_CO3, S_N2, \n", " S_O2, S_CAT, S_AN, H2O, \n", "])\n" ] } ], "source": [ "# Just to remind ourselves what are the default components\n", "cmps.show()" ] }, { "cell_type": "markdown", "id": "6c64c7b1", "metadata": {}, "source": [ "
\n", "\n", "**Three stream classes.** `qsdsan` works with three related stream classes that share the **same constructor**, so you build and update flows the same way for all of them:\n", "\n", "- `Stream` (from `thermosteam`) — a plain process stream; use it when you only need mass and energy balances.\n", "- `SanStream` — a `Stream` that can additionally carry life cycle impacts. Stream-level LCA (`add_indicators`, `get_impact`, `get_impacts`) is covered in [8. LCA](https://qsdsan.readthedocs.io/en/latest/tutorials/8_LCA.html).\n", "- `WasteStream` — a `SanStream` with the wastewater-specific properties used throughout this tutorial (COD, BOD, TN, solids, ...).\n", "\n", "This tutorial focuses on `WasteStream`. See the [stream classes documentation](https://qsdsan.readthedocs.io/en/latest/api/major_classes/streams.html) for the full comparison.\n", "\n", "
" ] }, { "cell_type": "markdown", "id": "ws14md1", "metadata": {}, "source": [ "Because the three classes share a constructor, the same call builds any of them, and you can convert an existing `Stream` or `SanStream` into a `WasteStream` with `from_stream`:" ] }, { "cell_type": "code", "execution_count": 4, "id": "ws14code1", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "WasteStream: ws_from_s\n", "phase: 'l', T: 298.15 K, P: 101325 Pa\n", "flow (g/hr): S_F 100\n", " H2O 1e+06\n", " WasteStream-specific properties:\n", " pH : 7.0\n", " COD : 99.7 mg/L\n", " BOD : 71.5 mg/L\n", " TC : 31.9 mg/L\n", " TOC : 31.9 mg/L\n", " TN : 3.0 mg/L\n", " TP : 1.0 mg/L\n", " Component concentrations (mg/L):\n", " S_F 99.7\n", " H2O 996949.8\n" ] } ], "source": [ "s = qs.Stream('s', S_F=0.1, H2O=1000, units='kg/hr') # thermosteam Stream\n", "ss = qs.SanStream('ss', S_F=0.1, H2O=1000, units='kg/hr') # adds LCA support\n", "ws_from_s = qs.WasteStream.from_stream(s, 'ws_from_s') # convert a Stream to a WasteStream\n", "ws_from_s.show()" ] }, { "cell_type": "markdown", "id": "0fee20d7", "metadata": {}, "source": [ "### 1.1. By defining component flow rate" ] }, { "cell_type": "code", "execution_count": 5, "id": "6d344034", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "WasteStream: ws1\n", "phase: 'l', T: 298.15 K, P: 101325 Pa\n", "flow (g/hr): X_GAO_Gly 500\n", " H2O 1e+06\n", " WasteStream-specific properties:\n", " pH : 7.0\n", " Alkalinity : 2.5 mmol/L\n", " COD : 498.4 mg/L\n", " BOD : 289.0 mg/L\n", " TC : 186.9 mg/L\n", " TOC : 186.9 mg/L\n", " TSS : 420.5 mg/L\n", " Component concentrations (mg/L):\n", " X_GAO_Gly 498.4\n", " H2O 996745.1\n" ] } ], "source": [ "# You can initialize a WasteStream by setting the flow rate of the components within it,\n", "# we usually use lower case for ID of a WasteStream\n", "ws1 = qs.WasteStream('ws1', X_GAO_Gly=.5, H2O=1000, units='kg/hr')\n", "ws1.show()" ] }, { "cell_type": "code", "execution_count": 6, "id": "ad50172e", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "WasteStream: ws1\n", "phase: 'l', T: 298.15 K, P: 101325 Pa\n", "flow (kg/hr): X_GAO_Gly 1.5\n", " H2O 1.8e+03\n", " WasteStream-specific properties:\n", " pH : 7.0\n", " Alkalinity : 2.5 mmol/L\n", " ...\n", " Component concentrations (g/L):\n", " X_GAO_Gly 0.8\n", " H2O 996.5\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "c:\\Users\\Yalin\\Documents\\Coding\\QSDsan-platform\\.venv\\Lib\\site-packages\\thermosteam\\_stream.py:407: RuntimeWarning: has been replaced in registry\n", " self._register(ID)\n" ] } ], "source": [ "# You can certainly use differnent units in defining and showing the stream,\n", "# note that using the same ID will replace the original one (and you'll receive a warning like the one below)\n", "# also note that the `ws1` in the beginning () is not the same as the \"ws1\" in the parentheses (actual ID),\n", "# you can use different names, but for consistency we usually keep them as the same\n", "ws1 = qs.WasteStream('ws1', X_GAO_Gly=1.5, H2O=100, units='kmol/hr')\n", "ws1.show(flow='kg/hr', details=False, concentrations='g/L')" ] }, { "cell_type": "code", "execution_count": 7, "id": "54227f80", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "700.0928198338145" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# You can also get other information such as TDS, VSS\n", "ws1.get_VSS()" ] }, { "cell_type": "markdown", "id": "301d0cea", "metadata": {}, "source": [ "### 1.2. By specifying component concentration" ] }, { "cell_type": "code", "execution_count": 8, "id": "2a4bdbfc", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "WasteStream: ws2\n", "phase: 'l', T: 298.15 K, P: 101325 Pa\n", "flow (g/hr): S_Ca 10\n", " H2O 9.97e+04\n", " WasteStream-specific properties:\n", " pH : 7.0\n", " Alkalinity : 2.5 mmol/L\n", " Component concentrations (mg/L):\n", " S_Ca 100.0\n", " H2O 996949.4\n" ] } ], "source": [ "# Sometimes we might prefer concentration over flow rates\n", "# Note that if you don't provide an ID, qsdsan will assign a default one as in \"wsX\"\n", "# (X being an ascending number)\n", "ws2 = qs.WasteStream('ws2')\n", "ws2.set_flow_by_concentration(flow_tot=100, concentrations={'S_Ca':100}, units=('L/hr', 'mg/L'))\n", "ws2.show()" ] }, { "cell_type": "markdown", "id": "6b25f1be", "metadata": {}, "source": [ "### 1.3. By using wastewater models" ] }, { "cell_type": "code", "execution_count": 9, "id": "878dad20", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "WasteStream: ws3\n", "phase: 'l', T: 298.15 K, P: 101325 Pa\n", "flow (g/hr): S_F 100\n", " S_U_Inf 25\n", " C_B_Subst 46.5\n", " X_B_Subst 264\n", " X_U_Inf 65\n", " X_Ig_ISS 60.8\n", " S_NH4 25\n", " S_PO4 8\n", " S_K 28\n", " S_Ca 140\n", " S_Mg 50\n", " S_CO3 120\n", " S_N2 18\n", " S_CAT 3\n", " S_AN 12\n", " H2O 9.96e+05\n", " WasteStream-specific properties:\n", " pH : 6.8\n", " Alkalinity : 10.0 mmol/L\n", " COD : 500.0 mg/L\n", " BOD : 257.9 mg/L\n", " TC : 288.7 mg/L\n", " TOC : 160.0 mg/L\n", " TN : 40.0 mg/L\n", " TP : 11.0 mg/L\n", " TK : 28.0 mg/L\n", " TSS : 243.3 mg/L\n", " Component concentrations (mg/L):\n", " S_F 100.0\n", " S_U_Inf 25.0\n", " C_B_Subst 46.5\n", " X_B_Subst 263.5\n", " X_U_Inf 65.0\n", " X_Ig_ISS 60.8\n", " S_NH4 25.0\n", " S_PO4 8.0\n", " S_K 28.0\n", " S_Ca 140.0\n", " S_Mg 50.0\n", " S_CO3 120.0\n", " S_N2 18.0\n", " S_CAT 3.0\n", " S_AN 12.0\n", " H2O 996166.9\n" ] } ], "source": [ "# We can default the WasteSteram to typical raw wastewater composition based on different models\n", "ws3 = qs.WasteStream.codstates_inf_model('ws3', flow_tot=1000, pH=6.8, COD=500, TP=11)\n", "ws3.show(N=20)" ] }, { "cell_type": "markdown", "id": "c1394540", "metadata": {}, "source": [ "The other influent models follow the same pattern but take a different primary input: `codbased_inf_model` (COD, with a different COD fractionation), `bodbased_inf_model` (BOD instead of COD), and `sludge_inf_model` (high-solids streams, takes TSS). For example, the BOD-based model:" ] }, { "cell_type": "code", "execution_count": 10, "id": "0ac3b1a5", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "WasteStream: ws3b\n", "phase: 'l', T: 298.15 K, P: 101325 Pa\n", "flow (g/hr): S_F 87.2\n", " S_U_Inf 37.8\n", " C_B_Subst 40\n", " X_B_Subst 227\n", " X_U_Inf 39.4\n", " X_Ig_ISS 49.3\n", " S_NH4 25\n", " S_PO4 8\n", " ... 9.97e+05\n", " WasteStream-specific properties:\n", " pH : 7.0\n", " Alkalinity : 10.0 mmol/L\n", " COD : 431.0 mg/L\n", " BOD : 250.0 mg/L\n", " TC : 264.9 mg/L\n", " TOC : 137.9 mg/L\n", " TN : 40.0 mg/L\n", " TP : 10.0 mg/L\n", " TK : 28.0 mg/L\n", " TSS : 197.1 mg/L\n", " Component concentrations (mg/L):\n", " S_F 87.2\n", " S_U_Inf 37.8\n", " C_B_Subst 40.0\n", " X_B_Subst 226.7\n", " X_U_Inf 39.4\n", " X_Ig_ISS 49.3\n", " S_NH4 25.0\n", " S_PO4 8.0\n", " ...\n" ] } ], "source": [ "ws3b = qs.WasteStream.bodbased_inf_model('ws3b', flow_tot=1000, BOD=250)\n", "ws3b.show(N=8)" ] }, { "cell_type": "markdown", "id": "bb660398", "metadata": {}, "source": [ "## 2. Major attributes" ] }, { "cell_type": "markdown", "id": "f02fea35", "metadata": {}, "source": [ "### 2.1. Retrieving flow info" ] }, { "cell_type": "code", "execution_count": 11, "id": "7051b235", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "The total mass flow rate of ws3 is 997.1 kg/hr\n", "\n", "996.1668549779508\n" ] }, { "data": { "text/plain": [ "sparse([0.000e+00, 0.000e+00, 0.000e+00, 0.000e+00, 0.000e+00, 1.000e-01,\n", " 2.500e-02, 0.000e+00, 4.650e-02, 0.000e+00, 0.000e+00, 0.000e+00,\n", " 2.635e-01, 0.000e+00, 0.000e+00, 0.000e+00, 0.000e+00, 0.000e+00,\n", " 0.000e+00, 0.000e+00, 0.000e+00, 0.000e+00, 0.000e+00, 0.000e+00,\n", " 0.000e+00, 0.000e+00, 0.000e+00, 0.000e+00, 6.500e-02, 0.000e+00,\n", " 0.000e+00, 6.083e-02, 0.000e+00, 0.000e+00, 0.000e+00, 0.000e+00,\n", " 0.000e+00, 0.000e+00, 0.000e+00, 0.000e+00, 0.000e+00, 0.000e+00,\n", " 0.000e+00, 2.500e-02, 0.000e+00, 0.000e+00, 8.000e-03, 2.800e-02,\n", " 1.400e-01, 5.000e-02, 1.200e-01, 1.800e-02, 0.000e+00, 3.000e-03,\n", " 1.200e-02, 9.962e+02])" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# In many cases we want the flow rates of all components at once,\n", "# to do that we can use\n", "print(f'The total mass flow rate of {ws3.ID} is {ws3.F_mass:.1f} kg/hr\\n')\n", "print(ws3.imass['H2O']) # mass flow rate, always in kg/hr\n", "ws3.mass # the entire array" ] }, { "cell_type": "code", "execution_count": 12, "id": "92713574", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "The total molar flow rate of ws3 is 55.9 kmol/hr\n", "\n", "55.29566318025314\n" ] }, { "data": { "text/plain": [ "sparse([0.000e+00, 0.000e+00, 0.000e+00, 0.000e+00, 0.000e+00, 1.000e-01,\n", " 2.500e-02, 0.000e+00, 4.650e-02, 0.000e+00, 0.000e+00, 0.000e+00,\n", " 2.635e-01, 0.000e+00, 0.000e+00, 0.000e+00, 0.000e+00, 0.000e+00,\n", " 0.000e+00, 0.000e+00, 0.000e+00, 0.000e+00, 0.000e+00, 0.000e+00,\n", " 0.000e+00, 0.000e+00, 0.000e+00, 0.000e+00, 6.500e-02, 0.000e+00,\n", " 0.000e+00, 6.083e-02, 0.000e+00, 0.000e+00, 0.000e+00, 0.000e+00,\n", " 0.000e+00, 0.000e+00, 0.000e+00, 0.000e+00, 0.000e+00, 0.000e+00,\n", " 0.000e+00, 1.386e-03, 0.000e+00, 0.000e+00, 4.146e-05, 7.161e-04,\n", " 3.493e-03, 2.057e-03, 1.967e-03, 6.425e-04, 0.000e+00, 3.000e-03,\n", " 1.200e-02, 5.530e+01])" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Similarly for molar and volumetric flow rates\n", "print(f'The total molar flow rate of {ws3.ID} is {ws3.F_mol:.1f} kmol/hr\\n')\n", "print(ws3.imol['H2O'])\n", "ws3.mol" ] }, { "cell_type": "code", "execution_count": 13, "id": "10ef9026", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "The total volumetric flow rate of ws3 is 1.0 m3/hr\n", "\n", "0.9990997329993598\n" ] }, { "data": { "text/plain": [ "sparse([0.000e+00, 0.000e+00, 0.000e+00, 0.000e+00, 0.000e+00, 1.154e-04,\n", " 2.885e-05, 0.000e+00, 2.988e-05, 0.000e+00, 0.000e+00, 0.000e+00,\n", " 1.693e-04, 0.000e+00, 0.000e+00, 0.000e+00, 0.000e+00, 0.000e+00,\n", " 0.000e+00, 0.000e+00, 0.000e+00, 0.000e+00, 0.000e+00, 0.000e+00,\n", " 0.000e+00, 0.000e+00, 0.000e+00, 0.000e+00, 4.177e-05, 0.000e+00,\n", " 0.000e+00, 3.910e-05, 0.000e+00, 0.000e+00, 0.000e+00, 0.000e+00,\n", " 0.000e+00, 0.000e+00, 0.000e+00, 0.000e+00, 0.000e+00, 0.000e+00,\n", " 0.000e+00, 2.885e-05, 0.000e+00, 0.000e+00, 9.233e-06, 3.232e-05,\n", " 1.616e-04, 5.771e-05, 1.385e-04, 3.041e-05, 0.000e+00, 3.462e-06,\n", " 1.385e-05, 9.991e-01])" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "print(f'The total volumetric flow rate of {ws3.ID} is {ws3.F_vol:.1f} m3/hr\\n')\n", "print(ws3.ivol['H2O'])\n", "ws3.vol" ] }, { "cell_type": "code", "execution_count": 14, "id": "04a698c9", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "sparse([0.000e+00, 0.000e+00, 0.000e+00, 0.000e+00, 0.000e+00, 1.000e+02,\n", " 2.500e+01, 0.000e+00, 4.650e+01, 0.000e+00, 0.000e+00, 0.000e+00,\n", " 2.635e+02, 0.000e+00, 0.000e+00, 0.000e+00, 0.000e+00, 0.000e+00,\n", " 0.000e+00, 0.000e+00, 0.000e+00, 0.000e+00, 0.000e+00, 0.000e+00,\n", " 0.000e+00, 0.000e+00, 0.000e+00, 0.000e+00, 6.500e+01, 0.000e+00,\n", " 0.000e+00, 6.083e+01, 0.000e+00, 0.000e+00, 0.000e+00, 0.000e+00,\n", " 0.000e+00, 0.000e+00, 0.000e+00, 0.000e+00, 0.000e+00, 0.000e+00,\n", " 0.000e+00, 2.500e+01, 0.000e+00, 0.000e+00, 8.000e+00, 2.800e+01,\n", " 1.400e+02, 5.000e+01, 1.200e+02, 1.800e+01, 0.000e+00, 3.000e+00,\n", " 1.200e+01, 9.962e+05])" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Also concentrations (in g/m3)\n", "ws3.Conc" ] }, { "cell_type": "markdown", "id": "wsconcmd", "metadata": {}, "source": [ "`Conc` returns the whole concentration array (in g/m³, i.e. mg/L). For individual values, index `iconc` by component ID, or use `get_mass_concentration` when you want to choose the unit and a subset of components:" ] }, { "cell_type": "code", "execution_count": 15, "id": "wsconccode", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "24.999999999999986 mg/L\n" ] }, { "data": { "text/plain": [ "array([25., 8.])" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "print(ws3.iconc['S_NH4'], 'mg/L') # iconc is indexable by component ID\n", "ws3.get_mass_concentration(unit='mg/L', IDs=('S_NH4', 'S_PO4'))" ] }, { "cell_type": "markdown", "id": "wsbulkmd", "metadata": {}, "source": [ "A few scalar properties summarize the whole stream: `density` (kg/m³) and `dry_mass` (total solids, mg/L), alongside the totals `F_mass`, `F_mol`, and `F_vol` used above." ] }, { "cell_type": "code", "execution_count": 16, "id": "wsbulkcode", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "density = 997.1 kg/m3\n", "dry_mass = 1227.7 mg/L\n" ] } ], "source": [ "print(f'density = {ws3.density:.1f} kg/m3')\n", "print(f'dry_mass = {ws3.dry_mass:.1f} mg/L')" ] }, { "cell_type": "code", "execution_count": 17, "id": "e4ce783b", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Before updating, mass flow of water is 996.1668549779508\n", "After updating, mass flow of water is 1992.3337099559017\n" ] } ], "source": [ "# And you can update the arrays as you like\n", "print(f\"Before updating, mass flow of water is {ws3.imass['H2O']}\")\n", "ws3.imass['H2O'] *= 2\n", "print(f\"After updating, mass flow of water is {ws3.imass['H2O']}\")" ] }, { "cell_type": "code", "execution_count": 18, "id": "2ad08788", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Before updating, mass array is \n", "[0.000e+00 0.000e+00 0.000e+00 0.000e+00 0.000e+00 1.000e-01 2.500e-02\n", " 0.000e+00 4.650e-02 0.000e+00 0.000e+00 0.000e+00 2.635e-01 0.000e+00\n", " 0.000e+00 0.000e+00 0.000e+00 0.000e+00 0.000e+00 0.000e+00 0.000e+00\n", " 0.000e+00 0.000e+00 0.000e+00 0.000e+00 0.000e+00 0.000e+00 0.000e+00\n", " 6.500e-02 0.000e+00 0.000e+00 6.083e-02 0.000e+00 0.000e+00 0.000e+00\n", " 0.000e+00 0.000e+00 0.000e+00 0.000e+00 0.000e+00 0.000e+00 0.000e+00\n", " 0.000e+00 2.500e-02 0.000e+00 0.000e+00 8.000e-03 2.800e-02 1.400e-01\n", " 5.000e-02 1.200e-01 1.800e-02 0.000e+00 3.000e-03 1.200e-02 1.992e+03]\n", "After updating, mass array is \n", "[0.000e+00 0.000e+00 0.000e+00 0.000e+00 0.000e+00 5.000e-02 1.250e-02\n", " 0.000e+00 2.325e-02 0.000e+00 0.000e+00 0.000e+00 1.318e-01 0.000e+00\n", " 0.000e+00 0.000e+00 0.000e+00 0.000e+00 0.000e+00 0.000e+00 0.000e+00\n", " 0.000e+00 0.000e+00 0.000e+00 0.000e+00 0.000e+00 0.000e+00 0.000e+00\n", " 3.250e-02 0.000e+00 0.000e+00 3.042e-02 0.000e+00 0.000e+00 0.000e+00\n", " 0.000e+00 0.000e+00 0.000e+00 0.000e+00 0.000e+00 0.000e+00 0.000e+00\n", " 0.000e+00 1.250e-02 0.000e+00 0.000e+00 4.000e-03 1.400e-02 7.000e-02\n", " 2.500e-02 6.000e-02 9.000e-03 0.000e+00 1.500e-03 6.000e-03 9.962e+02]\n" ] } ], "source": [ "# This works on the entire array as well\n", "print(f\"Before updating, mass array is \\n{ws3.mass}\")\n", "ws3.mass /= 2\n", "print(f\"After updating, mass array is \\n{ws3.mass}\")" ] }, { "cell_type": "markdown", "id": "8fed2f72", "metadata": {}, "source": [ "### 2.2. Copying, mixing, and splitting" ] }, { "cell_type": "code", "execution_count": 19, "id": "4eea126c", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "WasteStream: copy_of_ws1\n", "phase: 'l', T: 298.15 K, P: 101325 Pa\n", "flow (g/hr): X_GAO_Gly 1.5e+03\n", " H2O 1.8e+06\n", " WasteStream-specific properties:\n", " pH : 7.0\n", " Alkalinity : 2.5 mmol/L\n", " COD : 829.7 mg/L\n", " BOD : 581.4 mg/L\n", " TC : 311.2 mg/L\n", " TOC : 311.2 mg/L\n", " TSS : 700.1 mg/L\n", " Component concentrations (mg/L):\n", " X_GAO_Gly 829.7\n", " H2O 996532.8\n" ] } ], "source": [ "# We can make copies of a stream\n", "ws4 = ws1.copy(new_ID='copy_of_ws1')\n", "ws4.show()" ] }, { "cell_type": "markdown", "id": "wscopymd", "metadata": {}, "source": [ "`qsdsan` gives you several ways to duplicate a stream. The right one depends on whether you want a brand-new object and whether the two streams should stay linked:\n", "\n", "| Method | New stream? | What it brings over | Linked to the original? |\n", "| --- | --- | --- | --- |\n", "| `copy` | yes | flows, temperature, pressure, phase, and wastewater properties | no, fully independent |\n", "| `copy_like` | no (the target must already exist) | same as `copy`, written into the existing stream | no, fully independent |\n", "| `copy_flow` | no | the mass flows only (not T/P), optionally a chosen subset of components | no, fully independent |\n", "| `proxy` | yes | nothing is copied | yes, the two **share** their data |\n", "\n", "`copy`, `copy_like`, and `copy_flow` do **not** bring over the `price` or the stream impact item by default; pass `copy_price=True` / `copy_impact_item=True` to `copy` or `copy_like` if you need them." ] }, { "cell_type": "code", "execution_count": 20, "id": "wscopycode", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "copy is independent: True\n", "proxy is linked: True\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "C:\\Users\\Yalin\\Documents\\Coding\\QSDsan-platform\\QSDsan\\qsdsan\\_sanstream.py:676: UserWarning: The property `characterization_factors` is used in biosteam/thermosteam, not qsdsan. Use `stream_impact_item` instead.\n", " warn('The property `characterization_factors` is used in biosteam/thermosteam, '\n" ] } ], "source": [ "# `copy` -> an independent stream; editing the copy leaves the original untouched\n", "ws_copy = ws1.copy('ws_copy')\n", "ws_copy.imass['H2O'] *= 2\n", "print('copy is independent:', ws1.imass['H2O'] != ws_copy.imass['H2O'])\n", "\n", "# `proxy` -> shares data; editing the proxy also changes the original\n", "ws_view = ws1.proxy('ws_view')\n", "ws_view.imass['H2O'] *= 2\n", "print('proxy is linked: ', ws1.imass['H2O'] == ws_view.imass['H2O'])" ] }, { "cell_type": "code", "execution_count": 21, "id": "870273bf", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "WasteStream: ws5\n", "phase: 'l', T: 298.15 K, P: 101325 Pa\n", "flow (g/hr): S_F 50\n", " S_U_Inf 12.5\n", " C_B_Subst 23.2\n", " X_B_Subst 132\n", " X_U_Inf 32.5\n", " X_Ig_ISS 30.4\n", " S_NH4 12.5\n", " S_PO4 4\n", " S_K 14\n", " S_Ca 80\n", " S_Mg 25\n", " S_CO3 60\n", " S_N2 9\n", " S_CAT 1.5\n", " S_AN 6\n", " ... 1.1e+06\n", " WasteStream-specific properties:\n", " pH : 6.8\n", " Alkalinity : 9.3 mmol/L\n", " COD : 227.4 mg/L\n", " BOD : 131.7 mg/L\n", " TC : 131.3 mg/L\n", " TOC : 72.8 mg/L\n", " TN : 19.3 mg/L\n", " TP : 4.7 mg/L\n", " TK : 12.7 mg/L\n", " TSS : 110.7 mg/L\n", " Component concentrations (mg/L):\n", " S_F 45.5\n", " S_U_Inf 11.4\n", " C_B_Subst 21.1\n", " X_B_Subst 119.8\n", " X_U_Inf 29.6\n", " X_Ig_ISS 27.7\n", " S_NH4 11.4\n", " S_PO4 3.6\n", " S_K 12.7\n", " S_Ca 72.8\n", " S_Mg 22.7\n", " S_CO3 54.6\n", " S_N2 8.2\n", " S_CAT 1.4\n", " S_AN 5.5\n", " ...\n" ] } ], "source": [ "# We can mix two streams\n", "ws5 = qs.WasteStream('ws5')\n", "ws5.mix_from((ws2, ws3))\n", "ws5.show()" ] }, { "cell_type": "code", "execution_count": 22, "id": "96da072a", "metadata": {}, "outputs": [], "source": [ "# Or split one stream into two, note that the split will be the fraction to the first effluent stream\n", "ws6, ws7 = qs.WasteStream('ws6'), qs.WasteStream('ws7')\n", "ws5.split_to(ws6, ws7, split=0.3)" ] }, { "cell_type": "code", "execution_count": 23, "id": "87504c1d", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1096.3542122657523 328.90626367972567 767.4479485860265 diff is 0.0\n" ] } ], "source": [ "print(ws5.F_mass, ws6.F_mass, ws7.F_mass, f'diff is {ws5.F_mass-ws6.F_mass-ws7.F_mass}')" ] }, { "cell_type": "code", "execution_count": 24, "id": "3c62eebe", "metadata": {}, "outputs": [], "source": [ "# In splitting, you can set the split for each Component\n", "ws8 = qs.WasteStream('ws8', X_AlOH=1, H2O=1000, units='kg/hr')\n", "ws9, ws10 = qs.WasteStream('ws9'), qs.WasteStream('ws10')\n", "split = cmps.kwarray({'X_AlOH':0.5, 'H2O':0.8})\n", "ws8.split_to(ws9, ws10, split=split)" ] }, { "cell_type": "code", "execution_count": 25, "id": "e4ebdd91", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "WasteStream: ws9\n", "phase: 'l', T: 298.15 K, P: 101325 Pa\n", "flow (kg/hr): X_AlOH 0.5\n", " H2O 800\n", " WasteStream-specific properties:\n", " pH : 7.0\n", " Alkalinity : 2.5 mmol/L\n", " TSS : 622.9 mg/L\n", " Component concentrations (mg/L):\n", " X_AlOH 622.9\n", " H2O 996665.3\n" ] } ], "source": [ "ws9.show(flow='kg/hr')" ] }, { "cell_type": "code", "execution_count": 26, "id": "dd3ebc34", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "WasteStream: ws10\n", "phase: 'l', T: 298.15 K, P: 101325 Pa\n", "flow (kg/hr): X_AlOH 0.5\n", " H2O 200\n", " WasteStream-specific properties:\n", " pH : 7.0\n", " Alkalinity : 2.5 mmol/L\n", " TSS : 2488.7 mg/L\n", " Component concentrations (mg/L):\n", " X_AlOH 2488.7\n", " H2O 995469.8\n" ] } ], "source": [ "ws10.show(flow='kg/hr')" ] }, { "cell_type": "markdown", "id": "540add5e", "metadata": {}, "source": [ "### 2.3. `composite`" ] }, { "cell_type": "code", "execution_count": 27, "id": "657d6207", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "COD of ws3 is 250.11258405293376 mg/L\n", "TOC of ws3 is 80.03602689693881 mg/L\n", "TKN of ws3 is 21.22792824647371 mg/L\n", "TP of ws3 is 5.159434503278149 mg/L\n", "ThOD of ws3 is 338.19822319820423 mg/L\n" ] } ], "source": [ "# Most composite variables relevant to wastewater treatment are available as properties of the WasteStream object\n", "# for example\n", "print('COD of ws3 is', ws3.COD, 'mg/L')\n", "print('TOC of ws3 is', ws3.TOC, 'mg/L')\n", "print('TKN of ws3 is', ws3.TKN, 'mg/L')\n", "print('TP of ws3 is', ws3.TP, 'mg/L')\n", "print('ThOD of ws3 is', ws3.ThOD, 'mg/L')" ] }, { "cell_type": "code", "execution_count": 28, "id": "647936d5", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Without colloidals, VSS for the waste stream is 91.3 mg/L.\n", "With colloidals, VSS for the stream is 109.7 mg/L.\n" ] } ], "source": [ "# For composite variables related to dissolved or suspended solids, they are \n", "# available as methods (i.e., functions specifically for the WasteStream class),\n", "# because there are controversies around whether colloidal components should be \n", "# considered suspened or dissolved solids (default is to NOT include colloidals).\n", "print(f'Without colloidals, VSS for the waste stream is {ws3.get_VSS():.1f} mg/L.')\n", "print(f'With colloidals, VSS for the stream is {ws3.get_VSS(include_colloidal=True):.1f} mg/L.')" ] }, { "cell_type": "code", "execution_count": 29, "id": "ea419d7a", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Without colloidals, VSS for the waste stream is 350.1 mg/L.\n", "With colloidals, VSS for the stream is 350.1 mg/L.\n" ] } ], "source": [ "# Of course, this won't matter if the waste stream does not have colloidal components\n", "print(f'Without colloidals, VSS for the waste stream is {ws1.get_VSS():.1f} mg/L.')\n", "print(f'With colloidals, VSS for the stream is {ws1.get_VSS(include_colloidal=True):.1f} mg/L.')" ] }, { "cell_type": "code", "execution_count": 30, "id": "9caaf027", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "TSS = 121.7 mg/L\n", "ISS = 30.4 mg/L\n", "TDS = 492.4 mg/L\n" ] } ], "source": [ "# `get_VSS` has companions for the other solids fractions:\n", "# total suspended solids (TSS), inorganic suspended solids (ISS),\n", "# and total dissolved solids (TDS), all in mg/L\n", "print(f'TSS = {ws3.get_TSS():.1f} mg/L')\n", "print(f'ISS = {ws3.get_ISS():.1f} mg/L')\n", "print(f'TDS = {ws3.get_TDS():.1f} mg/L')" ] }, { "cell_type": "markdown", "id": "074352e7", "metadata": {}, "source": [ "All the composite variables above (COD, VSS, etc.) are essentially calculated using the `composite` method. You can calculate all kinds of composite variables by specifying different arguments in this method." ] }, { "cell_type": "code", "execution_count": 31, "id": "45aebd00", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "92.35569739334213" ] }, "execution_count": 31, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# For example, to calculate the particulate BOD (i.e., xBOD) of the WasteStream object,\n", "# you just need to specify the composite variable as \"BOD\", and particle size as \"x\"\n", "ws3.composite('BOD', particle_size='x')" ] }, { "cell_type": "code", "execution_count": 32, "id": "a7616fd5", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "0.0" ] }, "execution_count": 32, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Biomass COD\n", "ws3.composite('COD', specification='X_Bio')" ] }, { "cell_type": "code", "execution_count": 33, "id": "2c24ad9d", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "0.0" ] }, "execution_count": 33, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Nitrogen as nitrate/nitrite \n", "ws3.composite('N', specification='S_NOx')" ] }, { "cell_type": "code", "execution_count": 34, "id": "2281c652", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "80.03602689693881" ] }, "execution_count": 34, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Total organic carbon\n", "ws3.composite('C', organic=True)" ] }, { "cell_type": "code", "execution_count": 35, "id": "a3a2f7a2", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "473.95944660217674" ] }, "execution_count": 35, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Total dissolved solids\n", "ws3.composite('solids', particle_size='s')" ] }, { "cell_type": "code", "execution_count": 36, "id": "fdb86e29", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "13.63947291701999" ] }, "execution_count": 36, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# soluble TKN\n", "ws3.composite('N', subgroup=cmps.TKN, particle_size='s')" ] }, { "cell_type": "markdown", "id": "wssetmd", "metadata": {}, "source": [ "**Calculated vs. assigned.** The composite variables above (`COD`, `BOD`, `TN`, `TP`, ...) are computed from the component flows, so they reflect the stream's actual composition. A couple of properties are not: most importantly `pH` and alkalinity (`SAlk`). `QSDsan` does not yet model acid-base chemistry, so these simply return a default (pH 7, alkalinity 2.5 meq/L) or whatever value you assign. Treat them as inputs rather than predictions: assign `pH` directly, or pass `pH=`/`SAlk=` when you create the stream." ] }, { "cell_type": "code", "execution_count": 37, "id": "wssetcode", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "pH is now 6.5\n" ] } ], "source": [ "# pH is not calculated from composition; it just returns what you assign (default 7)\n", "ws3.pH = 6.5\n", "print('pH is now', ws3.pH)" ] }, { "cell_type": "markdown", "id": "nav-footer-3_wastestream", "metadata": {}, "source": [ "\n", "\n", "---\n", "\n", "↑ Back to top\n" ] } ], "metadata": { "kernelspec": { "display_name": ".venv", "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.12.7" }, "varInspector": { "cols": { "lenName": 16, "lenType": 16, "lenVar": 40 }, "kernels_config": { "python": { "delete_cmd_postfix": "", "delete_cmd_prefix": "del ", "library": "var_list.py", "varRefreshCmd": "print(var_dic_list())" }, "r": { "delete_cmd_postfix": ") ", "delete_cmd_prefix": "rm(", "library": "var_list.r", "varRefreshCmd": "cat(var_dic_list()) " } }, "types_to_exclude": [ "module", "function", "builtin_function_or_method", "instance", "_Feature" ], "window_display": false } }, "nbformat": 4, "nbformat_minor": 5 }