WasteStream

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

You can also watch a video demo on YouTube (subscriptions & likes appreciated!).

[1]:
import qsdsan as qs
print(f'This tutorial was made with qsdsan v{qs.__version__}.')
This tutorial was made with qsdsan v1.2.0.

1. Creating WasteStream

A WasteStream object can be created by defining flow rate of each Component (similar to creation of a Stream) or through built-in influent characterization models (e.g., by specifying total volumetric flowrate, concentrations of total COD, TKN, etc. together with COD fractions).


Note

qsdsan can work with three main stream classes: Stream, SanStream, and WasteStream. Stream is from the package thermosteam while SanStream and WasteStream are created in qsdsan. The following tutorial is focused on WasteStream as it is one of the core classes of qsdsan. You can learn more about the different classes in the documentation.

In the future, it is likely that the SanStream class will be merged into thermosteam so that LCA can be implemented for systems developed using BioSTEAM.


[2]:
qs.WasteStream?
[3]:
# Before using `WasteStream`, we need to tell qsdsan what components we will be working with
# let's load the default components for the demo purpose
cmps = qs.Components.load_default()
qs.set_thermo(cmps)
[4]:
# Just to remind ourselves what are the default components
cmps
CompiledComponents([S_H2, S_CH4, S_CH3OH, S_Ac, S_Prop, S_F, S_U_Inf, S_U_E, C_B_Subst, C_B_BAP, C_B_UAP, C_U_Inf, X_B_Subst, X_OHO_PHA, X_GAO_PHA, X_PAO_PHA, X_GAO_Gly, X_PAO_Gly, X_OHO, X_AOO, X_NOO, X_AMO, X_PAO, X_MEOLO, X_FO, X_ACO, X_HMO, X_PRO, X_U_Inf, X_U_OHO_E, X_U_PAO_E, X_Ig_ISS, X_MgCO3, X_CaCO3, X_MAP, X_HAP, X_HDP, X_FePO4, X_AlPO4, X_AlOH, X_FeOH, X_PAO_PP_Lo, X_PAO_PP_Hi, S_NH4, S_NO2, S_NO3, S_PO4, S_K, S_Ca, S_Mg, S_CO3, S_N2, S_O2, S_CAT, S_AN, H2O])

1.1. By defining component flow rate

[5]:
# You can initialize a WasteStream by setting the flow rate of the components within it,
# we usually use lower case for ID of a WasteStream
ws1 = qs.WasteStream('ws1', X_GAO_Gly=.5, H2O=1000, units='kg/hr')
ws1.show()
WasteStream: ws1
 phase: 'l', T: 298.15 K, P: 101325 Pa
 flow (g/hr): X_GAO_Gly  500
              H2O        1e+06
 WasteStream-specific properties:
  pH         : 7.0
  Alkalinity : 2.5 mg/L
  COD        : 498.4 mg/L
  BOD        : 289.0 mg/L
  TC         : 186.9 mg/L
  TOC        : 186.9 mg/L
 Component concentrations (mg/L):
  X_GAO_Gly    498.4
  H2O          996705.4
[6]:
# You can certainly use differnent units in defining and showing the stream,
# note that using the same ID will replace the original one (and you'll receive a warning like the one below)
# also note that the `ws1` in the beginning () is not the same as the "ws1" in the parentheses (actual ID),
# you can use different names, but for consistency we usually keep them as the same
ws1 = qs.WasteStream('ws1', X_GAO_Gly=1.5, H2O=100, units='kmol/hr')
ws1.show(flow='kg/hr', details=False, concentrations='g/L')
WasteStream: ws1
 phase: 'l', T: 298.15 K, P: 101325 Pa
 flow (kg/hr): X_GAO_Gly  1.5
               H2O        1.8e+03
 WasteStream-specific properties:
  pH         : 7.0
  Alkalinity : 2.5 mg/L
  ...
 Component concentrations (g/L):
  X_GAO_Gly    0.8
  H2O          996.5
