Process Modeling 101#

  • Prepared by:

  • Covered topics:

    • 1. Introduction

    • 2. System Setup

    • 3. System Simulation

To run tutorials in your browser, go to this Binder page.

[1]:
import qsdsan as qs
print(f'This tutorial was made with qsdsan v{qs.__version__}.')
This tutorial was made with qsdsan v1.2.1.
[2]:
# Import packages
import numpy as np, pandas as pd
from qsdsan import sanunits as su, processes as pc, WasteStream, System
from qsdsan.utils import time_printer, load_data, get_SRT

import warnings
warnings.filterwarnings('ignore')             # to ignore Pandas future warning & NumbaPerformanceWarning

1. Introduction#

In this tutorial, we will explore how each QSDsan class is used in practical process simulation.

For this purpose, we will utilize an example system consisting of five-compartment activated sludge reactor followed by a flat-bottom circular clarifier. In addition, as a process model, Activated Sludge Model No. 2d (ASM2d) will be employed.

Example%20Wastewater%20Treatment%20Process.png

Back to top

2. System Setup#

2.1. Component#

Chemicals or biomass existing in a system can be expressed using the Component class of QSDsan.

Component.png

[3]:
# Components
cmps = pc.create_asm2d_cmps()           # create components of ASM2d
                                        # you don't need to define each component one by one.
                                        # compiled components for ASM2d are already available.

cmps.show()                             # 18 components of ASM2d + water (X_TSS was excluded due to redundancy.)
CompiledComponents([S_O2, S_N2, S_NH4, S_NO3, S_PO4, S_F, S_A, S_I, S_ALK, X_I, X_S, X_H, X_PAO, X_PP, X_PHA, X_AUT, X_MeOH, X_MeP, H2O])

S_O2: Dissolved oxygen, S_N2: Dinitrogen, S_NH4: Ammonium plus ammonia nitrogen, S_NO3: Nitrate plus nitrite nitrogen (NO3-N + NO2-N), S_PO4: Inorganic soluble phosphorus, primarily orthophosphates, S_F: Fermentable, readily biodegradable organic substrates, S_A: Fermentation products, considered to be acetate, S_I: Inert soluble organic material, S_ALK: Alkalinity of the wastewater, X_I: Inert particulate organic material, X_S: Slowly biodegradable substrates, X_H: Heterotrophic organisms, X_PAO: Phosphate-accumulating organisms, PAO, X_PP: Poly-phosphate, X_PHA: A cell internal storage product of phosphorus-accumulating organisms, PAO, X_AUT: Nitrifying organisms, X_MeOH: Metal-hydroxides, X_MeP: Metal-phosphate, MePO4

