WasteStream

Click the badge below to try this tutorial interactively in your browser:

Launch Binder

You can also run this tutorial inGoogle Colab. It takes a one-time setup per session: follow theColab instructions.

  • Prepared by:

  • Learning objectives. After this tutorial, you will be able to:

    • Create a WasteStream and set its composition

    • Read derived flow properties and concentrations

    • Convert between mass-flow and concentration representations

  • Prerequisites: 2. Component

  • Covered topics:

      1. Creating WasteStream

      1. Major attributes

Companion video. A walkthrough of this tutorial is available on YouTube, presented by Hannah Lohman. Recorded against QSDsan v1.2.0. The concepts still apply, but if the code on screen differs from this notebook, follow the notebook.

Setup

Import QSDsan and confirm the installed version.

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

1. Creating WasteStream

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).

[2]:
# 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)
[3]:
# Just to remind ourselves what are the default components
cmps.show()
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,
])

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:

  • Stream (from thermosteam) — a plain process stream; use it when you only need mass and energy balances.

  • SanStream — a Stream that can additionally carry life cycle impacts. Stream-level LCA (add_indicators, get_impact, get_impacts) is covered in 8. LCA.

  • WasteStream — a SanStream with the wastewater-specific properties used throughout this tutorial (COD, BOD, TN, solids, …).

This tutorial focuses on WasteStream. See the stream classes documentation for the full comparison.

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:

[4]:
s  = qs.Stream('s', S_F=0.1, H2O=1000, units='kg/hr')        # thermosteam Stream
ss = qs.SanStream('ss', S_F=0.1, H2O=1000, units='kg/hr')    # adds LCA support
ws_from_s = qs.WasteStream.from_stream(s, 'ws_from_s')       # convert a Stream to a WasteStream
ws_from_s.show()
WasteStream: ws_from_s
phase: 'l', T: 298.15 K, P: 101325 Pa
flow (g/hr): S_F  100
             H2O  1e+06
 WasteStream-specific properties:
  pH         : 7.0
  COD        : 99.7 mg/L
  BOD        : 71.5 mg/L
  TC         : 31.9 mg/L
  TOC        : 31.9 mg/L
  TN         : 3.0 mg/L
  TP         : 1.0 mg/L
 Component concentrations (mg/L):
  S_F          99.7
  H2O          996949.8

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 mmol/L
  COD        : 498.4 mg/L
  BOD        : 289.0 mg/L
  TC         : 186.9 mg/L
  TOC        : 186.9 mg/L
  TSS        : 420.5 mg/L
 Component concentrations (mg/L):
  X_GAO_Gly    498.4
  H2O          996745.1