/Users/yalinli_cabbi/opt/anaconda3/envs/demo/lib/python3.8/site-packages/qsdsan/_sanstream.py:59: RuntimeWarning: <WasteStream: ws1> has been replaced in registry
  super().__init__(ID=ID, flow=flow, phase=phase, T=T, P=P,
[7]:
# You can also get other information such as TDS, VSS
ws1.get_VSS()
[7]:
700.0649399604162
[8]:
# To learn more about the attribute (e.g., unit of VSS), check the documentation
ws1.get_VSS?

1.2. By specifying component concentration

[9]:
# Sometimes we might prefer concentration over flow rates
# Note that if you don't provide an ID, qsdsan will assign a default one as in "wsX"
# (X being an ascending number)
ws2 = qs.WasteStream()
ws2.set_flow_by_concentration(flow_tot=100, concentrations={'S_Ca':100}, units=('L/hr', 'mg/L'))
ws2.show()
WasteStream: ws2
 phase: 'l', T: 298.15 K, P: 101325 Pa
 flow (g/hr): S_Ca  10
              H2O   9.97e+04
 WasteStream-specific properties:
  pH         : 7.0
  Alkalinity : 2.5 mg/L
 Component concentrations (mg/L):
  S_Ca         100.0
  H2O          996909.7

1.3. By using wastewater models

[10]:
# We can default the WasteSteram to typical raw wastewater composition based on different models
qs.WasteStream.codstates_inf_model?
[11]:
ws3 = qs.WasteStream.codstates_inf_model('ws3', flow_tot=1000, pH=6.8, COD=500, TP=11)
ws3.show(N=20)
WasteStream: ws3
 phase: 'l', T: 298.15 K, P: 101325 Pa
 flow (g/hr): S_F        100
              S_U_Inf    25
              C_B_Subst  46.5
              X_B_Subst  264
              X_U_Inf    65
              X_Ig_ISS   60.8
              S_NH4      25
              S_PO4      8
              S_K        28
              S_Ca       140
              S_Mg       50
              S_CO3      120
              S_N2       18
              S_CAT      3
              S_AN       12
              H2O        9.96e+05
 WasteStream-specific properties:
  pH         : 6.8
  Alkalinity : 10.0 mg/L
  COD        : 500.0 mg/L
  BOD        : 257.9 mg/L
  TC         : 288.7 mg/L
  TOC        : 160.0 mg/L
  TN         : 40.0 mg/L
  TP         : 11.0 mg/L
  TK         : 28.0 mg/L
 Component concentrations (mg/L):
  S_F          100.0
  S_U_Inf      25.0
  C_B_Subst    46.5
  X_B_Subst    263.5
  X_U_Inf      65.0
  X_Ig_ISS     60.8
  S_NH4        25.0
  S_PO4        8.0
  S_K          28.0
  S_Ca         140.0
  S_Mg         50.0
  S_CO3        120.0
  S_N2         18.0
  S_CAT        3.0
  S_AN         12.0
  H2O          996087.7

Back to top

2. Major attributes

2.1. Retrieving flow info

[12]:
# In many cases we will want to get or the flow rates of all components at the same time,
# to do that we can use
print(f'The total mass flow rate of {ws3.ID} is {ws3.F_mass:.1f} kg/hr\n') # '\n' is just a line-breaker
print(ws3.imass['H2O']) # mass flow rate, always in kg/hr
ws3.mass # the entire array
The total mass flow rate of ws3 is 997.1 kg/hr

996.0877273822409
[12]:
property_array([0.0, 0.0, 0.0, 0.0, 0.0, 0.09999999999999999,
                0.024999999999999998, 0.0, 0.0465, 0.0, 0.0, 0.0, 0.2635,
                0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
                0.0, 0.0, 0.0, 0.0, 0.065, 0.0, 0.0, 0.060833333382000004,
                0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
                0.024999999999999998, 0.0, 0.0, 0.008,
                0.027999999999999994, 0.13999999999999999, 0.05,
                0.11999999999999998, 0.018, 0.0, 0.003, 0.012,
                996.0877273822409])
[13]:
# Similarly for molar and volumetric flow rates
print(f'The total molar flow rate of {ws3.ID} is {ws3.F_mol:.1f} kmol/hr\n')
print(ws3.imol['H2O'])
ws3.mol
The total molar flow rate of ws3 is 55.9 kmol/hr

55.29127093124508
[13]:
array([0.000e+00, 0.000e+00, 0.000e+00, 0.000e+00, 0.000e+00, 1.000e-01,
       2.500e-02, 0.000e+00, 4.650e-02, 0.000e+00, 0.000e+00, 0.000e+00,
       2.635e-01, 0.000e+00, 0.000e+00, 0.000e+00, 0.000e+00, 0.000e+00,
       0.000e+00, 0.000e+00, 0.000e+00, 0.000e+00, 0.000e+00, 0.000e+00,
       0.000e+00, 0.000e+00, 0.000e+00, 0.000e+00, 6.500e-02, 0.000e+00,
       0.000e+00, 6.083e-02, 0.000e+00, 0.000e+00, 0.000e+00, 0.000e+00,
       0.000e+00, 0.000e+00, 0.000e+00, 0.000e+00, 0.000e+00, 0.000e+00,
       0.000e+00, 1.468e-03, 0.000e+00, 0.000e+00, 4.146e-05, 7.161e-04,
       3.493e-03, 2.057e-03, 1.967e-03, 6.425e-04, 0.000e+00, 3.000e-03,
       1.200e-02, 5.529e+01])
[14]:
print(f'The total volumetric flow rate of {ws3.ID} is {ws3.F_vol:.1f} m3/hr\n')
print(ws3.ivol['H2O'])
ws3.vol
The total volumetric flow rate of ws3 is 1.0 m3/hr

0.9990601793487384
[14]:
property_array([0.0, 0.0, 0.0, 0.0, 0.0, 0.00011541180876787882,
                2.8852952191969706e-05, 0.0, 2.988425157209598e-05, 0.0,
                0.0, 0.0, 0.00016934409224187723, 0.0, 0.0, 0.0, 0.0, 0.0,
                0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
                4.177368499325245e-05, 0.0, 0.0, 3.909588470444887e-05,
                0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
                4.153439138452432e-05, 0.0, 0.0, 9.232944701430308e-06,
                3.231530645500607e-05, 0.00016157653227503034,
                5.770590438393942e-05, 0.00013849607715419416,
                5.7285049120968535e-05, 0.0, 3.4623542630363655e-06,
                1.3849417052145462e-05, 0.9990601793487384])
[15]:
# Also concentrations (in g/m3)
ws3.Conc
[15]:
property_array([0.0, 0.0, 0.0, 0.0, 0.0, 99.99999999999996,
                24.99999999999999, 0.0, 46.49999999999999, 0.0, 0.0, 0.0,
                263.49999999999994, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
                0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 64.99999999999999,
                0.0, 0.0, 60.83333338199999, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
                0.0, 0.0, 0.0, 0.0, 0.0, 24.99999999999999, 0.0, 0.0,
                7.999999999999998, 27.999999999999986, 139.99999999999994,
                49.999999999999986, 119.99999999999996,
                17.999999999999996, 0.0, 2.9999999999999996,
                11.999999999999998, 996087.7273822407])
[16]:
# And you can update the arrays as you like
print(f"Before updating, mass flow of water is {ws3.imass['H2O']}")
ws3.imass['H2O'] *= 2 # this is the same as `ws3.imass['H20'] = `ws3.imass['H20']*2`
print(f"After updating, mass flow of water is {ws3.imass['H2O']}")
Before updating, mass flow of water is 996.0877273822409
After updating, mass flow of water is 1992.1754547644819
[17]:
# This works on the entire array as well
print(f"Before updating, mass array is \n{ws3.mass}")
ws3.mass /= 2
print(f"After updating, mass array is \n{ws3.mass}")
Before updating, mass array is
[0.0 0.0 0.0 0.0 0.0 0.09999999999999999 0.024999999999999998 0.0 0.0465
 0.0 0.0 0.0 0.2635 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
 0.0 0.0 0.065 0.0 0.0 0.060833333382000004 0.0 0.0 0.0 0.0 0.0 0.0 0.0
 0.0 0.0 0.0 0.0 0.024999999999999998 0.0 0.0 0.008 0.027999999999999994
 0.13999999999999999 0.05 0.11999999999999998 0.018 0.0 0.003 0.012
 1992.1754547644819]
After updating, mass array is
[0.0 0.0 0.0 0.0 0.0 0.049999999999999996 0.012499999999999999 0.0 0.02325
 0.0 0.0 0.0 0.13175 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
 0.0 0.0 0.0325 0.0 0.0 0.030416666691000002 0.0 0.0 0.0 0.0 0.0 0.0 0.0
 0.0 0.0 0.0 0.0 0.012499999999999999 0.0 0.0 0.004 0.013999999999999997
 0.06999999999999999 0.025 0.05999999999999999 0.009 0.0 0.0015 0.006
 996.0877273822409]

2.2. Copying, mixing, and splitting

[18]:
# We can make copies of a stream
ws4 = ws1.copy(new_ID='copy_of_ws1')
ws4.show()
WasteStream: copy_of_ws1
 phase: 'l', T: 298.15 K, P: 101325 Pa
 flow (g/hr): X_GAO_Gly  1.5e+03
              H2O        1.8e+06
 WasteStream-specific properties:
  pH         : 7.0
  Alkalinity : 2.5 mg/L
  COD        : 829.7 mg/L
  BOD        : 481.1 mg/L
  TC         : 311.1 mg/L
  TOC        : 311.1 mg/L
 Component concentrations (mg/L):
  X_GAO_Gly    829.7
  H2O          996493.1
[19]:
# We can mix two streams
ws5 = qs.WasteStream()
ws5.mix_from((ws2, ws3))
ws5.show()
WasteStream: ws4
 phase: 'l', T: 298.15 K, P: 101325 Pa
 flow (g/hr): S_F        50
              S_U_Inf    12.5
              C_B_Subst  23.3
              X_B_Subst  132
              X_U_Inf    32.5
              X_Ig_ISS   30.4
              S_NH4      12.5
              S_PO4      4
              S_K        14
              S_Ca       80
              S_Mg       25
              S_CO3      60
              S_N2       9
              S_CAT      1.5
              S_AN       6
              ...
 WasteStream-specific properties:
  pH         : 6.8
  Alkalinity : 9.3 mg/L
  COD        : 227.4 mg/L
  BOD        : 117.3 mg/L
  TC         : 131.3 mg/L
  TOC        : 72.8 mg/L
  TN         : 18.2 mg/L
  TP         : 5.0 mg/L
  TK         : 12.7 mg/L
 Component concentrations (mg/L):
  S_F          45.5
  S_U_Inf      11.4
  C_B_Subst    21.1
  X_B_Subst    119.8
  X_U_Inf      29.6
  X_Ig_ISS     27.7
  S_NH4        11.4
  S_PO4        3.6
  S_K          12.7
  S_Ca         72.8
  S_Mg         22.7
  S_CO3        54.6
  S_N2         8.2
  S_CAT        1.4
  S_AN         5.5
  ...
[20]:
# Or split one stream into two, note that the split will be the fraction to the first effluent stream
ws6, ws7 = qs.WasteStream(), qs.WasteStream()
ws5.split_to(ws6, ws7, split=0.3)
[21]:
print(ws5.F_mass, ws6.F_mass, ws7.F_mass, f'diff is {ws5.F_mass-ws6.F_mass-ws7.F_mass}')
1096.271112389316 328.88133371679476 767.3897786725211 diff is 1.1368683772161603e-13
[22]:
# In splitting, you can set the split for each Component
ws8 = qs.WasteStream(X_AlOH=1, H2O=1000, units='kg/hr')
ws9, ws10 = qs.WasteStream(), qs.WasteStream()
split = cmps.kwarray({'X_AlOH':0.5, 'H2O':0.8})
ws8.split_to(ws9, ws10, split=split)
[23]:
ws9.show(flow='kg/hr')
WasteStream: ws8
 phase: 'l', T: 298.15 K, P: 101325 Pa
 flow (kg/hr): X_AlOH  0.5
               H2O     800
 WasteStream-specific properties:
  pH         : 7.0
  Alkalinity : 2.5 mg/L
 Component concentrations (mg/L):
  X_AlOH       622.9
  H2O          996625.6
[24]:
ws10.show(flow='kg/hr')
WasteStream: ws9
 phase: 'l', T: 298.15 K, P: 101325 Pa
 flow (kg/hr): X_AlOH  0.5
               H2O     200
 WasteStream-specific properties:
  pH         : 7.0
  Alkalinity : 2.5 mg/L
 Component concentrations (mg/L):
  X_AlOH       2488.6
  H2O          995430.2

2.3. composite

[25]:
# Most composite variables relevant to wastewater treatment are available as properties of the WasteStream object
# for example
ws3.COD
[25]:
250.1175328112893
[26]:
# You can also try others such as TOC, TKN, TP, ThOD
# ws3.TOC
# ws3.TKN
# ws3.TP
# ws3.ThOD
[27]:
# For composite variables related to dissolved or suspended solids, they are
# available as methods (i.e., functions specifically for the WasteStream class),
# because there are controversies around whether colloidal components should be
# considered suspened or dissolved solids (default is to NOT include colloidals).
print(f'Without colloidals, VSS for the waste stream is {ws3.get_VSS():.1f} mg/L.')
print(f'With colloidals, VSS for the stream is {ws3.get_VSS(include_colloidal=True):.1f} mg/L.')
Without colloidals, VSS for the waste stream is 91.3 mg/L.
With colloidals, VSS for the stream is 109.7 mg/L.
[28]:
# Of course, this won't matter if the waste stream does not have colloidal components
print(f'Without colloidals, VSS for the waste stream is {ws1.get_VSS():.1f} mg/L.')
print(f'With colloidals, VSS for the stream is {ws1.get_VSS(include_colloidal=True):.1f} mg/L.')
Without colloidals, VSS for the waste stream is 700.1 mg/L.
With colloidals, VSS for the stream is 700.1 mg/L.

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.

[29]:
?qs.WasteStream.composite
[30]:
# For example, to calculate the particulate BOD (i.e., xBOD) of the WasteStream object,
# you just need to specify the composite variable as "BOD", and particle size as "x"
ws3.composite('BOD', particle_size='x')
[30]:
76.45092507909868
[31]:
# Biomass COD
ws3.composite('COD', specification='X_Bio')
[31]:
0.0
[32]:
# Nitrogen as nitrate/nitrite
ws3.composite('N', specification='S_NOx')
[32]:
0.0
[33]:
# Total organic carbon
ws3.composite('C', organic=True)
[33]:
80.03761049961257
[34]:
# Total dissolved solids
ws3.composite('solids', particle_size='s')
[34]:
473.0688855855991
[35]:
# soluble TKN
ws3.composite('N', subgroup=cmps.TKN, particle_size='s')
[35]:
13.89541848951607

Back to top