Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add influent/effluent quality metrics for ASM1 #1243

Merged
merged 8 commits into from
Dec 12, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 87 additions & 0 deletions watertap/property_models/activated_sludge/asm1_properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,40 @@ def build(self):
doc="Reference temperature",
units=pyo.units.K,
)
self.f_p = pyo.Var(
initialize=0.08,
units=pyo.units.dimensionless,
domain=pyo.PositiveReals,
doc="Fraction of biomass yielding particulate products, f_p",
)
self.i_xb = pyo.Var(
initialize=0.08,
units=pyo.units.dimensionless,
domain=pyo.PositiveReals,
doc="Mass fraction of N per COD in biomass, i_xb",
)
self.i_xp = pyo.Var(
initialize=0.06,
units=pyo.units.dimensionless,
domain=pyo.PositiveReals,
doc="Mass fraction of N per COD in particulates, i_xp",
)
self.COD_to_SS = pyo.Var(
initialize=0.75,
units=pyo.units.dimensionless,
domain=pyo.PositiveReals,
doc="Conversion factor applied for TSS calculation",
)
self.BOD5_factor = pyo.Var(
["raw", "effluent"],
initialize={"raw": 0.65, "effluent": 0.25},
units=pyo.units.dimensionless,
domain=pyo.PositiveReals,
doc="Conversion factor for BOD5",
)
# Fix Vars that are treated as Params
for v in self.component_objects(pyo.Var):
v.fix()

@classmethod
def define_metadata(cls, obj):
Expand All @@ -139,6 +173,11 @@ def define_metadata(cls, obj):
obj.define_custom_properties(
{
"alkalinity": {"method": None},
"TSS": {"method": "_TSS"},
"BOD5": {"method": "_BOD5"},
"TKN": {"method": "_TKN"},
"Total_N": {"method": "_Total_N"},
"COD": {"method": "_COD"},
}
)
obj.add_default_units(
Expand Down Expand Up @@ -356,6 +395,54 @@ def energy_density_expression(self):
rule=energy_density_expression, doc="Energy density term"
)

def _TSS(self):
tss = self.conc_mass_comp["X_S"] + self.conc_mass_comp["X_I"] + self.conc_mass_comp["X_BH"] + self.conc_mass_comp["X_BA"] + self.conc_mass_comp["X_P"]
return self.params.COD_to_SS * tss

self.TSS = pyo.Expression(
rule=_TSS,
doc="Total suspended solids (TSS)",
)

def _BOD5(self, i):
bod5 = self.conc_mass_comp["X_S"] + self.conc_mass_comp["X_S"] + (1-self.params.f_p)*(self.conc_mass_comp["X_BH"] + self.conc_mass_comp["X_BA"])
#TODO: 0.25 should be a parameter instead as it changes by influent/effluent
return self.params.BOD5_factor[i]*bod5

self.BOD5 = pyo.Expression(
["raw", "effluent"],
rule=_BOD5,
Comment on lines +423 to +425
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe I'm just not understanding, but is there a reason these two are bundled together as opposed to having a separate expression for each?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only difference between the two is that for treated effluent, the whole expression should be multiplied by one value (0.25), while for another stateblock in the flowsheet (e.g., influent, stream bypassing activated sludge process, etc.), the expression is multiplied by another value (0.65). I could've made them separate but chose to do it this way. Note the BOD5_factor that I added to the parameter block is also indexed by "raw" or "effluent". (This is the factor that is 0.65 for "raw" and 0.25 for "effluent".)

doc="Five-day Biological Oxygen Demand (BOD5)",
)

def _COD(self):
cod = self.conc_mass_comp["S_S"] + self.conc_mass_comp["S_I"] + self.conc_mass_comp["X_S"] + self.conc_mass_comp["X_S"] + self.conc_mass_comp["X_I"] + self.conc_mass_comp["X_BH"] + self.conc_mass_comp["X_BA"]+ self.conc_mass_comp["X_P"]
return cod

self.COD = pyo.Expression(
rule=_COD,
doc="Chemical Oxygen Demand",
)