[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 mmol/L
  ...
 Component concentrations (g/L):
  X_GAO_Gly    0.8
  H2O          996.5
c:\Users\Yalin\Documents\Coding\QSDsan-platform\.venv\Lib\site-packages\thermosteam\_stream.py:407: RuntimeWarning: <WasteStream: ws1> has been replaced in registry
  self._register(ID)
[7]:
# You can also get other information such as TDS, VSS
ws1.get_VSS()
[7]:
700.0928198338145

1.2. By specifying component concentration

[8]:
# 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')
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 mmol/L
 Component concentrations (mg/L):
  S_Ca         100.0
  H2O          996949.4

1.3. By using wastewater models

[9]:
# We can default the WasteSteram to typical raw wastewater composition based on different models
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 mmol/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
  TSS        : 243.3 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          996166.9

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:

[10]:
ws3b = qs.WasteStream.bodbased_inf_model('ws3b', flow_tot=1000, BOD=250)
ws3b.show(N=8)
WasteStream: ws3b
phase: 'l', T: 298.15 K, P: 101325 Pa
flow (g/hr): S_F        87.2
             S_U_Inf    37.8
             C_B_Subst  40
             X_B_Subst  227
             X_U_Inf    39.4
             X_Ig_ISS   49.3
             S_NH4      25
             S_PO4      8
             ...        9.97e+05
 WasteStream-specific properties:
  pH         : 7.0
  Alkalinity : 10.0 mmol/L
  COD        : 431.0 mg/L
  BOD        : 250.0 mg/L
  TC         : 264.9 mg/L
  TOC        : 137.9 mg/L
  TN         : 40.0 mg/L
  TP         : 10.0 mg/L
  TK         : 28.0 mg/L
  TSS        : 197.1 mg/L
 Component concentrations (mg/L):
  S_F          87.2
  S_U_Inf      37.8
  C_B_Subst    40.0
  X_B_Subst    226.7
  X_U_Inf      39.4
  X_Ig_ISS     49.3
  S_NH4        25.0
  S_PO4        8.0
  ...

2. Major attributes

2.1. Retrieving flow info

[11]:
# In many cases we want the flow rates of all components at once,
# to do that we can use
print(f'The total mass flow rate of {ws3.ID} is {ws3.F_mass:.1f} kg/hr\n')
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.1668549779508
[11]:
sparse([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, 2.500e-02, 0.000e+00, 0.000e+00, 8.000e-03, 2.800e-02,
        1.400e-01, 5.000e-02, 1.200e-01, 1.800e-02, 0.000e+00, 3.000e-03,
        1.200e-02, 9.962e+02])
[12]:
# 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.29566318025314
[12]:
sparse([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.386e-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.530e+01])
[13]:
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.9990997329993598
[13]:
sparse([0.000e+00, 0.000e+00, 0.000e+00, 0.000e+00, 0.000e+00, 1.154e-04,
        2.885e-05, 0.000e+00, 2.988e-05, 0.000e+00, 0.000e+00, 0.000e+00,
        1.693e-04, 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, 4.177e-05, 0.000e+00,
        0.000e+00, 3.910e-05, 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, 2.885e-05, 0.000e+00, 0.000e+00, 9.233e-06, 3.232e-05,
        1.616e-04, 5.771e-05, 1.385e-04, 3.041e-05, 0.000e+00, 3.462e-06,
        1.385e-05, 9.991e-01])
[14]:
# Also concentrations (in g/m3)
ws3.Conc
[14]:
sparse([0.000e+00, 0.000e+00, 0.000e+00, 0.000e+00, 0.000e+00, 1.000e+02,
        2.500e+01, 0.000e+00, 4.650e+01, 0.000e+00, 0.000e+00, 0.000e+00,
        2.635e+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,
        0.000e+00, 0.000e+00, 0.000e+00, 0.000e+00, 6.500e+01, 0.000e+00,
        0.000e+00, 6.083e+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, 2.500e+01, 0.000e+00, 0.000e+00, 8.000e+00, 2.800e+01,
        1.400e+02, 5.000e+01, 1.200e+02, 1.800e+01, 0.000e+00, 3.000e+00,
        1.200e+01, 9.962e+05])

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:

[15]:
print(ws3.iconc['S_NH4'], 'mg/L')   # iconc is indexable by component ID
ws3.get_mass_concentration(unit='mg/L', IDs=('S_NH4', 'S_PO4'))
24.999999999999986 mg/L
[15]:
array([25.,  8.])

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.

[16]:
print(f'density  = {ws3.density:.1f} kg/m3')
print(f'dry_mass = {ws3.dry_mass:.1f} mg/L')
density  = 997.1 kg/m3
dry_mass = 1227.7 mg/L
[17]:
# 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
print(f"After updating, mass flow of water is {ws3.imass['H2O']}")
Before updating, mass flow of water is 996.1668549779508
After updating, mass flow of water is 1992.3337099559017
[18]:
# 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.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 2.500e-02 0.000e+00 0.000e+00 8.000e-03 2.800e-02 1.400e-01
 5.000e-02 1.200e-01 1.800e-02 0.000e+00 3.000e-03 1.200e-02 1.992e+03]
After updating, mass array is
[0.000e+00 0.000e+00 0.000e+00 0.000e+00 0.000e+00 5.000e-02 1.250e-02
 0.000e+00 2.325e-02 0.000e+00 0.000e+00 0.000e+00 1.318e-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
 3.250e-02 0.000e+00 0.000e+00 3.042e-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.250e-02 0.000e+00 0.000e+00 4.000e-03 1.400e-02 7.000e-02
 2.500e-02 6.000e-02 9.000e-03 0.000e+00 1.500e-03 6.000e-03 9.962e+02]

2.2. Copying, mixing, and splitting

[19]:
# 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 mmol/L
  COD        : 829.7 mg/L
  BOD        : 581.4 mg/L
  TC         : 311.2 mg/L
  TOC        : 311.2 mg/L
  TSS        : 700.1 mg/L
 Component concentrations (mg/L):
  X_GAO_Gly    829.7
  H2O          996532.8

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:

Method

New stream?

What it brings over

Linked to the original?

copy

yes

flows, temperature, pressure, phase, and wastewater properties

no, fully independent

copy_like

no (the target must already exist)

same as copy, written into the existing stream

no, fully independent

copy_flow

no

the mass flows only (not T/P), optionally a chosen subset of components

no, fully independent

proxy

yes

nothing is copied

yes, the two share their data

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.

[20]:
# `copy` -> an independent stream; editing the copy leaves the original untouched
ws_copy = ws1.copy('ws_copy')
ws_copy.imass['H2O'] *= 2
print('copy is independent:', ws1.imass['H2O'] != ws_copy.imass['H2O'])

