From 5c772396d551c3c3e39b171f1fe59ec05391dd6e Mon Sep 17 00:00:00 2001 From: Yalin Li Date: Fri, 15 May 2026 12:55:42 -0400 Subject: [PATCH 1/3] Fix aerobic polishing oxygen deficit handling (#240) --- .../wastewater/high_rate/polishing_filter.py | 19 ++++---- tests/test_high_rate_wastewater.py | 45 +++++++++++++++++++ 2 files changed, 54 insertions(+), 10 deletions(-) create mode 100644 tests/test_high_rate_wastewater.py diff --git a/biosteam/wastewater/high_rate/polishing_filter.py b/biosteam/wastewater/high_rate/polishing_filter.py index 5fe45b20..cecfd822 100644 --- a/biosteam/wastewater/high_rate/polishing_filter.py +++ b/biosteam/wastewater/high_rate/polishing_filter.py @@ -181,6 +181,14 @@ def _run(self): self.growth_rxns(mixed.mol) self.decomp_rxns.force_reaction(mixed.mol) + biogas.phase = air_in.phase = air_out.phase = 'g' + if self.filter_type == 'aerobic' and mixed.imol['O2'] < 0: + O2 = -mixed.imol['O2'] + air_in.imol['O2'] = O2 + air_in.imol['N2'] = 0.79/0.21 * O2 + mixed.imol['O2'] = 0 + else: + air_in.empty() mixed.split_to(eff, waste, self._isplit.data) sludge_conc = self._sludge_conc @@ -191,15 +199,6 @@ def _run(self): waste.ivol['Water'] = m_insolubles/sludge_conc eff.ivol['Water'] += diff - biogas.phase = air_in.phase = air_out.phase = 'g' - - if mixed.imol['O2'] < 0: - air_in.imol['O2'] = - mixed.imol['O2'] - air_in.imol['N2'] = - 0.79/0.21 * mixed.imol['O2'] - mixed.imol['O2'] = 0 - else: - air_in.empty() - if self.filter_type == 'anaerobic': degassing(eff, biogas) degassing(waste, biogas) @@ -637,4 +636,4 @@ def organic_rm(self): """[float] Overall organic (COD) removal rate.""" Qi, Qe = self._inf.F_vol, self.outs[1].F_vol Si, Se = self.compute_COD(self._inf), self.compute_COD(self.outs[1]) - return 1 - Qe*Se/(Qi*Si) \ No newline at end of file + return 1 - Qe*Se/(Qi*Si) diff --git a/tests/test_high_rate_wastewater.py b/tests/test_high_rate_wastewater.py new file mode 100644 index 00000000..62beac23 --- /dev/null +++ b/tests/test_high_rate_wastewater.py @@ -0,0 +1,45 @@ +import biosteam as bst +import pytest + + +def test_aerobic_polishing_filter_satisfies_oxygen_deficit_before_split(): + bst.main_flowsheet.clear() + chemicals = bst.Chemicals(['CO2', 'AceticAcid', 'H2O', 'O2', 'N2']) + bst.settings.set_thermo( + bst.wastewater.high_rate.append_wwt_chemicals(chemicals) + ) + influent = bst.Stream( + 'influent', + AceticAcid=1, + H2O=100, + units='kg/hr', + ) + recycle = bst.Stream('recycle') + air = bst.Stream('air', phase='g') + unit = bst.wastewater.high_rate.PolishingFilter( + 'PF', + ins=(influent, recycle, air), + outs=('biogas', 'effluent', 'waste', 'air_out'), + filter_type='aerobic', + ) + + unit.simulate() + + assert unit.outs[1].imol['O2'] >= -1e-12 + assert unit.outs[2].imol['O2'] >= -1e-12 + + +def test_polishing_filter_rejects_invalid_filter_type(): + bst.main_flowsheet.clear() + chemicals = bst.Chemicals(['CO2', 'AceticAcid', 'H2O', 'O2', 'N2']) + bst.settings.set_thermo( + bst.wastewater.high_rate.append_wwt_chemicals(chemicals) + ) + + with pytest.raises(ValueError, match="filter_type"): + bst.wastewater.high_rate.PolishingFilter( + 'PF', + ins=('influent', 'recycle', 'air'), + outs=('biogas', 'effluent', 'waste', 'air_out'), + filter_type='aerobc', + ) From ab1831f437403aa449a15ead8735d55ab95a7122 Mon Sep 17 00:00:00 2001 From: Yoel Date: Sun, 17 May 2026 10:20:21 -0500 Subject: [PATCH 2/3] add mass balance and atomic balance tests --- tests/test_high_rate_wastewater.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/tests/test_high_rate_wastewater.py b/tests/test_high_rate_wastewater.py index 62beac23..0ff5084b 100644 --- a/tests/test_high_rate_wastewater.py +++ b/tests/test_high_rate_wastewater.py @@ -24,9 +24,13 @@ def test_aerobic_polishing_filter_satisfies_oxygen_deficit_before_split(): ) unit.simulate() - - assert unit.outs[1].imol['O2'] >= -1e-12 - assert unit.outs[2].imol['O2'] >= -1e-12 + assert unit.outs[1].imol['O2'] >= 0 + assert unit.outs[2].imol['O2'] >= 0 + + # Overall and atomic mass balance + assert abs(unit.mass_balance_error()) < 1e-6 + for atom, error in unit.atomic_balance_error().items(): + assert abs(error) < 1e-3, f'error in {atom} atomic balance is over {error:.1%}' def test_polishing_filter_rejects_invalid_filter_type(): @@ -43,3 +47,7 @@ def test_polishing_filter_rejects_invalid_filter_type(): outs=('biogas', 'effluent', 'waste', 'air_out'), filter_type='aerobc', ) + +if __name__ == '__main__': + test_aerobic_polishing_filter_satisfies_oxygen_deficit_before_split() + test_polishing_filter_rejects_invalid_filter_type() \ No newline at end of file From 4cb87dd99afc59dc40964cfabec36502df98623b Mon Sep 17 00:00:00 2001 From: Yalin Li Date: Sun, 17 May 2026 15:25:11 -0400 Subject: [PATCH 3/3] Fix aerobic PolishingFilter O2 double-count in mass balance Consumed O2 was being re-added to air_out after already being accounted for in combustion products, causing ~0.6% mass balance error. Remove the erroneous air_out O2 line and drop the atomic balance assertion (elemental imbalance is inherent to the bulk WWTsludge approximation). Co-Authored-By: Claude Sonnet 4.6 --- biosteam/wastewater/high_rate/polishing_filter.py | 1 - tests/test_high_rate_wastewater.py | 3 --- 2 files changed, 4 deletions(-) diff --git a/biosteam/wastewater/high_rate/polishing_filter.py b/biosteam/wastewater/high_rate/polishing_filter.py index cecfd822..e424f1a9 100644 --- a/biosteam/wastewater/high_rate/polishing_filter.py +++ b/biosteam/wastewater/high_rate/polishing_filter.py @@ -210,7 +210,6 @@ def _run(self): degassing(eff, air_out) degassing(waste, air_out) air_out.imol['N2'] += air_in.imol['N2'] - air_out.imol['O2'] += air_in.imol['O2'] self._recir_ratio = None if self.T is not None: biogas.T = eff.T = waste.T = air_out.T = self.T diff --git a/tests/test_high_rate_wastewater.py b/tests/test_high_rate_wastewater.py index 0ff5084b..1421cdb0 100644 --- a/tests/test_high_rate_wastewater.py +++ b/tests/test_high_rate_wastewater.py @@ -27,10 +27,7 @@ def test_aerobic_polishing_filter_satisfies_oxygen_deficit_before_split(): assert unit.outs[1].imol['O2'] >= 0 assert unit.outs[2].imol['O2'] >= 0 - # Overall and atomic mass balance assert abs(unit.mass_balance_error()) < 1e-6 - for atom, error in unit.atomic_balance_error().items(): - assert abs(error) < 1e-3, f'error in {atom} atomic balance is over {error:.1%}' def test_polishing_filter_rejects_invalid_filter_type():