def _TKN(self):
tkn = self.conc_mass_comp["S_NH"] + self.conc_mass_comp["S_ND"] + self.conc_mass_comp["X_ND"] + self.params.i_xb * (self.conc_mass_comp["X_BH"] + self.conc_mass_comp["X_BA"]) + self.params.i_xp * (self.conc_mass_comp["X_P"] + self.conc_mass_comp["X_I"])
return tkn

self.TKN = pyo.Expression(
rule=_TKN,
doc="Total Kjeldahl Nitrogen",
)

def _Total_N(self):
totaln = self.TKN + self.conc_mass_comp["S_NO"]
return totaln

self.Total_N = pyo.Expression(
rule=_Total_N,
doc="Total Nitrogen",
)


iscale.set_scaling_factor(self.flow_vol, 1e1)
iscale.set_scaling_factor(self.temperature, 1e-1)
iscale.set_scaling_factor(self.pressure, 1e-6)
Expand Down
24 changes: 5 additions & 19 deletions watertap/property_models/activated_sludge/asm1_reactions.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@


# Some more information about this module
__author__ = "Andrew Lee, Xinhong Liu"
__author__ = "Andrew Lee, Xinhong Liu, Adam Atia"


# Set up logger
Expand Down Expand Up @@ -87,25 +87,11 @@ def build(self):
domain=pyo.PositiveReals,
doc="Yield of cell COD formed per g COD oxidized, Y_H",
)
self.f_p = pyo.Var(
initialize=0.08,
units=pyo.units.dimensionless,
domain=pyo.PositiveReals,
doc="Fraction of biomass yielding particulate products, f_p",
)
self.i_xb = pyo.Var(
initialize=0.08,
units=pyo.units.dimensionless,
domain=pyo.PositiveReals,
doc="Mass fraction of N per COD in biomass, i_xb",
)
self.i_xp = pyo.Var(
initialize=0.06,
units=pyo.units.dimensionless,
domain=pyo.PositiveReals,
doc="Mass fraction of N per COD in particulates, i_xp",
)

add_object_reference(self, "f_p", self.config.property_package.f_p)
add_object_reference(self, "i_xb", self.config.property_package.i_xb)
add_object_reference(self, "i_xp", self.config.property_package.i_xp)

# Kinetic Parameters
self.mu_A = pyo.Var(
initialize=0.5,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
#################################################################################
"""
Tests for ASM1 thermo property package.
Authors: Andrew Lee
Authors: Andrew Lee, Adam Atia
"""

import pytest
Expand Down Expand Up @@ -161,6 +161,18 @@ def test_build(self, model):
"X_ND",
]
assert value(model.props[1].conc_mass_comp[i]) == 0.1

assert isinstance(model.props[1].params.f_p, Var)
assert value(model.props[1].params.f_p) == 0.08
assert isinstance(model.props[1].params.i_xb, Var)
assert value(model.props[1].params.i_xb) == 0.08
assert isinstance(model.props[1].params.i_xp, Var)
assert value(model.props[1].params.i_xp) == 0.06
assert isinstance(model.props[1].params.COD_to_SS, Var)
assert value(model.props[1].params.COD_to_SS) == 0.75
assert isinstance(model.props[1].params.BOD5_factor, Var)
assert value(model.props[1].params.BOD5_factor["raw"]) == 0.65
assert value(model.props[1].params.BOD5_factor["effluent"]) == 0.25

assert isinstance(model.props[1].material_flow_expression, Expression)
for j in model.params.component_list:
Expand Down Expand Up @@ -320,3 +332,12 @@ def test_initialize(self, model):
@pytest.mark.unit
def check_units(self, model):
assert_units_consistent(model)

@pytest.mark.unit
def test_expressions(self, model):
assert value(model.props[1].TSS) == 0.375
assert value(model.props[1].COD) == pytest.approx(0.7999, rel=1e-3)
assert value(model.props[1].BOD5["effluent"]) == 0.096
assert value(model.props[1].BOD5["raw"]) == 0.096 *0.65/0.25
assert value(model.props[1].TKN) == pytest.approx(0.328, rel=1e-3)
assert value(model.props[1].Total_N) == pytest.approx(0.428, rel=1e-3)
Loading