# `proxy` -> shares data; editing the proxy also changes the original
ws_view = ws1.proxy('ws_view')
ws_view.imass['H2O'] *= 2
print('proxy is linked:   ', ws1.imass['H2O'] == ws_view.imass['H2O'])
copy is independent: True
proxy is linked:    True
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.
  warn('The property `characterization_factors` is used in biosteam/thermosteam, '
[21]:
# We can mix two streams
ws5 = qs.WasteStream('ws5')
ws5.mix_from((ws2, ws3))
ws5.show()
WasteStream: ws5
phase: 'l', T: 298.15 K, P: 101325 Pa
flow (g/hr): S_F        50
             S_U_Inf    12.5
             C_B_Subst  23.2
             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
             ...        1.1e+06
 WasteStream-specific properties:
  pH         : 6.8
  Alkalinity : 9.3 mmol/L
  COD        : 227.4 mg/L
  BOD        : 131.7 mg/L
  TC         : 131.3 mg/L
  TOC        : 72.8 mg/L
  TN         : 19.3 mg/L
  TP         : 4.7 mg/L
  TK         : 12.7 mg/L
  TSS        : 110.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
  ...
[22]:
# Or split one stream into two, note that the split will be the fraction to the first effluent stream
ws6, ws7 = qs.WasteStream('ws6'), qs.WasteStream('ws7')
ws5.split_to(ws6, ws7, split=0.3)
[23]:
print(ws5.F_mass, ws6.F_mass, ws7.F_mass, f'diff is {ws5.F_mass-ws6.F_mass-ws7.F_mass}')
1096.3542122657523 328.90626367972567 767.4479485860265 diff is 0.0
[24]:
# In splitting, you can set the split for each Component
ws8 = qs.WasteStream('ws8', X_AlOH=1, H2O=1000, units='kg/hr')
ws9, ws10 = qs.WasteStream('ws9'), qs.WasteStream('ws10')
split = cmps.kwarray({'X_AlOH':0.5, 'H2O':0.8})
ws8.split_to(ws9, ws10, split=split)
[25]:
ws9.show(flow='kg/hr')
WasteStream: ws9
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 mmol/L
  TSS        : 622.9 mg/L
 Component concentrations (mg/L):
  X_AlOH       622.9
  H2O          996665.3
[26]:
ws10.show(flow='kg/hr')
WasteStream: ws10
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 mmol/L
  TSS        : 2488.7 mg/L
 Component concentrations (mg/L):
  X_AlOH       2488.7
  H2O          995469.8

2.3. composite

[27]:
# Most composite variables relevant to wastewater treatment are available as properties of the WasteStream object
# for example
print('COD of ws3 is', ws3.COD, 'mg/L')
print('TOC of ws3 is', ws3.TOC, 'mg/L')
print('TKN of ws3 is', ws3.TKN, 'mg/L')
print('TP of ws3 is', ws3.TP, 'mg/L')
print('ThOD of ws3 is', ws3.ThOD, 'mg/L')
COD of ws3 is 250.11258405293376 mg/L
TOC of ws3 is 80.03602689693881 mg/L
TKN of ws3 is 21.22792824647371 mg/L
TP of ws3 is 5.159434503278149 mg/L
ThOD of ws3 is 338.19822319820423 mg/L
[28]:
# 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.
[29]:
# 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 350.1 mg/L.
With colloidals, VSS for the stream is 350.1 mg/L.
[30]:
# `get_VSS` has companions for the other solids fractions:
# total suspended solids (TSS), inorganic suspended solids (ISS),
# and total dissolved solids (TDS), all in mg/L
print(f'TSS = {ws3.get_TSS():.1f} mg/L')
print(f'ISS = {ws3.get_ISS():.1f} mg/L')
print(f'TDS = {ws3.get_TDS():.1f} mg/L')
TSS = 121.7 mg/L
ISS = 30.4 mg/L
TDS = 492.4 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.

[31]:
# 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')
[31]:
92.35569739334213
[32]:
# Biomass COD
ws3.composite('COD', specification='X_Bio')
[32]:
0.0
[33]:
# Nitrogen as nitrate/nitrite
ws3.composite('N', specification='S_NOx')
[33]:
0.0
[34]:
# Total organic carbon
ws3.composite('C', organic=True)
[34]:
80.03602689693881
[35]:
# Total dissolved solids
ws3.composite('solids', particle_size='s')
[35]:
473.95944660217674
[36]:
# soluble TKN
ws3.composite('N', subgroup=cmps.TKN, particle_size='s')
[36]:
13.63947291701999

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.

[37]:
# pH is not calculated from composition; it just returns what you assign (default 7)
ws3.pH = 6.5
print('pH is now', ws3.pH)
pH is now 6.5

↑ Back to top