[4]:
cmps.S_A.show(chemical_info=True)              # each component stores thermodynamic properties.
Component: S_A (phase_ref='l')
[Names]  CAS: 64-19-7
         InChI: C2H4O2/c1-2(3)4/h1H3...
         InChI_key: QTBSBXVTEAMEQO-U...
         common_name: acetic acid
         iupac_name: ('ethanoic acid...
         pubchemid: 176
         smiles: CC(=O)O
         formula: C2H4O2
[Groups] Dortmund: <1CH3, 1COOH>
         UNIFAC: <1CH3, 1COOH>
         PSRK: <1CH3, 1COOH>
         NIST: <Empty>
[Data]   MW: 60.052 g/mol
         Tm: 289.85 K
         Tb: 391.05 K
         Tt: 289.69 K
         Tc: 590.7 K
         Pt: 1267.7 Pa
         Pc: 5.78e+06 Pa
         Vc: 0.000171 m^3/mol
         Hf: -4.8358e+05 J/mol
         S0: 159.8 J/K/mol
         LHV: 7.87e+05 J/mol
         HHV: 8.7502e+05 J/mol
         Hfus: 11730 J/mol
         Sfus: None
         omega: 0.4218
         dipole: 1.7 Debye
         similarity_variable: 0.13322
         iscyclic_aliphatic: 0
         combustion: {'CO2': 2, 'O2'...
Component-specific properties:
[Others] measured_as: COD
         description: Acetate
         particle_size: Soluble
         degradability: Readily
         organic: True
         i_C: 0.37535 g C/g COD
         i_N: 0 g N/g COD
         i_P: 0 g P/g COD
         i_K: 0 g K/g COD
         i_Mg: 0 g Mg/g COD
         i_Ca: 0 g Ca/g COD
         i_mass: 0.93835 g mass/g COD
         i_charge: -0.015625 mol +/g COD
         i_COD: 1 g COD/g COD
         i_NOD: 0 g NOD/g COD
         f_BOD5_COD: 0.717
         f_uBOD_COD: 0.8628
         f_Vmass_Totmass: 1
         chem_MW: 60.052

2.2. WasteStream#

Mass and energy flow within the system can be represented using the WastStream class of QSDsan.

WasteStream.png

[5]:
# Parameters (flowrates, temperature)
Q_inf = 18446                               # influent flowrate [m3/d]
Q_was = 385                                 # sludge wastage flowrate [m3/d]
Q_ext = 18446                               # external recycle flowrate [m3/d]
                                            # internal recycle flowrate will be defined later using split ratio.
                                            # effluent flowrate will be calculated as the amount remaining after recycling and wastage.

Temp = 273.15+20                            # temperature [K]
[6]:
# Create influent, effluent, recycle stream
influent = WasteStream('influent', T=Temp)                         # create an empty wastestream with specified temperature
effluent = WasteStream('effluent', T=Temp)

int_recycle = WasteStream('internal_recycle', T=Temp)
ext_recycle = WasteStream('external_recycle', T=Temp)
wastage = WasteStream('wastage', T=Temp)                           # streams between the reactors will be
                                                                   # automatically assigned when we define SanUnit.
[7]:
# Set the influent composition
default_inf_kwargs = {                                             # default influent composition
    'concentrations': {                                            # you can set concentration of each component separately.
      'S_I': 14,
      'X_I': 26.5,
      'S_F': 20.1,
      'S_A': 94.3,
      'X_S': 409.75,
      'S_NH4': 31,
      'S_N2': 0,
      'S_NO3': 0.266,
      'S_PO4': 2.8,
      'X_PP': 0.05,
      'X_PHA': 0.5,
      'X_H': 0.15,
      'X_AUT': 0,
      'X_PAO': 0,
      'S_ALK':7*12,
      },
    'units': ('m3/d', 'mg/L'),                                     # ('input total flowrate', 'input concentrations')
    }

influent.set_flow_by_concentration(Q_inf, **default_inf_kwargs)    # set flowrate and composition of empty influent WasteStream
[8]:
influent                               # wastestream stores bulk properties of the stream, as well as concentration of each component.
WasteStream: influent
 phase: 'l', T: 293.15 K, P: 101325 Pa
 flow (g/hr): S_NH4  2.38e+04
              S_NO3  204
              S_PO4  2.15e+03
              S_F    1.54e+04
              S_A    7.25e+04
              S_I    1.08e+04
              S_ALK  6.46e+04
              X_I    2.04e+04
              X_S    3.15e+05
              X_H    115
              X_PP   38.4
              X_PHA  384
              H2O    7.67e+08
 WasteStream-specific properties:
  pH         : 7.0
  Alkalinity : 2.5 mg/L
  COD        : 565.3 mg/L
  BOD        : 320.1 mg/L
  TC         : 271.4 mg/L
  TOC        : 187.4 mg/L
  TN         : 48.9 mg/L
  TP         : 7.4 mg/L
  TK         : 0.1 mg/L
 Component concentrations (mg/L):
  S_NH4   31.0
  S_NO3   0.3
  S_PO4   2.8
  S_F     20.1
  S_A     94.3
  S_I     14.0
  S_ALK   84.0
  X_I     26.5
  X_S     409.8
  X_H     0.2
  X_PP    0.1
  X_PHA   0.5
  H2O     998426.3
[9]:
influent.get_VSS()                     # you can also retreive other information, such as VSS, TSS, TDS, etc.
[9]:
324.9843750592503

2.3. Process#

Chemical or biological reactions occurring within the system can be included using the Process class of QSDsan.

2.3.1. Aeration#

Aeration%20Process.png

[10]:
# Parameters (volumes)
V_an = 1000                                 # anoxic zone tank volume [m3/d]
V_ae = 1333                                 # aerated zone tank volume [m3/d]
[11]:
# Aeration model
aer1 = pc.DiffusedAeration('aer1', DO_ID='S_O2', KLa=240, DOsat=8.0, V=V_ae)             # aeration model for Tank 3 & Tank 4
aer2 = pc.DiffusedAeration('aer2', DO_ID='S_O2', KLa=84, DOsat=8.0, V=V_ae)              # aeration model for Tank 5

DO_ID: The component ID of dissolved oxygen (DO). KLa: Oxygen mass transfer coefficient. DOsat: Surface DO saturation concentration. V: Reactor volume

[12]:
aer1
Process: aer1
[stoichiometry]      S_O2: 1
[reference]          S_O2
[rate equation]      KLa*(DOsat - S_O2)
[parameters]         KLa: 240
                     DOsat: 8
[dynamic parameters]

2.3.2. ASM2d#

ASM2d%20Process.png

[13]:
# ASM2d
asm2d = pc.ASM2d()                       # create ASM2d processes
asm2d.show()                             # 21 processes in ASM2d
ASM2d([aero_hydrolysis, anox_hydrolysis, anae_hydrolysis, hetero_growth_S_F, hetero_growth_S_A, denitri_S_F, denitri_S_A, ferment, hetero_lysis, PAO_storage_PHA, aero_storage_PP, PAO_aero_growth_PHA, PAO_lysis, PP_lysis, PHA_lysis, auto_aero_growth, auto_lysis, precipitation, redissolution, anox_storage_PP, PAO_anox_growth])
[14]:
asm2d.aero_hydrolysis                    # Each process includes stoichiometry, rate equation, and parameters.
Process: aero_hydrolysis
[stoichiometry]      S_NH4: 0.02*f_SI + 0.01
                     S_PO4: 0.01*f_SI
                     S_F: 1.0 - 1.0*f_SI
                     S_I: 1.0*f_SI
                     S_ALK: 0.0113*f_SI + 0.00858
                     X_S: -1.00
[reference]          X_S
[rate equation]      K_h*S_O2*X_S/((K_O2 + S_O2)*...
[parameters]         f_SI: 0
                     Y_H: 0.625
                     f_XI_H: 0.1
                     Y_PAO: 0.625
                     Y_PO4: 0.4
                     Y_PHA: 0.2
                     f_XI_PAO: 0.1
                     Y_A: 0.24
                     f_XI_AUT: 0.1
                     K_h: 3
                     eta_NO3: 0.6
                     eta_fe: 0.4
                     K_O2: 0.2
                     K_NO3: 0.5
                     K_X: 0.1
                     mu_H: 6
                     q_fe: 3
                     eta_NO3_H: 0.8
                     b_H: 0.4
                     K_O2_H: 0.2
                     K_F: 4
                     K_fe: 4
                     K_A_H: 4
                     K_NO3_H: 0.5
                     K_NH4_H: 0.05
                     K_P_H: 0.01
                     K_ALK_H: 1.2
                     q_PHA: 3
                     q_PP: 1.5
                     mu_PAO: 1
                     eta_NO3_PAO: 0.6
                     b_PAO: 0.2
                     b_PP: 0.2
                     b_PHA: 0.2
                     K_O2_PAO: 0.2
                     K_NO3_PAO: 0.5
                     K_A_PAO: 4
                     K_NH4_PAO: 0.05
                     K_PS: 0.2
                      K_P_PAO: K_P_PAO
                     K_ALK_PAO: 1.2
                     K_PP: 0.01
                     K_MAX: 0.34
                     K_IPP: 0.02
                     K_PHA: 0.01
                     mu_AUT: 1
                     b_AUT: 0.15
                     K_O2_AUT: 0.5
                     K_NH4_AUT: 1
                     K_ALK_AUT: 6
                     K_P_AUT: 0.01
                     k_PRE: 1
                     k_RED: 0.6
                     K_ALK_PRE: 6
                     K_P_PAO: 0.01
[dynamic parameters]
[15]:
# Petersen stoichiometric matrix of ASM2d
pd.set_option('display.max_columns', None)                  # to display all columns

asm2d.stoichiometry
[15]:
S_O2 S_N2 S_NH4 S_NO3 S_PO4 S_F S_A S_I S_ALK X_I X_S X_H X_PAO X_PP X_PHA X_AUT X_MeOH X_MeP H2O
aero_hydrolysis 0 0 0.01 0 0 1 0 0 0.00858 0 -1 0 0 0 0 0 0 0 0
anox_hydrolysis 0 0 0.01 0 0 1 0 0 0.00858 0 -1 0 0 0 0 0 0 0 0
anae_hydrolysis 0 0 0.01 0 0 1 0 0 0.00858 0 -1 0 0 0 0 0 0 0 0
hetero_growth_S_F -0.6 0 -0.022 0 -0.004 -1.6 0 0 -0.0165 0 0 1 0 0 0 0 0 0 0
hetero_growth_S_A -0.6 0 -0.07 0 -0.02 0 -1.6 0 0.252 0 0 1 0 0 0 0 0 0 0
denitri_S_F 0 0.21 -0.022 -0.21 -0.004 -1.6 0 0 0.164 0 0 1 0 0 0 0 0 0 0
denitri_S_A 0 0.21 -0.07 -0.21 -0.02 0 -1.6 0 0.432 0 0 1 0 0 0 0 0 0 0
ferment 0 0 0.03 0 0.01 -1 1 0 -0.168 0 0 0 0 0 0 0 0 0 0
hetero_lysis 0 0 0.032 0 0.01 0 0 0 0.0216 0.1 0.9 -1 0 0 0 0 0 0 0
PAO_storage_PHA 0 0 0 0 0.4 0 -1 0 0.11 0 0 0 0 -0.4 1 0 0 0 0
aero_storage_PP -0.2 0 0 0 -1 0 0 0 0.194 0 0 0 0 1 -0.2 0 0 0 0
PAO_aero_growth_PHA -0.6 0 -0.07 0 -0.02 0 0 0 -0.0484 0 0 0 1 0 -1.6 0 0 0 0
PAO_lysis 0 0 0.032 0 0.01 0 0 0 0.0216 0.1 0.9 0 -1 0 0 0 0 0 0
PP_lysis 0 0 0 0 1 0 0 0 -0.194 0 0 0 0 -1 0 0 0 0 0
PHA_lysis 0 0 0 0 0 0 1 0 -0.188 0 0 0 0 0 -1 0 0 0 0
auto_aero_growth -18 0 -4.24 4.17 -0.02 0 0 0 -7.2 0 0 0 0 0 0 1 0 0 0
auto_lysis 0 0 0.032 0 0.01 0 0 0 0.0216 0.1 0.9 0 0 0 0 -1 0 0 0
precipitation 0 0 0 0 -1 0 0 0 0.582 0 0 0 0 0 0 0 -3.45 4.87 0
redissolution 0 0 0 0 1 0 0 0 -0.582 0 0 0 0 0 0 0 3.45 -4.87 0
anox_storage_PP 0 0.07 0 -0.07 -1 0 0 0 0.254 0 0 0 0 1 -0.2 0 0 0 0
PAO_anox_growth 0 0.208 -0.0683 -0.21 -0.02 0 0 0 0.133 0 0 0 1 0 -1.6 0 0 0 0
[16]:
# Rate equations of ASM2d
asm2d.rate_equations
[16]:
rate_equation
aero_hydrolysis 3.0*S_O2*X_S/((0.1 + X_S/X_H)*(...
anox_hydrolysis 0.36*S_NO3*X_S/((0.1 + X_S/X_H)...
anae_hydrolysis 0.12*X_S/((0.1 + X_S/X_H)*(S_NO...
hetero_growth_S_F 6.0*S_ALK*S_F**2*S_NH4*S_O2*S_P...
hetero_growth_S_A 6.0*S_A**2*S_ALK*S_NH4*S_O2*S_P...
denitri_S_F 0.96*S_ALK*S_F**2*S_NH4*S_NO3*S...
denitri_S_A 0.96*S_A**2*S_ALK*S_NH4*S_NO3*S...
ferment 0.3*S_ALK*S_F*X_H/((S_ALK + 1.2...
hetero_lysis 0.4*X_H
PAO_storage_PHA 3.0*S_A*S_ALK*X_PP/((0.01 + X_P...
aero_storage_PP 1.5*S_ALK*S_O2*S_PO4*X_PHA*(0.3...
PAO_aero_growth_PHA 1.0*S_ALK*S_NH4*S_O2*S_PO4*X_PH...
PAO_lysis 0.2*S_ALK*X_PAO/(S_ALK + 1.2)
PP_lysis 0.2*S_ALK*X_PP/(S_ALK + 1.2)
PHA_lysis 0.2*S_ALK*X_PHA/(S_ALK + 1.2)
auto_aero_growth 1.0*S_ALK*S_NH4*S_O2*S_PO4*X_AU...
auto_lysis 0.15*X_AUT
precipitation 1.0*S_PO4*X_MeOH
redissolution 0.6*S_ALK*X_MeP/(S_ALK + 6.0)
anox_storage_PP 0.18*S_ALK*S_NO3*S_PO4*X_PHA*(0...
PAO_anox_growth 0.12*S_ALK*S_NH4*S_NO3*S_PO4*X_...

2.4. SanUnit#

Reactors constituting the system can be represented using the SanUnit class of QSDsan.

SanUnit.png

[17]:
# Anoxic reactors (Tank 1 & Tank 2)
A1 = su.CSTR('A1', ins=[influent, int_recycle, ext_recycle], V_max=V_an,      # As CSTR, 3 input streams, tank volume as V_an
             aeration=None, suspended_growth_model=asm2d)                     # No aeration, biokinetic model as asm2d

A2 = su.CSTR('A2', ins=A1-0, V_max=V_an,                 # ins=A1-0: set influent of A2 as effluent of A1 reactor (to connect A1 with A2)
             aeration=None, suspended_growth_model=asm2d)

ins: Influents to the reactor. outs: Treated effluent from the reactor. V_max: Designed volume, in [m^3]. The default is 1000. aeration: Aeration setting. Either specify a targeted dissolved oxygen concentration in [mg O2/L] or provide a :class:Process object to represent aeration, or None for no aeration. The default is 2.0. suspended_growth_model: The suspended growth biokinetic model. The default is None.

[18]:
A1                                        # Before simulation, outs are empty.
../_images/tutorials_13_Process_Modeling_101_41_0.svg
CSTR: A1
ins...
[0] influent
    phase: 'l', T: 293.15 K, P: 101325 Pa
    flow (g/hr): S_NH4  2.38e+04
                 S_NO3  204
                 S_PO4  2.15e+03
                 S_F    1.54e+04
                 S_A    7.25e+04
                 S_I    1.08e+04
                 S_ALK  6.46e+04
                 X_I    2.04e+04
                 X_S    3.15e+05
                 X_H    115
                 X_PP   38.4
                 X_PHA  384
                 H2O    7.67e+08
    WasteStream-specific properties:
     pH         : 7.0
     COD        : 565.3 mg/L
     BOD        : 320.1 mg/L
     TC         : 271.4 mg/L
     TOC        : 187.4 mg/L
     TN         : 48.9 mg/L
     TP         : 7.4 mg/L
     TK         : 0.1 mg/L
[1] internal_recycle
    phase: 'l', T: 293.15 K, P: 101325 Pa
    flow: 0
    WasteStream-specific properties: None for empty waste streams
[2] external_recycle
    phase: 'l', T: 293.15 K, P: 101325 Pa
    flow: 0
    WasteStream-specific properties: None for empty waste streams
outs...
[0] ws1  to  CSTR-A2
    phase: 'l', T: 298.15 K, P: 101325 Pa
    flow: 0
    WasteStream-specific properties: None for empty waste streams
[19]:
# Aerated reactors (Tank 3, Tank 4, Tank 5)
O1 = su.CSTR('O1', ins=A2-0, V_max=V_ae, aeration=aer1,                          # tank volume as V_ae with aeration model1
             DO_ID='S_O2', suspended_growth_model=asm2d)

O2 = su.CSTR('O2', ins=O1-0, V_max=V_ae, aeration=aer1,
             DO_ID='S_O2', suspended_growth_model=asm2d)

O3 = su.CSTR('O3', ins=O2-0, outs=[int_recycle, 'treated'], split=[0.6, 0.4],    # 60% of efflunet to internal recycle, 40% to clarifier
             V_max=V_ae, aeration=aer2,
             DO_ID='S_O2', suspended_growth_model=asm2d)
[20]:
O3
../_images/tutorials_13_Process_Modeling_101_43_0.svg
CSTR: O3
ins...
[0] ws7  from  CSTR-O2
    phase: 'l', T: 298.15 K, P: 101325 Pa
    flow: 0
    WasteStream-specific properties: None for empty waste streams
outs...
[0] internal_recycle  to  CSTR-A1
    phase: 'l', T: 293.15 K, P: 101325 Pa
    flow: 0
    WasteStream-specific properties: None for empty waste streams
[1] treated
    phase: 'l', T: 298.15 K, P: 101325 Pa
    flow: 0
    WasteStream-specific properties: None for empty waste streams
[21]:
# Clarifier
C1 = su.FlatBottomCircularClarifier('C1', ins=O3-1, outs=[effluent, ext_recycle, wastage],  # O3-1: second effluent of O3, three outs
                                    underflow=Q_ext, wastage=Q_was, surface_area=1500,
                                    height=4, N_layer=10, feed_layer=5,                     # modeled as a 10 layer non-reactive unit
                                    X_threshold=3000, v_max=474, v_max_practical=250,
                                    rh=5.76e-4, rp=2.86e-3, fns=2.28e-3)

underflow: Designed recycling sludge flowrate (RAS), in [m^3/d]. The default is 2000. wastage: Designed wasted sludge flowrate (WAS), in [m^3/d]. The default is 385. surface_area: Surface area of the clarifier, in [m^2]. The default is 1500. height: Height of the clarifier, in [m]. The default is 4. N_layer: The number of layers to model settling. The default is 10. feed_layer: The feed layer counting from top to bottom. The default is 4. X_threshold: Threshold suspended solid concentration, in [g/m^3]. The default is 3000. v_max: Maximum theoretical (i.e. Vesilind) settling velocity, in [m/d]. The default is 474. v_max_practical: Maximum practical settling velocity, in [m/d]. The default is 250. rh: Hindered zone settling parameter in the double-exponential settling velocity function, in [m^3/g]. The default is 5.76e-4. rp: Flocculant zone settling parameter in the double-exponential settling velocity function, in [m^3/g]. The default is 2.86e-3. fns: Non-settleable fraction of the suspended solids, dimensionless. Must be within [0, 1]. The default is 2.28e-3.

2.5. System#

System objects are used to organize unit operations in a certain order and facilitate mass and energy convergence, techno-economic analysis (TEA), and life cycle assessment (LCA).

System.png

2.5.1. Create system#

[22]:
# Create system
sys = System('example_system', path=(A1, A2, O1, O2, O3, C1), recycle=(int_recycle, ext_recycle))     # path: the order of reactor
[23]:
# System diagram
sys.diagram()
../_images/tutorials_13_Process_Modeling_101_51_0.svg
[24]:
sys                                                       # before running the simulation, 'outs' have nothing
../_images/tutorials_13_Process_Modeling_101_52_0.svg
System: example_system
ins...
[0] influent
    phase: 'l', T: 293.15 K, P: 101325 Pa
    flow (kmol/hr): S_NH4  1.4
                    S_NO3  0.0033
                    S_PO4  0.0112
                    S_F    15.4
                    S_A    1.21
                    S_I    10.8
                    S_ALK  1.06
                    ...    4.29e+04
outs...
[0] effluent
    phase: 'l', T: 293.15 K, P: 101325 Pa
    flow: 0
[1] wastage
    phase: 'l', T: 293.15 K, P: 101325 Pa
    flow: 0

2.5.2. Set initial conditions of reactors#

[25]:
# Import initial condition excel file
df = load_data('assets/tutorial_13/initial_conditions_asm2d.xlsx', sheet='default')
[26]:
df                                                 # Unlike other reactors, C1 has 3 rows for each of soluble, solids, and tss.
[26]:
S_O2 S_NH4 S_NO3 S_PO4 S_F S_A S_I S_ALK X_I X_S X_H X_PAO X_PP X_PHA X_AUT
A1 0.00213 7.23 10.2 4.45 0.211 0.0265 15.9 67 2.28e+03 84.4 3.78e+03 322 37.2 0.0517 218
A2 0.001 22.4 2.4 4.24 6.68 53.8 14.5 79 0 84.1 207 18.2 4.25 3.59 11.9
O2 2 16.5 4.31 5.48 1.9 2.73 13.7 82.6 611 77.3 1.04e+03 86.4 6.45 11 58
O3 2 10.9 9.31 2.62 0.649 0.163 14.1 74.2 662 59.3 1.14e+03 95.7 9.99 7.24 64
O1 2 0.111 26.1 2.32 0.276 0.00407 18.2 46.1 2.24e+03 61.1 3.79e+03 322 38.4 0.00852 218
C1_s 2 0.114 20.9 0.356 0.307 0.00537 20.1 49.6 NaN NaN NaN NaN NaN NaN NaN
C1_x NaN NaN NaN NaN NaN NaN NaN NaN 2.24e+03 61.1 3.79e+03 322 38.4 0.00852 218
C1_tss 17.8 27.9 44.9 90.5 305 304 306 304 304 5.83e+03 NaN NaN NaN NaN NaN
[27]:
# Create a function to set initial conditions of the reactors
def batch_init(sys, df):
    dct = df.to_dict('index')                                         # convert the DataFrame to a dictionary.
    u = sys.flowsheet.unit                                            # unit registry (A1, A2, O1, O2, O3, C1)

    for k in [u.A1, u.A2, u.O1, u.O2, u.O3]:                          # for A1, A2, O1, O2, O3 reactor,
        k.set_init_conc(**dct[k._ID])                                 # A1.set_init_conc(**dct[k_ID])

    c1s = {k:v for k,v in dct['C1_s'].items() if v>0}                # for clarifier, need to use different methods
    c1x = {k:v for k,v in dct['C1_x'].items() if v>0}
    tss = [v for v in dct['C1_tss'].values() if v>0]
    u.C1.set_init_solubles(**c1s)                                     # set solubles
    u.C1.set_init_sludge_solids(**c1x)                                # set sludge solids
    u.C1.set_init_TSS(tss)                                            # set TSS
[28]:
batch_init(sys, df)

Back to top

3. System Simulation#

3.1. Run simulation#

[29]:
# Simulation settings
sys.set_dynamic_tracker(influent, effluent, A1, A2, O1, O2, O3, C1, wastage)           # what you want to track changes in concentration
sys.set_tolerance(rmol=1e-6)

biomass_IDs = ('X_H', 'X_PAO', 'X_AUT')
[30]:
# Simulation settings
t = 50                          # total time for simulation
t_step = 1                      # times at which to store the computed solution

method = 'BDF'                  # integration method to use
# method = 'RK45'
# method = 'RK23'
# method = 'DOP853'
# method = 'Radau'
# method = 'LSODA'

# https://docs.scipy.org/doc/scipy/reference/generated/scipy.integrate.solve_ivp.html
[31]:
# Run simulation, this could take several minuates
sys.simulate(state_reset_hook='reset_cache',
             t_span=(0,t),
             t_eval=np.arange(0, t+t_step, t_step),
             method=method,
            # export_state_to=f'sol_{t}d_{method}.xlsx',               # uncomment to export simulation result as excel file
            )
[32]:
srt = get_SRT(sys, biomass_IDs)
print(f'Estimated SRT assuming at steady state is {round(srt, 2)} days')
Estimated SRT assuming at steady state is 10.02 days
[33]:
sys                                                                      # now you have 'outs' info.
../_images/tutorials_13_Process_Modeling_101_65_0.svg
System: example_system
Highest convergence error among components in recycle
streams {C1-1, O3-0} after 5 loops:
- flow rate   1.17e-06 kmol/hr (9.1e-10%)
- temperature 2.63e-08 K (9e-09%)
ins...
[0] influent
    phase: 'l', T: 293.15 K, P: 101325 Pa
    flow (kmol/hr): S_NH4  1.4
                    S_NO3  0.0033
                    S_PO4  0.0112
                    S_F    15.4
                    S_A    1.21
                    S_I    10.8
                    S_ALK  1.06
                    ...    4.29e+04
outs...
[0] effluent
    phase: 'l', T: 293.15 K, P: 101325 Pa
    flow (kmol/hr): S_O2   0.000506
                    S_N2   0.00716
                    S_NH4  1.64
                    S_NO3  2.21e-09
                    S_PO4  0.015
                    S_F    0.973
                    S_A    0.111
                    ...    4.17e+04
[1] wastage
    phase: 'l', T: 293.15 K, P: 101325 Pa
    flow (kmol/hr): S_O2   1.08e-05
                    S_N2   0.000153
                    S_NH4  0.0349
                    S_NO3  4.72e-11
                    S_PO4  0.000321
                    S_F    0.0207
                    S_A    0.00237
                    ...    1.04e+03

3.2. Check simulation results#

[34]:
# Influent
influent.scope.plot_time_series(('S_I','X_I','S_F','S_A','X_S','S_NH4','S_N2','S_NO3','S_PO4','X_PP','X_PHA',
                                 'X_H','X_AUT','X_PAO','S_ALK'))      # you can plot how each state variable changes over time

#default_inf_kwargs = {
#    'concentrations': {
#      'S_I': 14,
#      'X_I': 26.5,
#      'S_F': 20.1,
#      'S_A': 94.3,
#      'X_S': 409.75,
#      'S_NH4': 31,
#      'S_N2': 0,
#      'S_NO3': 0.266,
#      'S_PO4': 2.8,
#      'X_PP': 0.05,
#      'X_PHA': 0.5,
#      'X_H': 0.15,
#      'X_AUT': 0,
#      'X_PAO': 0,
#      'S_ALK':7*12,
#      },
#    'units': ('m3/d', 'mg/L'),
#    }                                                               # constant influent setting
[34]:
(<Figure size 800x450 with 1 Axes>,
 <AxesSubplot:xlabel='Time [d]', ylabel='Concentration [mg/L]'>)
../_images/tutorials_13_Process_Modeling_101_67_1.png
[35]:
# Effluent
effluent.scope.plot_time_series((('S_I','X_I','S_F','S_A','X_S','S_NH4','S_N2','S_NO3','S_PO4','X_PP','X_PHA',
                                 'X_H','X_AUT','X_PAO','S_ALK')))                   # you can plot how each state variable changes over time
[35]:
(<Figure size 800x450 with 1 Axes>,
 <AxesSubplot:xlabel='Time [d]', ylabel='Concentration [mg/L]'>)
../_images/tutorials_13_Process_Modeling_101_68_1.png
[36]:
effluent.scope.plot_time_series(('S_NH4', 'S_NO3'))  # you can plot how each state variable changes over time
[36]:
(<Figure size 800x450 with 1 Axes>,
 <AxesSubplot:xlabel='Time [d]', ylabel='Concentration [mg/L]'>)
../_images/tutorials_13_Process_Modeling_101_69_1.png
[37]:
effluent.scope.plot_time_series(('S_O2'))  # you can plot how each state variable changes over time
[37]:
(<Figure size 800x450 with 1 Axes>,
 <AxesSubplot:xlabel='Time [d]', ylabel='Concentration [mg/L]'>)
../_images/tutorials_13_Process_Modeling_101_70_1.png

Back to top