WasteStream¶
Click the badge below to try this tutorial interactively in your browser:
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
WasteStreamand set its compositionRead derived flow properties and concentrations
Convert between mass-flow and concentration representations
Prerequisites: 2. Component
Covered topics:
Creating WasteStream
Major attributes
Companion video. A walkthrough of this tutorial is available on YouTube, presented by Hannah Lohman. Recorded against
QSDsanv1.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(fromthermosteam) — a plain process stream; use it when you only need mass and energy balances.SanStream— aStreamthat can additionally carry life cycle impacts. Stream-level LCA (add_indicators,get_impact,get_impacts) is covered in 8. LCA.WasteStream— aSanStreamwith 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? |
|---|---|---|---|
|
yes |
flows, temperature, pressure, phase, and wastewater properties |
no, fully independent |
|
no (the target must already exist) |
same as |
no, fully independent |
|
no |
the mass flows only (not T/P), optionally a chosen subset of components |
no, fully independent |
|
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