WasteStream
¶
Prepared by:
Covered topics:
1. Creating WasteStream
2. Major attributes
Video demo:
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