From e65d54c0a96cd9edc585782463c0b7e9070e65a4 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Mon, 26 Sep 2022 17:12:09 -0500 Subject: [PATCH 001/424] Added a new class, Primary Clarifier Modelled on BSM2 --- qsdsan/sanunits/_clarifier.py | 77 +++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index e955d527..283b21af 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -533,5 +533,82 @@ def _run(self): eff.set_flow_by_concentration(Q_in-Qs, Ce, units=('m3/d', 'mg/L')) sludge.set_flow_by_concentration(Qs, Cs, units=('m3/d', 'mg/L')) + def _design(self): + pass + +class PrimaryClarifier(SanUnit): + + _N_ins = 3 + _N_outs = 2 + + def __init__(self, ID='', ins=None, outs=(), thermo=None, + isdynamic=False, init_with='WasteStream', Hydraulic_Retention_Time = 8, ratio_of_uf = 2, + f_corr = 1, F_BM_default=None, **kwargs): + + SanUnit.__init__(self, ID, ins, outs, thermo, isdynamic=isdynamic, + init_with=init_with, F_BM_default=F_BM_default) + self.Hydraulic_Retention_Time = Hydraulic_Retention_Time + self.ratio_of_uf = ratio_of_uf + self.f_corr = f_corr + + @property + def Hydraulic_Retention_Time(self): + '''The Hydraulic Retention time in days.''' + return self._HRT + + @Hydraulic_Retention_Time.setter + def Hydraulic_Retention_Time(self, HRT): + if HRT is not None: + self._HRT = HRT + else: + raise ValueError('HRT expected from user') + + @property + def ratio_of_uf(self): + return self._r + + @ratio_of_uf.setter + def ratio_of_uf(self, r): + if r is not None: + self._r = r + else: + raise ValueError('Effluent to Sludge ratio expected from user') + + @property + def f_corr(self): + return self._corr + + @f_corr.setter + def f_corr(self, corr): + if corr is not None: + if corr > 1 or corr < 0: + raise ValueError(f'correction factor must be within [0, 1], not {corr}') + self._corr = corr + + def _run(self): + q_inf, to, do = self.ins + uf, of = self.outs + cmps = self.components + Q_in = self.ins.get_total_flow('m3/d') + TSS_in = (self.ins.conc*cmps.x*cmps.i_mass).sum() + params = (HRT, r, corr) = self._HRT, self._r, self._corr + if (i is None for i in params): + raise RuntimeError('must specify HRT, ratio of effluent to sludge, and correction factor') + + f_i = 1 - (n_COD/100) + + + + Qs = Q_in*TSS_in*f_i/(self._MLSS-TSS_in) + + + Zs = Ze = self.ins.conc * (1-cmps.x) + Ce = dict(zip(cmps.IDs, Ze+Xe)) + Cs = dict(zip(cmps.IDs, Zs+Xs)) + Ce.pop('H2O', None) + Cs.pop('H2O', None) + of.set_flow_by_concentration(Q_in-Qs, Ce, units=('m3/d', 'mg/L')) + uf.set_flow_by_concentration(Qs, Cs, units=('m3/d', 'mg/L')) + def _design(self): pass \ No newline at end of file From b93f211127400b9ab0217f3888f3e6c77b2cc72f Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Mon, 26 Sep 2022 17:25:39 -0500 Subject: [PATCH 002/424] Updated Primary Clarifier Added expression for COD removal --- qsdsan/sanunits/_clarifier.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index 283b21af..c6f7453e 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -594,14 +594,9 @@ def _run(self): params = (HRT, r, corr) = self._HRT, self._r, self._corr if (i is None for i in params): raise RuntimeError('must specify HRT, ratio of effluent to sludge, and correction factor') - + n_COD = (corr*(2.88*r - 0.118)) - (1.45 + 6.15*np.log(HRT*24*60)) f_i = 1 - (n_COD/100) - - - Qs = Q_in*TSS_in*f_i/(self._MLSS-TSS_in) - - Zs = Ze = self.ins.conc * (1-cmps.x) Ce = dict(zip(cmps.IDs, Ze+Xe)) Cs = dict(zip(cmps.IDs, Zs+Xs)) From 130224f1bdae2c9ba9adbf65de6f6cdd1e763e5e Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Tue, 27 Sep 2022 15:51:33 -0500 Subject: [PATCH 003/424] Updated Primary Clarifier Updated --- qsdsan/sanunits/_clarifier.py | 64 ++++++++++++++++++++++++----------- 1 file changed, 44 insertions(+), 20 deletions(-) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index c6f7453e..e4d6a115 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -13,10 +13,12 @@ from numpy import maximum as npmax, minimum as npmin, exp as npexp from .. import SanUnit, WasteStream +# from qsdsan import SanUnit, WasteStream import numpy as np __all__ = ('FlatBottomCircularClarifier', - 'IdealClarifier',) + 'IdealClarifier', + 'PrimaryClarifier') def _settling_flux(X, v_max, v_max_practical, X_min, rh, rp, n0): @@ -542,14 +544,16 @@ class PrimaryClarifier(SanUnit): _N_outs = 2 def __init__(self, ID='', ins=None, outs=(), thermo=None, - isdynamic=False, init_with='WasteStream', Hydraulic_Retention_Time = 8, ratio_of_uf = 2, + isdynamic=False, init_with='WasteStream', Hydraulic_Retention_Time = 8, ratio_uf = 0.5, f_corr = 1, F_BM_default=None, **kwargs): SanUnit.__init__(self, ID, ins, outs, thermo, isdynamic=isdynamic, init_with=init_with, F_BM_default=F_BM_default) self.Hydraulic_Retention_Time = Hydraulic_Retention_Time - self.ratio_of_uf = ratio_of_uf + self.ratio_uf = ratio_uf self.f_corr = f_corr + + self.mixed = WasteStream('mixed') @property def Hydraulic_Retention_Time(self): @@ -564,15 +568,17 @@ def Hydraulic_Retention_Time(self, HRT): raise ValueError('HRT expected from user') @property - def ratio_of_uf(self): + def ratio_uf(self): return self._r - @ratio_of_uf.setter - def ratio_of_uf(self, r): + @ratio_uf.setter + def ratio_uf(self, r): if r is not None: + if r > 1 or r < 0: + raise ValueError(f'correction factor must be within [0, 1], not {r}') self._r = r else: - raise ValueError('Effluent to Sludge ratio expected from user') + raise ValueError('Sludge to influent ratio expected from user') @property def f_corr(self): @@ -584,26 +590,44 @@ def f_corr(self, corr): if corr > 1 or corr < 0: raise ValueError(f'correction factor must be within [0, 1], not {corr}') self._corr = corr + else: + raise ValueError('correction factor expected from user') def _run(self): q_inf, to, do = self.ins + + self.mixed.mix_from(self.ins) + xcod = self.mixed.composite('COD', particle_size='x') + fx = xcod/self.mixed.COD + uf, of = self.outs + cmps = self.components - Q_in = self.ins.get_total_flow('m3/d') - TSS_in = (self.ins.conc*cmps.x*cmps.i_mass).sum() + #Q_in = self.ins.get_total_flow('m3/d') + + #TSS_in = (self.ins.conc*cmps.x*cmps.i_mass).sum() + params = (HRT, r, corr) = self._HRT, self._r, self._corr - if (i is None for i in params): - raise RuntimeError('must specify HRT, ratio of effluent to sludge, and correction factor') - n_COD = (corr*(2.88*r - 0.118)) - (1.45 + 6.15*np.log(HRT*24*60)) + if sum([i is None for i in params]) > 0: + raise RuntimeError('must specify HRT, sludge to influent ratio, and correction factor') + + n_COD = (corr*(2.88*fx - 0.118)) - (1.45 + 6.15*np.log(HRT*24*60)) f_i = 1 - (n_COD/100) - Qs = Q_in*TSS_in*f_i/(self._MLSS-TSS_in) - Zs = Ze = self.ins.conc * (1-cmps.x) - Ce = dict(zip(cmps.IDs, Ze+Xe)) - Cs = dict(zip(cmps.IDs, Zs+Xs)) - Ce.pop('H2O', None) - Cs.pop('H2O', None) - of.set_flow_by_concentration(Q_in-Qs, Ce, units=('m3/d', 'mg/L')) - uf.set_flow_by_concentration(Qs, Cs, units=('m3/d', 'mg/L')) + + #Qs = Q_in*TSS_in*f_i/(self._MLSS-TSS_in) + + Zs = r*self.mixed.mass*cmps.s + Ze = (1-r)*self.mixed.mass*cmps.s + + Xe = (f_i)*self.mixed.mass*cmps.x + Xs = (1 - f_i)*self.mixed.mass*cmps.x + + Ce = Ze + Xe + Cs = Zs + Xs + + of.set_flow(Ce, 'kg/hr') + uf.set_flow(Cs, 'kg/hr') + def _design(self): pass \ No newline at end of file From 3609de2ceef96cb3a67ea2aaa476b687e6b4ce45 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Wed, 28 Sep 2022 13:46:49 -0500 Subject: [PATCH 004/424] Completed addition of Primary Clarifier Verified the mass balance by running a test case. Also added documentation. --- qsdsan/sanunits/_clarifier.py | 74 ++++++++++++++++++++++------------- 1 file changed, 47 insertions(+), 27 deletions(-) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index e4d6a115..7720e410 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -539,13 +539,36 @@ def _design(self): pass class PrimaryClarifier(SanUnit): + + """ + A Primary clarifier based on BSM2 Layout. [1] + + Parameters + ---------- + ID : str + ID for the clarifier. The default is ''. + ins : class:`WasteStream` + Influent to the clarifier. Expected number of influent is 3. + outs : class:`WasteStream` + Treated effluent and sludge. + Hydraulic Retention time : float + Hydraulic Retention Time in days. The default is 0.04268 days, based on IWA report. + ratio_uf : float + The ratio of sludge to primary influent. The default is 0.65, based on IWA report. + f_corr : float + Dimensionless correction factor for removal efficiency in the primary clarifier. + References + ---------- + .. [1] Gernaey, Krist V., Ulf Jeppsson, Peter A. Vanrolleghem, and John B. Copp. + Benchmarking of control strategies for wastewater treatment plants. IWA publishing, 2014. + """ _N_ins = 3 _N_outs = 2 def __init__(self, ID='', ins=None, outs=(), thermo=None, - isdynamic=False, init_with='WasteStream', Hydraulic_Retention_Time = 8, ratio_uf = 0.5, - f_corr = 1, F_BM_default=None, **kwargs): + isdynamic=False, init_with='WasteStream', Hydraulic_Retention_Time = 0.04268, ratio_uf =0.007, + f_corr = 0.65, F_BM_default=None, **kwargs): SanUnit.__init__(self, ID, ins, outs, thermo, isdynamic=isdynamic, init_with=init_with, F_BM_default=F_BM_default) @@ -553,8 +576,6 @@ def __init__(self, ID='', ins=None, outs=(), thermo=None, self.ratio_uf = ratio_uf self.f_corr = f_corr - self.mixed = WasteStream('mixed') - @property def Hydraulic_Retention_Time(self): '''The Hydraulic Retention time in days.''' @@ -575,7 +596,7 @@ def ratio_uf(self): def ratio_uf(self, r): if r is not None: if r > 1 or r < 0: - raise ValueError(f'correction factor must be within [0, 1], not {r}') + raise ValueError(f'Sludge to influent ratio must be within [0, 1], not {r}') self._r = r else: raise ValueError('Sludge to influent ratio expected from user') @@ -592,42 +613,41 @@ def f_corr(self, corr): self._corr = corr else: raise ValueError('correction factor expected from user') - - def _run(self): - q_inf, to, do = self.ins - - self.mixed.mix_from(self.ins) + + def _f_i(self): xcod = self.mixed.composite('COD', particle_size='x') fx = xcod/self.mixed.COD - uf, of = self.outs - - cmps = self.components - #Q_in = self.ins.get_total_flow('m3/d') - - #TSS_in = (self.ins.conc*cmps.x*cmps.i_mass).sum() - - params = (HRT, r, corr) = self._HRT, self._r, self._corr + params = (HRT, corr) = self._HRT, self._corr if sum([i is None for i in params]) > 0: raise RuntimeError('must specify HRT, sludge to influent ratio, and correction factor') - - n_COD = (corr*(2.88*fx - 0.118)) - (1.45 + 6.15*np.log(HRT*24*60)) + + n_COD = corr*(2.88*fx - 0.118)*(1.45 + 6.15*np.log(HRT*24*60)) f_i = 1 - (n_COD/100) + return f_i + + def _run(self): + + q_inf, to, do = self.ins + uf, of = self.outs + cmps = self.components + self.mixed = WasteStream('mixed') + self.mixed.mix_from(self.ins) - #Qs = Q_in*TSS_in*f_i/(self._MLSS-TSS_in) + r = self._r + f_i = self._f_i() + + Xs = (1 - f_i)*self.mixed.mass*cmps.x + Xe = (f_i)*self.mixed.mass*cmps.x Zs = r*self.mixed.mass*cmps.s Ze = (1-r)*self.mixed.mass*cmps.s - Xe = (f_i)*self.mixed.mass*cmps.x - Xs = (1 - f_i)*self.mixed.mass*cmps.x - Ce = Ze + Xe Cs = Zs + Xs - of.set_flow(Ce, 'kg/hr') - uf.set_flow(Cs, 'kg/hr') + of.set_flow(Ce,'kg/hr') + uf.set_flow(Cs,'kg/hr') - def _design(self): pass \ No newline at end of file From d44bc7fd93fb3ca07c532440685e0bf5e7d3ef4e Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Thu, 29 Sep 2022 20:08:48 -0500 Subject: [PATCH 005/424] Updated Clarifier Added the design method to primary clarifier --- qsdsan/sanunits/_clarifier.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index 7720e410..8b932b06 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -650,4 +650,8 @@ def _run(self): uf.set_flow(Cs,'kg/hr') def _design(self): - pass \ No newline at end of file + + design = self.design_results + HRT = self._HRT + Q_inflow = self.mixed.get_total_flow('m3/hr') + design['Volume'] = 24*HRT*Q_inflow \ No newline at end of file From b102717e440569471b2deb0c0489fc418cf5baa9 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Fri, 30 Sep 2022 08:39:42 -0500 Subject: [PATCH 006/424] Update _clarifier.py --- qsdsan/sanunits/_clarifier.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index 8b932b06..7278bc32 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -568,7 +568,7 @@ class PrimaryClarifier(SanUnit): def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, init_with='WasteStream', Hydraulic_Retention_Time = 0.04268, ratio_uf =0.007, - f_corr = 0.65, F_BM_default=None, **kwargs): + f_corr=0.65, F_BM_default=None, **kwargs): SanUnit.__init__(self, ID, ins, outs, thermo, isdynamic=isdynamic, init_with=init_with, F_BM_default=F_BM_default) @@ -618,10 +618,8 @@ def _f_i(self): xcod = self.mixed.composite('COD', particle_size='x') fx = xcod/self.mixed.COD - params = (HRT, corr) = self._HRT, self._corr - if sum([i is None for i in params]) > 0: - raise RuntimeError('must specify HRT, sludge to influent ratio, and correction factor') - + corr = self._corr + HRT = self._HRT n_COD = corr*(2.88*fx - 0.118)*(1.45 + 6.15*np.log(HRT*24*60)) f_i = 1 - (n_COD/100) return f_i @@ -650,8 +648,15 @@ def _run(self): uf.set_flow(Cs,'kg/hr') def _design(self): +<<<<<<< Updated upstream design = self.design_results HRT = self._HRT Q_inflow = self.mixed.get_total_flow('m3/hr') - design['Volume'] = 24*HRT*Q_inflow \ No newline at end of file + design['Volume'] = 24*HRT*Q_inflow +======= + design = self.design_results + HRT=self._HRT + Q_in = self.mixed.get_total_flow('m3/hr') + design['Volume'] = HRT*Q_in +>>>>>>> Stashed changes From ec4350c3a8b0b3bb28f845f239629d5f8cf22d51 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Fri, 30 Sep 2022 18:03:09 -0500 Subject: [PATCH 007/424] Created _sludge_treatment Created new class Thickener based on the BSM2 model --- qsdsan/sanunits/_sludge_treatment.py | 104 +++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 qsdsan/sanunits/_sludge_treatment.py diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py new file mode 100644 index 00000000..20d26445 --- /dev/null +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -0,0 +1,104 @@ +# -*- coding: utf-8 -*- +""" +Created on Fri Sep 30 11:15:25 2022 + +@author: raisa +""" +from .. import SanUnit, WasteStream +import numpy as np + +class Thickener(SanUnit): + + _N_ins = 1 + _N_outs = 2 + + def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, + init_with='WasteStream', F_BM_default=None, thickner_perc=7, TSS_removal_perc=98, **kwargs): + SanUnit.__init__(self, ID, ins, outs, thermo, isdynamic=isdynamic, init_with=init_with, + F_BM_default=F_BM_default) + + @property + def thickner_perc(self): + '''tp is the percentage of Suspended Sludge in the underflow of the thickener''' + return self._tp + + @thickner_perc.setter + def thickner_perc(self, tp): + if tp is not None: + if tp>=100 or tp<=0: + raise ValueError(f'should be between 0 and 100 not {tp}') + self._tp = tp + else: + raise ValueError('percentage of SS in the underflow of the thickener expected from user') + + @property + def TSS_removal_perc(self): + '''The percentage of suspended solids removed in the thickner''' + return self._TSS_rmv + + @TSS_removal_perc.setter + def TSS_removal_perc(self, TSS_rmv): + if TSS_rmv is not None: + if TSS_rmv>=100 or TSS_rmv<=0: + raise ValueError(f'should be between 0 and 100 not {TSS_rmv}') + self._TSS_rmv = TSS_rmv + else: + raise ValueError('percentage of suspended solids removed in the thickner expected from user') + + @property + def thickner_factor(self): + inf, = self.ins[0] + if not self.ins: return + elif inf.isempty(): return + else: + TSS_in = inf.get_TSS() + if TSS_in > 0: + thickner_factor = self._tp*10000/self.ins[0].get_TSS() + if thickner_factor<1: + thickner_factor=1 + return thickner_factor + else: return None + + @property + def thinning_factor(self): + thickner_factor = self.thickner_factor + if thickner_factor<1: + thinning_factor=0 + else: + Qu_factor = self._TSS_rmv/(100*thickner_factor) + thinning_factor = (1 - (self._TSS_rmv/100))/(1 - Qu_factor) + return thinning_factor + + def _run(self): + + # self.inlet = WasteStream('inlet') + # self.inlet = self.ins + inf, = self.ins + uf, of = self.outs + + split = + + inf.split_to(uf, of, split) + + + cmps = self.components + + + + + + + + + + + + + + + + + + + + From 3b06567009ef3526bda0c44e040e7e51a380f1eb Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Fri, 30 Sep 2022 18:04:34 -0500 Subject: [PATCH 008/424] Updated Primary Clarifier made small changes to resolve conflict between two versions --- qsdsan/sanunits/_clarifier.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index 7278bc32..039e2cd4 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -13,7 +13,6 @@ from numpy import maximum as npmax, minimum as npmin, exp as npexp from .. import SanUnit, WasteStream -# from qsdsan import SanUnit, WasteStream import numpy as np __all__ = ('FlatBottomCircularClarifier', @@ -648,15 +647,8 @@ def _run(self): uf.set_flow(Cs,'kg/hr') def _design(self): -<<<<<<< Updated upstream design = self.design_results HRT = self._HRT Q_inflow = self.mixed.get_total_flow('m3/hr') - design['Volume'] = 24*HRT*Q_inflow -======= - design = self.design_results - HRT=self._HRT - Q_in = self.mixed.get_total_flow('m3/hr') - design['Volume'] = HRT*Q_in ->>>>>>> Stashed changes + design['Volume'] = 24*HRT*Q_inflow \ No newline at end of file From 3254ba632fd06683d80240c4078894e336a519eb Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Mon, 3 Oct 2022 17:03:45 -0500 Subject: [PATCH 009/424] Updated Thickener Added the splits for particulates and soluble components in accordance with the BSM2 model --- qsdsan/sanunits/_sludge_treatment.py | 33 ++++++++++------------------ 1 file changed, 11 insertions(+), 22 deletions(-) diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py index 20d26445..02c1531a 100644 --- a/qsdsan/sanunits/_sludge_treatment.py +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -71,34 +71,23 @@ def thinning_factor(self): def _run(self): - # self.inlet = WasteStream('inlet') - # self.inlet = self.ins inf, = self.ins uf, of = self.outs - - split = - - inf.split_to(uf, of, split) - - cmps = self.components - - - + TSS_rmv = self._TSS_rmv + Ze = (1 - thinning_factor)/(thickner_factor - thinning_factor)*inf.mass*cmps.x + Zs = (thickner_factor - 1)/(thickner_factor - thinning_factor)*inf.mass*cmps.x + Xe = (TSS_rmv)*inf.mass*cmps.s + Xs = (1 - TSS_rmv)*inf.mass*cmps.s + Ce = Ze + Xe + Cs = Zs + Xs + of.set_flow(Ce,'kg/hr') + uf.set_flow(Cs,'kg/hr') - - - - - - - - - - - + #inf.cmps.s.split_to(uf, of, split= (1 - thinning_factor)/(thickner_factor - thinning_factor)) + #inf.cmps.x.split_to(uf, of, split = TSS_rmv) \ No newline at end of file From 470e3177b6255d10053706dca614e55d8bc654ee Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Mon, 3 Oct 2022 18:05:59 -0500 Subject: [PATCH 010/424] Updated _sludge_treatment Made changes to add thickener to sanunits --- qsdsan/sanunits/__init__.py | 3 + qsdsan/sanunits/_sludge_treatment.py | 149 ++++++++++++++------------- 2 files changed, 82 insertions(+), 70 deletions(-) diff --git a/qsdsan/sanunits/__init__.py b/qsdsan/sanunits/__init__.py index e0947657..41b79aa4 100644 --- a/qsdsan/sanunits/__init__.py +++ b/qsdsan/sanunits/__init__.py @@ -47,6 +47,7 @@ from ._suspended_growth_bioreactor import * from ._tanks import * from ._trucking import * +from ._sludge_treatment import * # Units that rely on other units from ._activated_sludge_process import * @@ -99,6 +100,7 @@ _biogenic_refinery, _eco_san, _reclaimer, + _sludge_treatment, ) @@ -135,4 +137,5 @@ *_biogenic_refinery.__all__, *_reclaimer.__all__, *_eco_san.__all__, + *_sludge_treatment.__all__, ) \ No newline at end of file diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py index 02c1531a..f41b561c 100644 --- a/qsdsan/sanunits/_sludge_treatment.py +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -7,6 +7,10 @@ from .. import SanUnit, WasteStream import numpy as np +__all__ = ('Thickener',) + + + class Thickener(SanUnit): _N_ins = 1 @@ -17,77 +21,82 @@ def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, SanUnit.__init__(self, ID, ins, outs, thermo, isdynamic=isdynamic, init_with=init_with, F_BM_default=F_BM_default) - @property - def thickner_perc(self): - '''tp is the percentage of Suspended Sludge in the underflow of the thickener''' - return self._tp + self.thickner_perc = thickner_perc + self.TSS_removal_perc = TSS_removal_perc + + @property + def thickner_perc(self): + '''tp is the percentage of Suspended Sludge in the underflow of the thickener''' + return self._tp - @thickner_perc.setter - def thickner_perc(self, tp): - if tp is not None: - if tp>=100 or tp<=0: - raise ValueError(f'should be between 0 and 100 not {tp}') - self._tp = tp - else: - raise ValueError('percentage of SS in the underflow of the thickener expected from user') - - @property - def TSS_removal_perc(self): - '''The percentage of suspended solids removed in the thickner''' - return self._TSS_rmv + @thickner_perc.setter + def thickner_perc(self, tp): + if tp is not None: + if tp>=100 or tp<=0: + raise ValueError(f'should be between 0 and 100 not {tp}') + self._tp = tp + else: + raise ValueError('percentage of SS in the underflow of the thickener expected from user') + + @property + def TSS_removal_perc(self): + '''The percentage of suspended solids removed in the thickner''' + return self._TSS_rmv - @TSS_removal_perc.setter - def TSS_removal_perc(self, TSS_rmv): - if TSS_rmv is not None: - if TSS_rmv>=100 or TSS_rmv<=0: - raise ValueError(f'should be between 0 and 100 not {TSS_rmv}') - self._TSS_rmv = TSS_rmv - else: - raise ValueError('percentage of suspended solids removed in the thickner expected from user') - - @property - def thickner_factor(self): - inf, = self.ins[0] - if not self.ins: return - elif inf.isempty(): return - else: - TSS_in = inf.get_TSS() - if TSS_in > 0: - thickner_factor = self._tp*10000/self.ins[0].get_TSS() - if thickner_factor<1: - thickner_factor=1 - return thickner_factor - else: return None + @TSS_removal_perc.setter + def TSS_removal_perc(self, TSS_rmv): + if TSS_rmv is not None: + if TSS_rmv>=100 or TSS_rmv<=0: + raise ValueError(f'should be between 0 and 100 not {TSS_rmv}') + self._TSS_rmv = TSS_rmv + else: + raise ValueError('percentage of suspended solids removed in the thickner expected from user') + + @property + def thickner_factor(self): + inf, = self.ins + if not self.ins: return + elif inf.isempty(): return + else: + TSS_in = inf.get_TSS() + if TSS_in > 0: + thickner_factor = self._tp*10000/self.ins[0].get_TSS() + if thickner_factor<1: + thickner_factor=1 + return thickner_factor + else: return None + + @property + def thinning_factor(self): + thickner_factor = self.thickner_factor + if thickner_factor<1: + thinning_factor=0 + else: + Qu_factor = self._TSS_rmv/(100*thickner_factor) + thinning_factor = (1 - (self._TSS_rmv/100))/(1 - Qu_factor) + return thinning_factor + + def _run(self): - @property - def thinning_factor(self): - thickner_factor = self.thickner_factor - if thickner_factor<1: - thinning_factor=0 - else: - Qu_factor = self._TSS_rmv/(100*thickner_factor) - thinning_factor = (1 - (self._TSS_rmv/100))/(1 - Qu_factor) - return thinning_factor + inf, = self.ins + uf, of = self.outs + cmps = self.components - def _run(self): - - inf, = self.ins - uf, of = self.outs - cmps = self.components - - TSS_rmv = self._TSS_rmv - - Ze = (1 - thinning_factor)/(thickner_factor - thinning_factor)*inf.mass*cmps.x - Zs = (thickner_factor - 1)/(thickner_factor - thinning_factor)*inf.mass*cmps.x - - Xe = (TSS_rmv)*inf.mass*cmps.s - Xs = (1 - TSS_rmv)*inf.mass*cmps.s - - Ce = Ze + Xe - Cs = Zs + Xs - - of.set_flow(Ce,'kg/hr') - uf.set_flow(Cs,'kg/hr') - - #inf.cmps.s.split_to(uf, of, split= (1 - thinning_factor)/(thickner_factor - thinning_factor)) - #inf.cmps.x.split_to(uf, of, split = TSS_rmv) \ No newline at end of file + TSS_rmv = self._TSS_rmv + thinning_factor = self.thinning_factor + thickner_factor = self.thickner_factor + + Ze = (1 - thinning_factor)/(thickner_factor - thinning_factor)*inf.mass*cmps.s + Zs = (thickner_factor - 1)/(thickner_factor - thinning_factor)*inf.mass*cmps.s + + Xe = (1 - TSS_rmv/100)*inf.mass*cmps.x + Xs = (TSS_rmv/100)*inf.mass*cmps.x + + Ce = Ze + Xe + Cs = Zs + Xs + + of.set_flow(Ce,'kg/hr') + uf.set_flow(Cs,'kg/hr') + + #inf.cmps.s.split_to(uf, of, split= (1 - thinning_factor)/(thickner_factor - thinning_factor)) + #inf.cmps.x.split_to(uf, of, split = TSS_rmv) \ No newline at end of file From 5d1a0f04ee58dc611bb70f32b9363c201488a4cb Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Tue, 4 Oct 2022 17:45:53 -0500 Subject: [PATCH 011/424] Updated _sluge_treatment Added design function for thickener, and created a subclass of thickener 'DewateringUnit' --- qsdsan/sanunits/_sludge_treatment.py | 144 +++++++++++++++++++++------ 1 file changed, 114 insertions(+), 30 deletions(-) diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py index f41b561c..1aaed012 100644 --- a/qsdsan/sanunits/_sludge_treatment.py +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -1,36 +1,67 @@ # -*- coding: utf-8 -*- -""" -Created on Fri Sep 30 11:15:25 2022 +''' +QSDsan: Quantitative Sustainable Design for sanitation and resource recovery systems -@author: raisa -""" -from .. import SanUnit, WasteStream -import numpy as np +This module is developed by: + + Saumitra Rai + + Joy Zhang -__all__ = ('Thickener',) +This module is under the University of Illinois/NCSA Open Source License. +Please refer to https://github.com/QSD-Group/QSDsan/blob/main/LICENSE.txt +for license details. +''' +from .. import SanUnit +import numpy as np +__all__ = ('Thickener', 'DewateringUnit',) class Thickener(SanUnit): + """ + Thickener based on BSM2 Layout. [1] + + Parameters + ---------- + ID : str + ID for the Thickener. The default is ''. + ins : class:`WasteStream` + Influent to the clarifier. Expected number of influent is 1. + outs : class:`WasteStream` + Treated effluent and sludge. + thickener_perc : float + The percentage of Suspended Sludge in the underflow of the thickener. + TSS_removal_perc : float + The percentage of suspended solids removed in the thickener. + + References + ---------- + .. [1] Gernaey, Krist V., Ulf Jeppsson, Peter A. Vanrolleghem, and John B. Copp. + Benchmarking of control strategies for wastewater treatment plants. IWA publishing, 2014. + """ + _N_ins = 1 _N_outs = 2 def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, - init_with='WasteStream', F_BM_default=None, thickner_perc=7, TSS_removal_perc=98, **kwargs): - SanUnit.__init__(self, ID, ins, outs, thermo, isdynamic=isdynamic, init_with=init_with, - F_BM_default=F_BM_default) + init_with='WasteStream', F_BM_default=None, thickener_perc=7, + TSS_removal_perc=98, solids_loading_rate = 50, **kwargs): + SanUnit.__init__(self, ID, ins, outs, thermo, isdynamic=isdynamic, + init_with=init_with, F_BM_default=F_BM_default) - self.thickner_perc = thickner_perc + self.thickener_perc = thickener_perc self.TSS_removal_perc = TSS_removal_perc + self.solids_loading_rate = solids_loading_rate @property - def thickner_perc(self): + def thickener_perc(self): '''tp is the percentage of Suspended Sludge in the underflow of the thickener''' return self._tp - @thickner_perc.setter - def thickner_perc(self, tp): + @thickener_perc.setter + def thickener_perc(self, tp): if tp is not None: if tp>=100 or tp<=0: raise ValueError(f'should be between 0 and 100 not {tp}') @@ -38,9 +69,21 @@ def thickner_perc(self, tp): else: raise ValueError('percentage of SS in the underflow of the thickener expected from user') + @property + def solids_loading_rate(self): + '''solids_loading_rate is the loading in the thickener''' + return self._slr + + @solids_loading_rate.setter + def solids_loading_rate(self, slr): + if slr is not None: + self._slr = slr + else: + raise ValueError('solids_loading_rate of the thickener expected from user') + @property def TSS_removal_perc(self): - '''The percentage of suspended solids removed in the thickner''' + '''The percentage of suspended solids removed in the thickener''' return self._TSS_rmv @TSS_removal_perc.setter @@ -50,29 +93,29 @@ def TSS_removal_perc(self, TSS_rmv): raise ValueError(f'should be between 0 and 100 not {TSS_rmv}') self._TSS_rmv = TSS_rmv else: - raise ValueError('percentage of suspended solids removed in the thickner expected from user') + raise ValueError('percentage of suspended solids removed in the thickener expected from user') @property - def thickner_factor(self): + def thickener_factor(self): inf, = self.ins if not self.ins: return elif inf.isempty(): return else: TSS_in = inf.get_TSS() if TSS_in > 0: - thickner_factor = self._tp*10000/self.ins[0].get_TSS() - if thickner_factor<1: - thickner_factor=1 - return thickner_factor + thickener_factor = self._tp*10000/self.ins[0].get_TSS() + if thickener_factor<1: + thickener_factor=1 + return thickener_factor else: return None @property def thinning_factor(self): - thickner_factor = self.thickner_factor - if thickner_factor<1: + thickener_factor = self.thickener_factor + if thickener_factor<1: thinning_factor=0 else: - Qu_factor = self._TSS_rmv/(100*thickner_factor) + Qu_factor = self._TSS_rmv/(100*thickener_factor) thinning_factor = (1 - (self._TSS_rmv/100))/(1 - Qu_factor) return thinning_factor @@ -84,19 +127,60 @@ def _run(self): TSS_rmv = self._TSS_rmv thinning_factor = self.thinning_factor - thickner_factor = self.thickner_factor + thickener_factor = self.thickener_factor - Ze = (1 - thinning_factor)/(thickner_factor - thinning_factor)*inf.mass*cmps.s - Zs = (thickner_factor - 1)/(thickner_factor - thinning_factor)*inf.mass*cmps.s + Ze = (1 - thinning_factor)/(thickener_factor - thinning_factor)*inf.mass*cmps.s + Zs = (thickener_factor - 1)/(thickener_factor - thinning_factor)*inf.mass*cmps.s Xe = (1 - TSS_rmv/100)*inf.mass*cmps.x Xs = (TSS_rmv/100)*inf.mass*cmps.x Ce = Ze + Xe Cs = Zs + Xs - + of.set_flow(Ce,'kg/hr') uf.set_flow(Cs,'kg/hr') - #inf.cmps.s.split_to(uf, of, split= (1 - thinning_factor)/(thickner_factor - thinning_factor)) - #inf.cmps.x.split_to(uf, of, split = TSS_rmv) \ No newline at end of file + def _design(self): + + design = self.design_results + slr = self._slr + + design['Area'] = ((self.ins[0].get_TSS()/1000)*self.ins[0].F_vol*24)/slr # in m2 + design['Hydraulic_Loading'] = (self.ins[0].F_vol*24)/design['Area'] #in m3/(m2*day) + design['Diameter'] = np.sqrt(4*design['Area']/np.pi) #in m + +class DewateringUnit(Thickener): + + """ + Dewatering Unit based on BSM2 Layout. [1] + + Parameters + ---------- + ID : str + ID for the Dewatering Unit. The default is ''. + ins : class:`WasteStream` + Influent to the Dewatering Unit. Expected number of influent is 1. + outs : class:`WasteStream` + Treated effluent and sludge. + thickener_perc : float + The percentage of Suspended Sludge in the underflow of the dewatering unit. + TSS_removal_perc : float + The percentage of suspended solids removed in the dewatering unit. + + References + ---------- + .. [1] Gernaey, Krist V., Ulf Jeppsson, Peter A. Vanrolleghem, and John B. Copp. + Benchmarking of control strategies for wastewater treatment plants. IWA publishing, 2014. + """ + + _N_ins = 1 + _N_outs = 2 + + def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, + init_with='WasteStream', F_BM_default=None, thickener_perc=28, TSS_removal_perc=98, **kwargs): + Thickener.__init__(self, ID=ID, ins=ins, outs=outs, thermo=thermo, isdynamic=isdynamic, + init_with=init_with, F_BM_default=F_BM_default, thickener_perc=thickener_perc, TSS_removal_perc=TSS_removal_perc, **kwargs) + + def _design(self): + pass \ No newline at end of file From 68316218b7e5529aed79baa293aa516269766ef0 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Thu, 6 Oct 2022 18:21:25 -0500 Subject: [PATCH 012/424] Updated _sludge_treatment Added design method of dewatering unit --- qsdsan/sanunits/_sludge_treatment.py | 60 +++++++++++++++++++++++++--- 1 file changed, 54 insertions(+), 6 deletions(-) diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py index 1aaed012..93bedaf1 100644 --- a/qsdsan/sanunits/_sludge_treatment.py +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -32,14 +32,18 @@ class Thickener(SanUnit): outs : class:`WasteStream` Treated effluent and sludge. thickener_perc : float - The percentage of Suspended Sludge in the underflow of the thickener. + The percentage of Suspended Sludge in the underflow of the thickener.[1] TSS_removal_perc : float - The percentage of suspended solids removed in the thickener. + The percentage of suspended solids removed in the thickener.[1] + solids_loading_rate : float + Solid loading rate in the thickener.[2] References ---------- .. [1] Gernaey, Krist V., Ulf Jeppsson, Peter A. Vanrolleghem, and John B. Copp. Benchmarking of control strategies for wastewater treatment plants. IWA publishing, 2014. + [2] Metcalf, Leonard, Harrison P. Eddy, and Georg Tchobanoglous. Wastewater + engineering: treatment, disposal, and reuse. Vol. 4. New York: McGraw-Hill, 1991. """ _N_ins = 1 @@ -166,21 +170,65 @@ class DewateringUnit(Thickener): thickener_perc : float The percentage of Suspended Sludge in the underflow of the dewatering unit. TSS_removal_perc : float - The percentage of suspended solids removed in the dewatering unit. + The percentage of suspended solids removed in the dewatering unit. + number_of_centrifuges : float + Number of centrifuges in the dewatering unit + specific_gravity_sludge: float + Specific gravity of influent sludge from secondary clarifier. + cake density: float + Density of effleunt dewatered sludge. + centrifugal_force : float + Centrifugal force in the centrifuge. + rotational_speed : float + rotational speed of the centrifuge. + polymer_dosage_per_kg_of_sludge : float + mass of polymer utilised per kg of influent sludge. + h_cylinderical: float + length of cylinderical portion of dewatering unit. + h_conical: float + length of conical portion of dewatering unit. References ---------- .. [1] Gernaey, Krist V., Ulf Jeppsson, Peter A. Vanrolleghem, and John B. Copp. Benchmarking of control strategies for wastewater treatment plants. IWA publishing, 2014. + [2] Metcalf, Leonard, Harrison P. Eddy, and Georg Tchobanoglous. Wastewater + engineering: treatment, disposal, and reuse. Vol. 4. New York: McGraw-Hill, 1991. """ _N_ins = 1 _N_outs = 2 def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, - init_with='WasteStream', F_BM_default=None, thickener_perc=28, TSS_removal_perc=98, **kwargs): + init_with='WasteStream', F_BM_default=None, thickener_perc=28, TSS_removal_perc=98, + number_of_centrifuges=1, specific_gravity_sludge=1.03, cake_density=965, + centrifugal_force=2500, rotational_speed = 40, polymer_dosage_per_kg_of_sludge = 0.0075, + h_cylinderical=2, h_conical=1, **kwargs): Thickener.__init__(self, ID=ID, ins=ins, outs=outs, thermo=thermo, isdynamic=isdynamic, - init_with=init_with, F_BM_default=F_BM_default, thickener_perc=thickener_perc, TSS_removal_perc=TSS_removal_perc, **kwargs) + init_with=init_with, F_BM_default=F_BM_default, thickener_perc=thickener_perc, + TSS_removal_perc=TSS_removal_perc, **kwargs) + self.number_of_centrifuges=number_of_centrifuges + self.specific_gravity_sludge=specific_gravity_sludge + self.cake_density=cake_density #in kg/m3 + self.centrifugal_force = centrifugal_force #in Newton + self.rotational_speed = rotational_speed #in revolution/sec + self.polymer_dosage_per_kg_of_sludge = polymer_dosage_per_kg_of_sludge #in (kg,polymer/kg,sludge) unitless + self.h_cylinderical = h_cylinderical + self.h_conical = h_conical def _design(self): - pass \ No newline at end of file + sludge_feed_rate = ((self.ins[0].get_TSS()*self.ins[0].F_vol)/1000)/self.number_of_centrifuges #in kg/hr + + #TSS_rmv = self._TSS_rmv + #recovery = 1 - TSS_rmv/100 + #cake_mass_discharge_rate = sludge_feed_rate*recovery #in kg/hr + #wetcake_mass_discharge_rate = cake_mass_discharge_rate/(self.thickener_perc/100) #in kg/hr + #cake_density = self.cake_density + #wetcake_flowrate = wetcake_mass_discharge_rate/cake_density #in m3/hr + #volume_reduction_perc= (1 - wetcake_flowrate/(self.ins[0].F_mass/(1000*self.specific_gravity_sludge*self.number_of_centrifuges)))*100 + + design = self.design_results + design['Diameter'] = 2*(self.centrifugal_force*9.81/np.square(2*np.pi*self.rotational_speed)) #in meter + design['Polymer feed rate'] = (self.polymer_dosage_per_kg_of_sludge*sludge_feed_rate) # in kg/hr + design['Volume'] = np.pi*np.square(design['Diameter']/2)*(self.h_cylinderical + (self.h_conical/3)) + design['Curved Surface Area'] = np.pi*design['Diameter']/2*(2*self.h_cylinderical + np.sqrt(np.square(design['Diameter']/2) + np.square(self.h_conical))) \ No newline at end of file From 73bdee1393cb26eef7ca43b8b5762948fbce915e Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Fri, 7 Oct 2022 09:53:56 -0500 Subject: [PATCH 013/424] Updated sludge treatment Completed design functions --- qsdsan/sanunits/_sludge_treatment.py | 38 ++++++++++++++++++---------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py index 93bedaf1..5592d954 100644 --- a/qsdsan/sanunits/_sludge_treatment.py +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -37,6 +37,8 @@ class Thickener(SanUnit): The percentage of suspended solids removed in the thickener.[1] solids_loading_rate : float Solid loading rate in the thickener.[2] + h_cylinderical = float + Height of cylinder forming the thickener.[2] References ---------- @@ -51,13 +53,14 @@ class Thickener(SanUnit): def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, init_with='WasteStream', F_BM_default=None, thickener_perc=7, - TSS_removal_perc=98, solids_loading_rate = 50, **kwargs): + TSS_removal_perc=98, solids_loading_rate = 50, h_cylinderical=2, **kwargs): SanUnit.__init__(self, ID, ins, outs, thermo, isdynamic=isdynamic, init_with=init_with, F_BM_default=F_BM_default) self.thickener_perc = thickener_perc self.TSS_removal_perc = TSS_removal_perc self.solids_loading_rate = solids_loading_rate + self.h_cylinderical = h_cylinderical @property def thickener_perc(self): @@ -153,6 +156,9 @@ def _design(self): design['Area'] = ((self.ins[0].get_TSS()/1000)*self.ins[0].F_vol*24)/slr # in m2 design['Hydraulic_Loading'] = (self.ins[0].F_vol*24)/design['Area'] #in m3/(m2*day) design['Diameter'] = np.sqrt(4*design['Area']/np.pi) #in m + design['Volume'] = np.pi*np.square(design['Diameter']/2)*self.h_cylinderical #in m3 + design['Curved Surface Area'] = np.pi*design['Diameter']*self.h_cylinderical #in m2 + class DewateringUnit(Thickener): @@ -168,25 +174,25 @@ class DewateringUnit(Thickener): outs : class:`WasteStream` Treated effluent and sludge. thickener_perc : float - The percentage of Suspended Sludge in the underflow of the dewatering unit. + The percentage of Suspended Sludge in the underflow of the dewatering unit.[1] TSS_removal_perc : float - The percentage of suspended solids removed in the dewatering unit. + The percentage of suspended solids removed in the dewatering unit.[1] number_of_centrifuges : float - Number of centrifuges in the dewatering unit + Number of centrifuges in the dewatering unit.[2,3] specific_gravity_sludge: float - Specific gravity of influent sludge from secondary clarifier. + Specific gravity of influent sludge from secondary clarifier.[2,3] cake density: float - Density of effleunt dewatered sludge. + Density of effleunt dewatered sludge.[2,3] centrifugal_force : float - Centrifugal force in the centrifuge. + Centrifugal force in the centrifuge.[2,3] rotational_speed : float - rotational speed of the centrifuge. + rotational speed of the centrifuge.[2,3] polymer_dosage_per_kg_of_sludge : float - mass of polymer utilised per kg of influent sludge. + mass of polymer utilised per kg of influent sludge.[2,3] h_cylinderical: float - length of cylinderical portion of dewatering unit. + length of cylinderical portion of dewatering unit.[2,3] h_conical: float - length of conical portion of dewatering unit. + length of conical portion of dewatering unit.[2,3] References ---------- @@ -194,6 +200,8 @@ class DewateringUnit(Thickener): Benchmarking of control strategies for wastewater treatment plants. IWA publishing, 2014. [2] Metcalf, Leonard, Harrison P. Eddy, and Georg Tchobanoglous. Wastewater engineering: treatment, disposal, and reuse. Vol. 4. New York: McGraw-Hill, 1991. + [3]Design of Municipal Wastewater Treatment Plants: WEF Manual of Practice + No. 8 ASCE Manuals and Reports on Engineering Practice No. 76, Fifth Edition. """ _N_ins = 1 @@ -228,7 +236,9 @@ def _design(self): #volume_reduction_perc= (1 - wetcake_flowrate/(self.ins[0].F_mass/(1000*self.specific_gravity_sludge*self.number_of_centrifuges)))*100 design = self.design_results - design['Diameter'] = 2*(self.centrifugal_force*9.81/np.square(2*np.pi*self.rotational_speed)) #in meter + design['Diameter'] = 2*(self.centrifugal_force*9.81/np.square(2*np.pi*self.rotational_speed)) #in m design['Polymer feed rate'] = (self.polymer_dosage_per_kg_of_sludge*sludge_feed_rate) # in kg/hr - design['Volume'] = np.pi*np.square(design['Diameter']/2)*(self.h_cylinderical + (self.h_conical/3)) - design['Curved Surface Area'] = np.pi*design['Diameter']/2*(2*self.h_cylinderical + np.sqrt(np.square(design['Diameter']/2) + np.square(self.h_conical))) \ No newline at end of file + design['Projected Area at Inlet'] = np.pi*np.square(design['Diameter']/2) #in m2 + design['Hydraulic_Loading'] = (self.ins[0].F_vol*24)/design['Area'] #in m3/(m2*day) + design['Volume'] = np.pi*np.square(design['Diameter']/2)*(self.h_cylinderical + (self.h_conical/3)) #in m3 + design['Curved Surface Area'] = np.pi*design['Diameter']/2*(2*self.h_cylinderical + np.sqrt(np.square(design['Diameter']/2) + np.square(self.h_conical))) #in m2 \ No newline at end of file From fff6dfd637e3626faad9c4b805f031771f3e7003 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Fri, 7 Oct 2022 11:41:06 -0500 Subject: [PATCH 014/424] Updated sludge treatment Corrected design function of dewatering unit --- qsdsan/sanunits/_sludge_treatment.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py index 5592d954..8cc3ebe4 100644 --- a/qsdsan/sanunits/_sludge_treatment.py +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -210,7 +210,7 @@ class DewateringUnit(Thickener): def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, init_with='WasteStream', F_BM_default=None, thickener_perc=28, TSS_removal_perc=98, number_of_centrifuges=1, specific_gravity_sludge=1.03, cake_density=965, - centrifugal_force=2500, rotational_speed = 40, polymer_dosage_per_kg_of_sludge = 0.0075, + g_factor=2500, rotational_speed = 40, polymer_dosage_per_kg_of_sludge = 0.0075, h_cylinderical=2, h_conical=1, **kwargs): Thickener.__init__(self, ID=ID, ins=ins, outs=outs, thermo=thermo, isdynamic=isdynamic, init_with=init_with, F_BM_default=F_BM_default, thickener_perc=thickener_perc, @@ -218,7 +218,7 @@ def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, self.number_of_centrifuges=number_of_centrifuges self.specific_gravity_sludge=specific_gravity_sludge self.cake_density=cake_density #in kg/m3 - self.centrifugal_force = centrifugal_force #in Newton + self.g_factor = g_factor #unitless, centrifugal acceleration = g_factor*9.81 self.rotational_speed = rotational_speed #in revolution/sec self.polymer_dosage_per_kg_of_sludge = polymer_dosage_per_kg_of_sludge #in (kg,polymer/kg,sludge) unitless self.h_cylinderical = h_cylinderical @@ -236,9 +236,9 @@ def _design(self): #volume_reduction_perc= (1 - wetcake_flowrate/(self.ins[0].F_mass/(1000*self.specific_gravity_sludge*self.number_of_centrifuges)))*100 design = self.design_results - design['Diameter'] = 2*(self.centrifugal_force*9.81/np.square(2*np.pi*self.rotational_speed)) #in m + design['Diameter'] = 2*(self.g_factor*9.81/np.square(2*np.pi*self.rotational_speed)) #in m design['Polymer feed rate'] = (self.polymer_dosage_per_kg_of_sludge*sludge_feed_rate) # in kg/hr design['Projected Area at Inlet'] = np.pi*np.square(design['Diameter']/2) #in m2 - design['Hydraulic_Loading'] = (self.ins[0].F_vol*24)/design['Area'] #in m3/(m2*day) + design['Hydraulic_Loading'] = (self.ins[0].F_vol*24)/design['Projected Area at Inlet'] #in m3/(m2*day) design['Volume'] = np.pi*np.square(design['Diameter']/2)*(self.h_cylinderical + (self.h_conical/3)) #in m3 design['Curved Surface Area'] = np.pi*design['Diameter']/2*(2*self.h_cylinderical + np.sqrt(np.square(design['Diameter']/2) + np.square(self.h_conical))) #in m2 \ No newline at end of file From b4cc2d78454a99db8199ef97fb7dadae65a2ce6a Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Fri, 7 Oct 2022 14:28:56 -0500 Subject: [PATCH 015/424] Updated primary clarifier Updated the _design() method --- qsdsan/sanunits/_clarifier.py | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index 039e2cd4..64033e22 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -551,29 +551,34 @@ class PrimaryClarifier(SanUnit): outs : class:`WasteStream` Treated effluent and sludge. Hydraulic Retention time : float - Hydraulic Retention Time in days. The default is 0.04268 days, based on IWA report. + Hydraulic Retention Time in days. The default is 0.04268 days, based on IWA report.[1] ratio_uf : float - The ratio of sludge to primary influent. The default is 0.65, based on IWA report. + The ratio of sludge to primary influent. The default is 0.65, based on IWA report.[1] f_corr : float - Dimensionless correction factor for removal efficiency in the primary clarifier. + Dimensionless correction factor for removal efficiency in the primary clarifier.[1] + oveflow_rate : float + The design overflow rate in the primary sedimentation tank. Default value taken from sample design problem.[2] References ---------- .. [1] Gernaey, Krist V., Ulf Jeppsson, Peter A. Vanrolleghem, and John B. Copp. Benchmarking of control strategies for wastewater treatment plants. IWA publishing, 2014. + [2] Metcalf, Leonard, Harrison P. Eddy, and Georg Tchobanoglous. Wastewater + engineering: treatment, disposal, and reuse. Vol. 4. New York: McGraw-Hill, 1991. """ _N_ins = 3 _N_outs = 2 def __init__(self, ID='', ins=None, outs=(), thermo=None, - isdynamic=False, init_with='WasteStream', Hydraulic_Retention_Time = 0.04268, ratio_uf =0.007, - f_corr=0.65, F_BM_default=None, **kwargs): + isdynamic=False, init_with='WasteStream', Hydraulic_Retention_Time=0.04268, + ratio_uf=0.007, f_corr=0.65, F_BM_default=None, oveflow_rate=40, **kwargs): SanUnit.__init__(self, ID, ins, outs, thermo, isdynamic=isdynamic, init_with=init_with, F_BM_default=F_BM_default) - self.Hydraulic_Retention_Time = Hydraulic_Retention_Time + self.Hydraulic_Retention_Time = Hydraulic_Retention_Time #in days self.ratio_uf = ratio_uf self.f_corr = f_corr + self.oveflow_rate = oveflow_rate #in m3/(m2*day) @property def Hydraulic_Retention_Time(self): @@ -649,6 +654,6 @@ def _run(self): def _design(self): design = self.design_results - HRT = self._HRT - Q_inflow = self.mixed.get_total_flow('m3/hr') - design['Volume'] = 24*HRT*Q_inflow \ No newline at end of file + design['Volume'] = 24*self._HRT*self.mixed.get_total_flow('m3/hr') #in m3 + design['Area'] = self.mixed.get_total_flow('m3/hr')/self.oveflow_rate #in m2 + design['Length'] = design['Volume']/design['Area'] #in m \ No newline at end of file From a5183794fe503ae21a51c35b71440e96f0d78ed8 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Wed, 12 Oct 2022 17:05:43 -0500 Subject: [PATCH 016/424] Updated _sludge_treatment() Created new class, Incinerator Add class definition, and parts of the _run() function. Not complete yet. --- qsdsan/sanunits/_sludge_treatment.py | 81 +++++++++++++++++++++++++++- 1 file changed, 80 insertions(+), 1 deletion(-) diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py index 8cc3ebe4..6d39bfd0 100644 --- a/qsdsan/sanunits/_sludge_treatment.py +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -241,4 +241,83 @@ def _design(self): design['Projected Area at Inlet'] = np.pi*np.square(design['Diameter']/2) #in m2 design['Hydraulic_Loading'] = (self.ins[0].F_vol*24)/design['Projected Area at Inlet'] #in m3/(m2*day) design['Volume'] = np.pi*np.square(design['Diameter']/2)*(self.h_cylinderical + (self.h_conical/3)) #in m3 - design['Curved Surface Area'] = np.pi*design['Diameter']/2*(2*self.h_cylinderical + np.sqrt(np.square(design['Diameter']/2) + np.square(self.h_conical))) #in m2 \ No newline at end of file + design['Curved Surface Area'] = np.pi*design['Diameter']/2*(2*self.h_cylinderical + np.sqrt(np.square(design['Diameter']/2) + np.square(self.h_conical))) #in m2 + +class Incinerator(SanUnit): + + _N_ins = 3 + _N_outs = 2 + + def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, + init_with='WasteStream', F_BM_default=None, process_efficiency=0.90, + calorific_value_sludge= 12, calorific_value_fuel=0.05, **kwargs): + SanUnit.__init__(self, ID, ins, outs, thermo, isdynamic=isdynamic, + init_with=init_with, F_BM_default=F_BM_default) + + self.calorific_value_sludge = calorific_value_sludge #in MJ/kg + self.calorific_value_fuel = calorific_value_fuel #in MJ/kg + + @property + def process_efficiency(self): + '''Process efficiency of incinerator.''' + return self._process_efficiency + + @process_efficiency.setter + def process_efficiency(self, process_efficiency): + if process_efficiency is not None: + if process_efficiency>=1 or process_efficiency<=0: + raise ValueError(f'should be between 0 and 1 not {process_efficiency}') + self._process_efficiency = process_efficiency + else: + raise ValueError('Process efficiency of incinerator expected from user') + + @property + def calorific_value_sludge(self): + '''Calorific value of sludge in MJ/kg.''' + return self._calorific_value_sludge + + @calorific_value_sludge.setter + def calorific_value_sludge(self, calorific_value_sludge): + if calorific_value_sludge is not None: + self._calorific_value_sludge = calorific_value_sludge + else: + raise ValueError('Calorific value of sludge expected from user') + + @property + def calorific_value_fuel(self): + '''Calorific value of fuel in MJ/kg.''' + return self._calorific_value_fuel + + @calorific_value_sludge.setter + def calorific_value_sludge(self, calorific_value_fuel): + if calorific_value_fuel is not None: + self._calorific_value_fuel = calorific_value_fuel + else: + raise ValueError('Calorific value of fuel expected from user') + + def _run(self): + + sludge, air, fuel = self.ins + flue_gas, ash = self.outs + + #mass balance + mass_flowrate_sludge = sludge.get_total_flow('kg/hr') + mass_flowrate_air = air.get_total_flow('kg/hr') + mass_flowrate_fuel = fuel.get_total_flow('kg/hr') + mass_ash = sludge.get_ISS()*ash.F_vol/1000 #in kg/hr (mg/l)*(m3/hr) = (1/1000)kg/hr + + # By conservation of mass + + mass_flue_gas = mass_flowrate_sludge+mass_flowrate_air+mass_flowrate_fuel-mass_ash + + Heat_sludge = sludge.dry_mass*sludge.F_vol*self.calorific_value_sludge #in KJ/hr (mg/L)*(m3/hr)*(MJ/kg)=KJ/hr + Cp_air = 1 #(Cp = 1 kJ/kg for air) + Heat_air = mass_flowrate_air*Cp_air #in KJ/hr + Heat_fuel = mass_flowrate_fuel*self.calorific_value_fuel + Heat_flue_gas = self.process_efficiency*(Heat_sludge + Heat_air + Heat_fuel) + + #By conservation of energy + Heat_loss = Heat_sludge+Heat_air+Heat_fuel-Heat_flue_gas + + + \ No newline at end of file From 8847a830aba85430c49d6c499c6765d7ea06c8ce Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Mon, 17 Oct 2022 10:32:05 -0500 Subject: [PATCH 017/424] Updated sludge treatment Edited the mass and energy balance of Incinerator. --- qsdsan/sanunits/_sludge_treatment.py | 134 ++++++++++++++++----------- 1 file changed, 78 insertions(+), 56 deletions(-) diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py index 6d39bfd0..6522ece5 100644 --- a/qsdsan/sanunits/_sludge_treatment.py +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -245,79 +245,101 @@ def _design(self): class Incinerator(SanUnit): + #These are class attributes _N_ins = 3 - _N_outs = 2 + _N_outs = 2 + Cp_air = 1 #(Cp = 1 kJ/kg for air) def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, init_with='WasteStream', F_BM_default=None, process_efficiency=0.90, - calorific_value_sludge= 12, calorific_value_fuel=0.05, **kwargs): + calorific_value_sludge= 12000, calorific_value_fuel=50000, **kwargs): SanUnit.__init__(self, ID, ins, outs, thermo, isdynamic=isdynamic, init_with=init_with, F_BM_default=F_BM_default) self.calorific_value_sludge = calorific_value_sludge #in MJ/kg self.calorific_value_fuel = calorific_value_fuel #in MJ/kg - - @property - def process_efficiency(self): - '''Process efficiency of incinerator.''' - return self._process_efficiency + self.Heat_air = None + self.Heat_fuel = None + self.Heat_flue_gas = None + self.Heat_sludge = None + self.Heat_loss = None + + @property + def process_efficiency(self): + '''Process efficiency of incinerator.''' + return self._process_efficiency - @process_efficiency.setter - def process_efficiency(self, process_efficiency): - if process_efficiency is not None: - if process_efficiency>=1 or process_efficiency<=0: - raise ValueError(f'should be between 0 and 1 not {process_efficiency}') - self._process_efficiency = process_efficiency - else: - raise ValueError('Process efficiency of incinerator expected from user') - - @property - def calorific_value_sludge(self): - '''Calorific value of sludge in MJ/kg.''' - return self._calorific_value_sludge + @process_efficiency.setter + def process_efficiency(self, process_efficiency): + if process_efficiency is not None: + if process_efficiency>=1 or process_efficiency<=0: + raise ValueError(f'should be between 0 and 1 not {process_efficiency}') + self._process_efficiency = process_efficiency + else: + raise ValueError('Process efficiency of incinerator expected from user') + + @property + def calorific_value_sludge(self): + '''Calorific value of sludge in KJ/kg.''' + return self._calorific_value_sludge - @calorific_value_sludge.setter - def calorific_value_sludge(self, calorific_value_sludge): - if calorific_value_sludge is not None: - self._calorific_value_sludge = calorific_value_sludge - else: - raise ValueError('Calorific value of sludge expected from user') - - @property - def calorific_value_fuel(self): - '''Calorific value of fuel in MJ/kg.''' - return self._calorific_value_fuel + @calorific_value_sludge.setter + def calorific_value_sludge(self, calorific_value_sludge): + if calorific_value_sludge is not None: + self._calorific_value_sludge = calorific_value_sludge + else: + raise ValueError('Calorific value of sludge expected from user') + + @property + def calorific_value_fuel(self): + '''Calorific value of fuel in KJ/kg.''' + return self._calorific_value_fuel - @calorific_value_sludge.setter - def calorific_value_sludge(self, calorific_value_fuel): - if calorific_value_fuel is not None: - self._calorific_value_fuel = calorific_value_fuel - else: - raise ValueError('Calorific value of fuel expected from user') + @calorific_value_fuel.setter + def calorific_value_fuel(self, calorific_value_fuel): + if calorific_value_fuel is not None: + self._calorific_value_fuel = calorific_value_fuel + else: + raise ValueError('Calorific value of fuel expected from user') + def _run(self): - sludge, air, fuel = self.ins - flue_gas, ash = self.outs - - #mass balance - mass_flowrate_sludge = sludge.get_total_flow('kg/hr') - mass_flowrate_air = air.get_total_flow('kg/hr') - mass_flowrate_fuel = fuel.get_total_flow('kg/hr') - mass_ash = sludge.get_ISS()*ash.F_vol/1000 #in kg/hr (mg/l)*(m3/hr) = (1/1000)kg/hr - - # By conservation of mass - - mass_flue_gas = mass_flowrate_sludge+mass_flowrate_air+mass_flowrate_fuel-mass_ash + sludge, air, fuel = self.ins + flue_gas, ash = self.outs + flue_gas.phase = 'g' + ash.phase = 's' + + if sludge.phase != 'l': + raise ValueError(f'The phase of incoming sludge is expected to be liquid not {sludge.phase}') + if air.phase != 'g': + raise ValueError(f'The phase of air is expected to be gas not {air.phase}') + if fuel.phase != 'g': + raise ValueError(f'The phase of fuel is expected to be gas not {fuel.phase}') + + #mass balance + mass_flowrate_sludge = sludge.get_total_flow('kg/hr') + mass_flowrate_air = air.get_total_flow('kg/hr') + mass_flowrate_fuel = fuel.get_total_flow('kg/hr') + mass_ash = sludge.get_ISS()*sludge.F_vol/1000 #in kg/hr (mg/l)*(m3/hr) = (1/1000)kg/hr + + # By conservation of mass + mass_flue_gas = mass_flowrate_sludge + mass_flowrate_air + mass_flowrate_fuel - mass_ash + + #energy balance + self.Heat_sludge = sludge.dry_mass*sludge.F_vol*self.calorific_value_sludge/1000 #in KJ/hr (mg/L)*(m3/hr)*(KJ/kg)=KJ/hr*(1/1000) + self.Heat_air = mass_flowrate_air*self.Cp_air #in KJ/hr + self.Heat_fuel = mass_flowrate_fuel*self.calorific_value_fuel #in KJ/hr + self.Heat_flue_gas = self.process_efficiency*(self.Heat_sludge + self.Heat_air + self.Heat_fuel) + + #By conservation of energy + self.Heat_loss = self.Heat_sludge + self.Heat_air + self.Heat_fuel - self.Heat_flue_gas + + #check if this method (set_flow) would work for non-arrays + flue_gas.set_flow(mass_flue_gas,'kg/hr') + ash.set_flow(mass_ash,'kg/hr') - Heat_sludge = sludge.dry_mass*sludge.F_vol*self.calorific_value_sludge #in KJ/hr (mg/L)*(m3/hr)*(MJ/kg)=KJ/hr - Cp_air = 1 #(Cp = 1 kJ/kg for air) - Heat_air = mass_flowrate_air*Cp_air #in KJ/hr - Heat_fuel = mass_flowrate_fuel*self.calorific_value_fuel - Heat_flue_gas = self.process_efficiency*(Heat_sludge + Heat_air + Heat_fuel) - #By conservation of energy - Heat_loss = Heat_sludge+Heat_air+Heat_fuel-Heat_flue_gas \ No newline at end of file From facfa24abdbfc588dca7bd61e0a0eea57fa49fb0 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Mon, 17 Oct 2022 11:48:29 -0500 Subject: [PATCH 018/424] Updated Sludge treatment Added documentation to the Incinerator class --- qsdsan/sanunits/_sludge_treatment.py | 46 +++++++++++++++++++++------- 1 file changed, 35 insertions(+), 11 deletions(-) diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py index 6522ece5..a2a6dfc7 100644 --- a/qsdsan/sanunits/_sludge_treatment.py +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -16,7 +16,7 @@ from .. import SanUnit import numpy as np -__all__ = ('Thickener', 'DewateringUnit',) +__all__ = ('Thickener', 'DewateringUnit', 'Incinerator') class Thickener(SanUnit): @@ -245,6 +245,36 @@ def _design(self): class Incinerator(SanUnit): + """ + Fluidized bed incinerator unit for metroWWTP. + + Parameters + ---------- + ID : str + ID for the Incinerator Unit. The default is ''. + ins : class:`WasteStream` + Influent to the Incinerator Unit. Expected number of influent streams are 3. + outs : class:`WasteStream` + Flue gas and ash. + thickener_perc : float + The percentage of Suspended Sludge in the underflow of the dewatering unit. + process_efficiency : float + The process efficiency of the incinerator unit. Expected value between 0 and 1. + calorific_value_sludge : float + The calorific value of influent sludge in KJ/kg. The default value used is 12000 KJ/kg. + calorific_value_fuel : float + The calorific value of fuel employed for combustion in KJ/kg. + The default fuel is natural gas with calofific value of 50000 KJ/kg. + + References + ---------- + .. [1] Khuriati, A., P. Purwanto, H. S. Huboyo, Suryono Sumariyah, S. Suryono, and A. B. Putranto. + "Numerical calculation based on mass and energy balance of waste incineration in the fixed bed reactor." + In Journal of Physics: Conference Series, vol. 1524, no. 1, p. 012002. IOP Publishing, 2020. + [2] Omari, Arthur, Karoli N. Njau, Geoffrey R. John, Joseph H. Kihedu, and Peter L. Mtui. + "Mass And Energy Balance For Fixed Bed Incinerators A case of a locally designed incinerator in Tanzania." + """ + #These are class attributes _N_ins = 3 _N_outs = 2 @@ -256,12 +286,12 @@ def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, SanUnit.__init__(self, ID, ins, outs, thermo, isdynamic=isdynamic, init_with=init_with, F_BM_default=F_BM_default) - self.calorific_value_sludge = calorific_value_sludge #in MJ/kg - self.calorific_value_fuel = calorific_value_fuel #in MJ/kg + self.calorific_value_sludge = calorific_value_sludge #in KJ/kg + self.calorific_value_fuel = calorific_value_fuel #in KJ/kg (here the considered fuel is natural gas) self.Heat_air = None self.Heat_fuel = None - self.Heat_flue_gas = None self.Heat_sludge = None + self.Heat_flue_gas = None self.Heat_loss = None @property @@ -302,7 +332,6 @@ def calorific_value_fuel(self, calorific_value_fuel): else: raise ValueError('Calorific value of fuel expected from user') - def _run(self): sludge, air, fuel = self.ins @@ -337,9 +366,4 @@ def _run(self): #check if this method (set_flow) would work for non-arrays flue_gas.set_flow(mass_flue_gas,'kg/hr') - ash.set_flow(mass_ash,'kg/hr') - - - - - \ No newline at end of file + ash.set_flow(mass_ash,'kg/hr') \ No newline at end of file From b80552e7df451af84aec283a0040cf3244e0a87a Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Mon, 17 Oct 2022 13:36:05 -0500 Subject: [PATCH 019/424] Updated Sludge Treatment Fixed bug related to the property 'process_efficiency' --- qsdsan/sanunits/_sludge_treatment.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py index a2a6dfc7..1bd6a0a9 100644 --- a/qsdsan/sanunits/_sludge_treatment.py +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -288,6 +288,7 @@ def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, self.calorific_value_sludge = calorific_value_sludge #in KJ/kg self.calorific_value_fuel = calorific_value_fuel #in KJ/kg (here the considered fuel is natural gas) + self.process_efficiency = process_efficiency self.Heat_air = None self.Heat_fuel = None self.Heat_sludge = None From 028246865abcbc2b2cc94d2d7e7384fc289ea37f Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Thu, 20 Oct 2022 16:37:03 -0500 Subject: [PATCH 020/424] Updated sludge treatment Corrected mass balance expressions for mass flowrate sludge, mass flowrate air, and mass flowrate fuel. --- qsdsan/sanunits/_sludge_treatment.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py index 1bd6a0a9..291d7f91 100644 --- a/qsdsan/sanunits/_sludge_treatment.py +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -254,6 +254,7 @@ class Incinerator(SanUnit): ID for the Incinerator Unit. The default is ''. ins : class:`WasteStream` Influent to the Incinerator Unit. Expected number of influent streams are 3. + Please remember the order of influents as {wastestream, air, fuel} outs : class:`WasteStream` Flue gas and ash. thickener_perc : float @@ -266,7 +267,7 @@ class Incinerator(SanUnit): The calorific value of fuel employed for combustion in KJ/kg. The default fuel is natural gas with calofific value of 50000 KJ/kg. - References + References: ---------- .. [1] Khuriati, A., P. Purwanto, H. S. Huboyo, Suryono Sumariyah, S. Suryono, and A. B. Putranto. "Numerical calculation based on mass and energy balance of waste incineration in the fixed bed reactor." @@ -274,7 +275,7 @@ class Incinerator(SanUnit): [2] Omari, Arthur, Karoli N. Njau, Geoffrey R. John, Joseph H. Kihedu, and Peter L. Mtui. "Mass And Energy Balance For Fixed Bed Incinerators A case of a locally designed incinerator in Tanzania." """ - + #These are class attributes _N_ins = 3 _N_outs = 2 @@ -282,13 +283,15 @@ class Incinerator(SanUnit): def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, init_with='WasteStream', F_BM_default=None, process_efficiency=0.90, - calorific_value_sludge= 12000, calorific_value_fuel=50000, **kwargs): + calorific_value_sludge= 12000, calorific_value_fuel=50000, + ash_component_ID = 'X_Ig_ISS', **kwargs): SanUnit.__init__(self, ID, ins, outs, thermo, isdynamic=isdynamic, init_with=init_with, F_BM_default=F_BM_default) self.calorific_value_sludge = calorific_value_sludge #in KJ/kg self.calorific_value_fuel = calorific_value_fuel #in KJ/kg (here the considered fuel is natural gas) self.process_efficiency = process_efficiency + self.ash_component_ID = ash_component_ID self.Heat_air = None self.Heat_fuel = None self.Heat_sludge = None @@ -334,11 +337,12 @@ def calorific_value_fuel(self, calorific_value_fuel): raise ValueError('Calorific value of fuel expected from user') def _run(self): - + sludge, air, fuel = self.ins flue_gas, ash = self.outs flue_gas.phase = 'g' ash.phase = 's' + cmps = self.components if sludge.phase != 'l': raise ValueError(f'The phase of incoming sludge is expected to be liquid not {sludge.phase}') @@ -348,9 +352,9 @@ def _run(self): raise ValueError(f'The phase of fuel is expected to be gas not {fuel.phase}') #mass balance - mass_flowrate_sludge = sludge.get_total_flow('kg/hr') - mass_flowrate_air = air.get_total_flow('kg/hr') - mass_flowrate_fuel = fuel.get_total_flow('kg/hr') + mass_flowrate_sludge = np.sum(sludge.mass*cmps.i_mass) + mass_flowrate_air = np.sum(air.mass*cmps.i_mass) + mass_flowrate_fuel = np.sum(fuel.mass*cmps.i_mass) mass_ash = sludge.get_ISS()*sludge.F_vol/1000 #in kg/hr (mg/l)*(m3/hr) = (1/1000)kg/hr # By conservation of mass @@ -364,7 +368,6 @@ def _run(self): #By conservation of energy self.Heat_loss = self.Heat_sludge + self.Heat_air + self.Heat_fuel - self.Heat_flue_gas - - #check if this method (set_flow) would work for non-arrays - flue_gas.set_flow(mass_flue_gas,'kg/hr') - ash.set_flow(mass_ash,'kg/hr') \ No newline at end of file + + flue_gas.set_flow( [air.imass['N2'], sludge.imass['H2O'], mass_flue_gas - air.imass['N2'] - sludge.imass['H2O']], 'kg/hr', ('S_N2', 'H2O', 'S_CO2')) + ash.set_flow([mass_ash], 'kg/hr', (self.ash_component_ID)) \ No newline at end of file From a4d8d91439e35ba64e1db4e2e6dae3cda937fce8 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Wed, 26 Oct 2022 17:33:38 -0500 Subject: [PATCH 021/424] Modified primary clarifier Added the _init_state() function to primary clarifier --- qsdsan/sanunits/_clarifier.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index 64033e22..656d369b 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -647,7 +647,6 @@ def _run(self): Ce = Ze + Xe Cs = Zs + Xs - of.set_flow(Ce,'kg/hr') uf.set_flow(Cs,'kg/hr') @@ -656,4 +655,16 @@ def _design(self): design = self.design_results design['Volume'] = 24*self._HRT*self.mixed.get_total_flow('m3/hr') #in m3 design['Area'] = self.mixed.get_total_flow('m3/hr')/self.oveflow_rate #in m2 - design['Length'] = design['Volume']/design['Area'] #in m \ No newline at end of file + design['Length'] = design['Volume']/design['Area'] #in m + + def _init_state(self): + if self._ins_QC.shape[0] <= 1: + # if only 1 inlet then simply copy the state of the influent wastestream + self._state = self._ins_QC[0] + else: + # if multiple wastestreams exist then concentration and total inlow + # would be calculated assumping perfect mixing + Qs = self._ins_QC[:,-1] + Cs = self._ins_QC[:,:-1] + self._state = np.append(Qs @ Cs / Qs.sum(), Qs.sum()) + self._dstate = self._state * 0. \ No newline at end of file From 6bafb99069f5b606ebb662def8a1cace3cc504e4 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Wed, 26 Oct 2022 17:34:48 -0500 Subject: [PATCH 022/424] Added _init__state() Added the _init_state() function to all units in Sludge Treatment --- qsdsan/sanunits/_sludge_treatment.py | 54 +++++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py index 291d7f91..0015f0a9 100644 --- a/qsdsan/sanunits/_sludge_treatment.py +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -158,6 +158,46 @@ def _design(self): design['Diameter'] = np.sqrt(4*design['Area']/np.pi) #in m design['Volume'] = np.pi*np.square(design['Diameter']/2)*self.h_cylinderical #in m3 design['Curved Surface Area'] = np.pi*design['Diameter']*self.h_cylinderical #in m2 + + def _init_state(self): + if self._ins_QC.shape[0] <= 1: + # if only 1 inlet then simply copy the state of the influent wastestream + self._state = self._ins_QC[0] + else: + # if multiple wastestreams exist then concentration and total inlow + # would be calculated assumping perfect mixing + Qs = self._ins_QC[:,-1] + Cs = self._ins_QC[:,:-1] + self._state = np.append(Qs @ Cs / Qs.sum(), Qs.sum()) + self._dstate = self._state * 0. + +# ============================================================================= +# def _update_state(self): +# +# #self._outs[0].state = self._state +# +# def _update_dstate(self): +# +# #self._outs[0].dstate = self._dstate +# +# @property +# def AE(self): +# if self._AE is None: +# self._compile_AE() +# return self._AE +# +# def _compile_AE(self): +# _state = self._state +# _dstate = self._dstate +# _update_state = self._update_state +# _update_dstate = self._update_dstate +# def yt(t, QC_ins, dQC_ins): +# +# _update_state() +# _update_dstate() +# self._AE = yt +# ============================================================================= + class DewateringUnit(Thickener): @@ -370,4 +410,16 @@ def _run(self): self.Heat_loss = self.Heat_sludge + self.Heat_air + self.Heat_fuel - self.Heat_flue_gas flue_gas.set_flow( [air.imass['N2'], sludge.imass['H2O'], mass_flue_gas - air.imass['N2'] - sludge.imass['H2O']], 'kg/hr', ('S_N2', 'H2O', 'S_CO2')) - ash.set_flow([mass_ash], 'kg/hr', (self.ash_component_ID)) \ No newline at end of file + ash.set_flow([mass_ash], 'kg/hr', (self.ash_component_ID)) + + def _init_state(self): + if self._ins_QC.shape[0] <= 1: + # if only 1 inlet then simply copy the state of the influent wastestream + self._state = self._ins_QC[0] + else: + # if multiple wastestreams exist then concentration and total inlow + # would be calculated assumping perfect mixing + Qs = self._ins_QC[:,-1] + Cs = self._ins_QC[:,:-1] + self._state = np.append(Qs @ Cs / Qs.sum(), Qs.sum()) + self._dstate = self._state * 0. \ No newline at end of file From b50f0e9f7d86951a091cb5baffc0902d1a31ecf0 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Thu, 27 Oct 2022 15:47:27 -0500 Subject: [PATCH 023/424] Added functions for dynamic simulations Added _init_state(), _update_state(), _update_dstate(), and _compile_AE() functions. --- qsdsan/sanunits/_clarifier.py | 65 +++++++++++++++++++++++++++++------ 1 file changed, 54 insertions(+), 11 deletions(-) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index 656d369b..a3a58f68 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -4,7 +4,8 @@ This module is developed by: Joy Zhang - Yalin Li + Yalin Li + Saumitra Rai This module is under the University of Illinois/NCSA Open Source License. Please refer to https://github.com/QSD-Group/QSDsan/blob/main/LICENSE.txt @@ -658,13 +659,55 @@ def _design(self): design['Length'] = design['Volume']/design['Area'] #in m def _init_state(self): - if self._ins_QC.shape[0] <= 1: - # if only 1 inlet then simply copy the state of the influent wastestream - self._state = self._ins_QC[0] - else: - # if multiple wastestreams exist then concentration and total inlow - # would be calculated assumping perfect mixing - Qs = self._ins_QC[:,-1] - Cs = self._ins_QC[:,:-1] - self._state = np.append(Qs @ Cs / Qs.sum(), Qs.sum()) - self._dstate = self._state * 0. \ No newline at end of file + # if multiple wastestreams exist then concentration and total inlow + # would be calculated assumping perfect mixing + Qs = self._ins_QC[:,-1] + Cs = self._ins_QC[:,:-1] + self._state = np.append(Qs @ Cs / Qs.sum(), Qs.sum()) + self._dstate = self._state * 0. + + uf, of = self.outs + s_flow = uf.F_vol/(uf.F_vol+of.F_vol) + s = uf.mass/(uf.mass + of.mass) + self._sludge = np.append(s/s_flow, s_flow) + self._effluent = np.append((1-s)/(1-s_flow), 1-s_flow) + + def _update_state(self): + '''updates conditions of output stream based on conditions of the Primary Clarifier''' + self._outs[0].state = self._sludge * self._state + self._outs[1].state = self._effluent * self._state + + def _update_dstate(self): + '''updates rates of change of output stream from rates of change of the Primary Clarifier''' + self._outs[0].dstate = self._sludge * self._dstate + self._outs[1].dstate = self._effluent * self._dstate + + @property + def AE(self): + if self._AE is None: + self._compile_AE() + return self._AE + + def _compile_AE(self): + _state = self._state + _dstate = self._dstate + _update_state = self._update_state + _update_dstate = self._update_dstate + def yt(t, QC_ins, dQC_ins): + #Because there are multiple inlets + Q_ins = QC_ins[:, -1] + C_ins = QC_ins[:, :-1] + dQ_ins = dQC_ins[:, -1] + dC_ins = dQC_ins[:, :-1] + Q = Q_ins.sum() + C = Q_ins @ C_ins / Q + _state[-1] = Q + _state[:-1] = C + Q_dot = dQ_ins.sum() + C_dot = (dQ_ins @ C_ins + Q_ins @ dC_ins - Q_dot * C)/Q + _dstate[-1] = Q_dot + _dstate[:-1] = C_dot + _update_state() + _update_dstate() + self._AE = yt + \ No newline at end of file From 43c6f3a6bae04f282798b11e5713a007cb5bfa66 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Thu, 27 Oct 2022 15:49:55 -0500 Subject: [PATCH 024/424] Updated dynamic simulation functions Updated the _init_state() function, & added compile_AE, update_state, and update_dstate functions. --- qsdsan/sanunits/_sludge_treatment.py | 86 +++++++++++++--------------- 1 file changed, 40 insertions(+), 46 deletions(-) diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py index 0015f0a9..5f5e35cf 100644 --- a/qsdsan/sanunits/_sludge_treatment.py +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -160,46 +160,44 @@ def _design(self): design['Curved Surface Area'] = np.pi*design['Diameter']*self.h_cylinderical #in m2 def _init_state(self): - if self._ins_QC.shape[0] <= 1: - # if only 1 inlet then simply copy the state of the influent wastestream - self._state = self._ins_QC[0] - else: - # if multiple wastestreams exist then concentration and total inlow - # would be calculated assumping perfect mixing - Qs = self._ins_QC[:,-1] - Cs = self._ins_QC[:,:-1] - self._state = np.append(Qs @ Cs / Qs.sum(), Qs.sum()) + # if only 1 inlet then simply copy the state of the influent wastestream + self._state = self._ins_QC[0] self._dstate = self._state * 0. -# ============================================================================= -# def _update_state(self): -# -# #self._outs[0].state = self._state -# -# def _update_dstate(self): -# -# #self._outs[0].dstate = self._dstate -# -# @property -# def AE(self): -# if self._AE is None: -# self._compile_AE() -# return self._AE -# -# def _compile_AE(self): -# _state = self._state -# _dstate = self._dstate -# _update_state = self._update_state -# _update_dstate = self._update_dstate -# def yt(t, QC_ins, dQC_ins): -# -# _update_state() -# _update_dstate() -# self._AE = yt -# ============================================================================= - + uf, of = self.outs + s_flow = uf.F_vol/(uf.F_vol+of.F_vol) + s = uf.mass/(uf.mass + of.mass) + self._sludge = np.append(s/s_flow, s_flow) + self._effluent = np.append((1-s)/(1-s_flow), 1-s_flow) - + def _update_state(self): + '''updates conditions of output stream based on conditions of the Thickener''' + self._outs[0].state = self._sludge * self._state + self._outs[1].state = self._effluent * self._state + + def _update_dstate(self): + '''updates rates of change of output stream from rates of change of the Thickener''' + self._outs[0].dstate = self._sludge * self._dstate + self._outs[1].dstate = self._effluent * self._dstate + + @property + def AE(self): + if self._AE is None: + self._compile_AE() + return self._AE + + def _compile_AE(self): + _state = self._state + _dstate = self._dstate + _update_state = self._update_state + _update_dstate = self._update_dstate + def yt(t, QC_ins, dQC_ins): + _state[:] = QC_ins[0] + _dstate[:] = dQC_ins[0] + _update_state() + _update_dstate() + self._AE = yt + class DewateringUnit(Thickener): """ @@ -413,13 +411,9 @@ def _run(self): ash.set_flow([mass_ash], 'kg/hr', (self.ash_component_ID)) def _init_state(self): - if self._ins_QC.shape[0] <= 1: - # if only 1 inlet then simply copy the state of the influent wastestream - self._state = self._ins_QC[0] - else: - # if multiple wastestreams exist then concentration and total inlow - # would be calculated assumping perfect mixing - Qs = self._ins_QC[:,-1] - Cs = self._ins_QC[:,:-1] - self._state = np.append(Qs @ Cs / Qs.sum(), Qs.sum()) + # if multiple wastestreams exist then concentration and total inlow + # would be calculated assumping perfect mixing + Qs = self._ins_QC[:,-1] + Cs = self._ins_QC[:,:-1] + self._state = np.append(Qs @ Cs / Qs.sum(), Qs.sum()) self._dstate = self._state * 0. \ No newline at end of file From c04262e66e8a989bf007d71a0b25871aadf1e1ce Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Fri, 28 Oct 2022 14:30:38 -0500 Subject: [PATCH 025/424] Updated Primary Clarifier Added test function --- qsdsan/sanunits/_clarifier.py | 81 ++++++++++++++++++++++++++++++++--- 1 file changed, 76 insertions(+), 5 deletions(-) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index a3a58f68..9ba9a052 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -558,8 +558,80 @@ class PrimaryClarifier(SanUnit): f_corr : float Dimensionless correction factor for removal efficiency in the primary clarifier.[1] oveflow_rate : float - The design overflow rate in the primary sedimentation tank. Default value taken from sample design problem.[2] - + The design overflow rate in the primary sedimentation tank. + Default value taken from sample design problem. Unit in m/hr[2] + + Examples + -------- + >>> from qsdsan import set_thermo, Components, WasteStream + >>> cmps = Components.load_default() + >>> cmps_test = cmps.subgroup(['S_F', 'S_NH4', 'X_OHO', 'H2O']) + >>> set_thermo(cmps_test) + >>> ws = WasteStream('ws', S_F = 10, S_NH4 = 20, X_OHO = 15, H2O=1000) + >>> from qsdsan.sanunits import PrimaryClarifier + >>> ps = PrimaryClarifier(ID='PC', ins= (ws), outs=()) + >>> ps._run() + >>> uf, of = ps.outs + >>> uf.imass['X_OHO']/ws.imass['X_OHO'] # doctest: +ELLIPSIS + 0.280... + >>> ps + PrimaryClarifier: PC + ins... + [0] ws + phase: 'l', T: 298.15 K, P: 101325 Pa + flow (g/hr): S_F 1e+04 + S_NH4 2e+04 + X_OHO 1.5e+04 + H2O 1e+06 + WasteStream-specific properties: + pH : 7.0 + COD : 23643.1 mg/L + BOD : 14819.1 mg/L + TC : 8218.3 mg/L + TOC : 8218.3 mg/L + TN : 20167.1 mg/L + TP : 364.1 mg/L + TK : 67.6 mg/L + [1] ws3 + phase: 'l', T: 298.15 K, P: 101325 Pa + flow: 0 + WasteStream-specific properties: None for empty waste streams + [2] ws4 + phase: 'l', T: 298.15 K, P: 101325 Pa + flow: 0 + WasteStream-specific properties: None for empty waste streams + outs... + [0] ws1 + phase: 'l', T: 298.15 K, P: 101325 Pa + flow (g/hr): S_F 70 + S_NH4 140 + X_OHO 4.2e+03 + H2O 7e+03 + WasteStream-specific properties: + pH : 7.0 + COD : 425826.3 mg/L + BOD : 242338.7 mg/L + TC : 155531.6 mg/L + TOC : 155531.6 mg/L + TN : 42767.0 mg/L + TP : 8027.9 mg/L + TK : 1997.1 mg/L + [1] ws2 + phase: 'l', T: 298.15 K, P: 101325 Pa + flow (g/hr): S_F 9.93e+03 + S_NH4 1.99e+04 + X_OHO 1.08e+04 + H2O 9.93e+05 + WasteStream-specific properties: + pH : 7.0 + COD : 19789.4 mg/L + BOD : 12639.0 mg/L + TC : 6806.8 mg/L + TOC : 6806.8 mg/L + TN : 19950.5 mg/L + TP : 290.7 mg/L + TK : 49.2 mg/L + References ---------- .. [1] Gernaey, Krist V., Ulf Jeppsson, Peter A. Vanrolleghem, and John B. Copp. @@ -580,6 +652,7 @@ def __init__(self, ID='', ins=None, outs=(), thermo=None, self.ratio_uf = ratio_uf self.f_corr = f_corr self.oveflow_rate = oveflow_rate #in m3/(m2*day) + self.mixed = WasteStream('mixed') @property def Hydraulic_Retention_Time(self): @@ -634,7 +707,6 @@ def _run(self): q_inf, to, do = self.ins uf, of = self.outs cmps = self.components - self.mixed = WasteStream('mixed') self.mixed.mix_from(self.ins) r = self._r @@ -709,5 +781,4 @@ def yt(t, QC_ins, dQC_ins): _dstate[:-1] = C_dot _update_state() _update_dstate() - self._AE = yt - \ No newline at end of file + self._AE = yt \ No newline at end of file From 04bacd3e4056e0246ed9adab68fa1fa769d2da5a Mon Sep 17 00:00:00 2001 From: Joy Cheung Date: Fri, 28 Oct 2022 15:08:57 -0500 Subject: [PATCH 026/424] Update _clarifier.py --- qsdsan/sanunits/_clarifier.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index 9ba9a052..65b98455 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -641,6 +641,7 @@ class PrimaryClarifier(SanUnit): """ _N_ins = 3 _N_outs = 2 + _ins_size_is_fixed = False def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, init_with='WasteStream', Hydraulic_Retention_Time=0.04268, @@ -703,8 +704,6 @@ def _f_i(self): return f_i def _run(self): - - q_inf, to, do = self.ins uf, of = self.outs cmps = self.components self.mixed.mix_from(self.ins) @@ -740,7 +739,9 @@ def _init_state(self): uf, of = self.outs s_flow = uf.F_vol/(uf.F_vol+of.F_vol) - s = uf.mass/(uf.mass + of.mass) + denominator = uf.mass + of.mass + denominator += (denominator == 0) + s = uf.mass/denominator self._sludge = np.append(s/s_flow, s_flow) self._effluent = np.append((1-s)/(1-s_flow), 1-s_flow) From bd679bb7fef3c50d7bf83e733af32e2c1fe4ce59 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Fri, 28 Oct 2022 16:17:35 -0500 Subject: [PATCH 027/424] update sludge treatment edited the _init_state function in dewatering unit to account for a bug --- qsdsan/sanunits/_sludge_treatment.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py index 5f5e35cf..bb6485ea 100644 --- a/qsdsan/sanunits/_sludge_treatment.py +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -166,7 +166,9 @@ def _init_state(self): uf, of = self.outs s_flow = uf.F_vol/(uf.F_vol+of.F_vol) - s = uf.mass/(uf.mass + of.mass) + denominator = uf.mass + of.mass + denominator += (denominator == 0) + s = uf.mass/denominator self._sludge = np.append(s/s_flow, s_flow) self._effluent = np.append((1-s)/(1-s_flow), 1-s_flow) From 5509c9c47eaab9cbec10456401892372f72653a2 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Mon, 31 Oct 2022 14:36:11 -0500 Subject: [PATCH 028/424] Added documentation Added example in documentation of Thickener unit --- qsdsan/sanunits/_sludge_treatment.py | 65 ++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py index bb6485ea..4671655f 100644 --- a/qsdsan/sanunits/_sludge_treatment.py +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -39,6 +39,71 @@ class Thickener(SanUnit): Solid loading rate in the thickener.[2] h_cylinderical = float Height of cylinder forming the thickener.[2] + + + Examples + -------- + + >>> from qsdsan import set_thermo, Components, WasteStream + >>> cmps = Components.load_default() + >>> cmps_test = cmps.subgroup(['S_F', 'S_NH4', 'X_OHO', 'H2O']) + >>> set_thermo(cmps_test) + >>> ws = WasteStream('ws', S_F = 10, S_NH4 = 20, X_OHO = 15, H2O=1000) + >>> from qsdsan.sanunits import Thickener + >>> ps = Thickener(ID='TC', ins= (ws), outs=('Sludge', 'Effluent')) + >>> ps._run() + >>> uf, of = ps.outs + >>> uf.imass['X_OHO']/ws.imass['X_OHO'] # doctest: +ELLIPSIS + 0.98 + >>> ps + Thickener: TC + ins... + [0] ws + phase: 'l', T: 298.15 K, P: 101325 Pa + flow (g/hr): S_F 1e+04 + S_NH4 2e+04 + X_OHO 1.5e+04 + H2O 1e+06 + WasteStream-specific properties: + pH : 7.0 + COD : 23643.1 mg/L + BOD : 14819.1 mg/L + TC : 8218.3 mg/L + TOC : 8218.3 mg/L + TN : 20167.1 mg/L + TP : 364.1 mg/L + TK : 67.6 mg/L + outs... + [0] Sludge + phase: 'l', T: 298.15 K, P: 101325 Pa + flow (g/hr): S_F 8.46e+03 + S_NH4 1.69e+04 + X_OHO 1.47e+04 + H2O 8.46e+05 + WasteStream-specific properties: + pH : 7.0 + COD : 25857.3 mg/L + BOD : 16071.7 mg/L + TC : 9029.4 mg/L + TOC : 9029.4 mg/L + TN : 20291.5 mg/L + TP : 406.3 mg/L + TK : 78.3 mg/L + [1] Effluent + phase: 'l', T: 298.15 K, P: 101325 Pa + flow (g/hr): S_F 1.54e+03 + S_NH4 3.08e+03 + X_OHO 300 + H2O 1.54e+05 + WasteStream-specific properties: + pH : 7.0 + COD : 11387.0 mg/L + BOD : 7885.7 mg/L + TC : 3729.1 mg/L + TOC : 3729.1 mg/L + TN : 19478.3 mg/L + TP : 130.6 mg/L + TK : 8.8 mg/L References ---------- From d1ecfc1d8228675425b424597354cf56f8c1ff11 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Mon, 31 Oct 2022 15:28:18 -0500 Subject: [PATCH 029/424] Modified incinerator Added dynamic simulation functions to incinerator --- qsdsan/sanunits/_sludge_treatment.py | 51 ++++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py index 4671655f..2c89fe85 100644 --- a/qsdsan/sanunits/_sludge_treatment.py +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -479,8 +479,55 @@ def _run(self): def _init_state(self): # if multiple wastestreams exist then concentration and total inlow - # would be calculated assumping perfect mixing + # would be calculated assuming perfect mixing Qs = self._ins_QC[:,-1] Cs = self._ins_QC[:,:-1] self._state = np.append(Qs @ Cs / Qs.sum(), Qs.sum()) - self._dstate = self._state * 0. \ No newline at end of file + self._dstate = self._state * 0. + + flue_gas, ash = self.outs + s_flow = flue_gas.F_vol/(flue_gas.F_vol + ash.F_vol) + denominator = flue_gas.mass + ash.mass + denominator += (denominator == 0) + s = flue_gas.mass/denominator + self._flue_gas = np.append(s/s_flow, s_flow) + self._ash = np.append((1-s)/(1-s_flow), 1-s_flow) + + def _update_state(self): + '''updates conditions of output stream based on conditions of the Thickener''' + self._outs[0].state = self._sludge * self._state + self._outs[1].state = self._effluent * self._state + + def _update_dstate(self): + '''updates rates of change of output stream from rates of change of the Thickener''' + self._outs[0].dstate = self._sludge * self._dstate + self._outs[1].dstate = self._effluent * self._dstate + + @property + def AE(self): + if self._AE is None: + self._compile_AE() + return self._AE + + def _compile_AE(self): + _state = self._state + _dstate = self._dstate + _update_state = self._update_state + _update_dstate = self._update_dstate + def yt(t, QC_ins, dQC_ins): + #Because there are multiple inlets + Q_ins = QC_ins[:, -1] + C_ins = QC_ins[:, :-1] + dQ_ins = dQC_ins[:, -1] + dC_ins = dQC_ins[:, :-1] + Q = Q_ins.sum() + C = Q_ins @ C_ins / Q + _state[-1] = Q + _state[:-1] = C + Q_dot = dQ_ins.sum() + C_dot = (dQ_ins @ C_ins + Q_ins @ dC_ins - Q_dot * C)/Q + _dstate[-1] = Q_dot + _dstate[:-1] = C_dot + _update_state() + _update_dstate() + self._AE = yt \ No newline at end of file From 6d6f6ccd7aff532a7a60d2fe8ee7b255c63037be Mon Sep 17 00:00:00 2001 From: Joy Cheung Date: Mon, 31 Oct 2022 15:58:42 -0500 Subject: [PATCH 030/424] debug incinerator --- qsdsan/_waste_stream.py | 6 +++++- qsdsan/sanunits/_sludge_treatment.py | 18 +++++++++++------- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/qsdsan/_waste_stream.py b/qsdsan/_waste_stream.py index b042750a..658c3649 100644 --- a/qsdsan/_waste_stream.py +++ b/qsdsan/_waste_stream.py @@ -1207,7 +1207,11 @@ def scope(self, s): self._scope = s def _init_state(self): - self.state = np.append(self.conc.astype('float64'), self.get_total_flow('m3/d')) + if self.phase == 'l': + self.state = np.append(self.conc.astype('float64'), self.get_total_flow('m3/d')) + else: + Q = self.F_vol # m3/hr + self.state = np.append(self.mass.astype('float64')/Q*1e3, Q*24) self.dstate = np.zeros_like(self.state) def _state2flows(self): diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py index 2c89fe85..eddd5e6f 100644 --- a/qsdsan/sanunits/_sludge_treatment.py +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -473,9 +473,13 @@ def _run(self): #By conservation of energy self.Heat_loss = self.Heat_sludge + self.Heat_air + self.Heat_fuel - self.Heat_flue_gas - - flue_gas.set_flow( [air.imass['N2'], sludge.imass['H2O'], mass_flue_gas - air.imass['N2'] - sludge.imass['H2O']], 'kg/hr', ('S_N2', 'H2O', 'S_CO2')) - ash.set_flow([mass_ash], 'kg/hr', (self.ash_component_ID)) + + n2 = air.imass['N2'] + h2o = sludge.imass['H2O'] + mass_co2 = mass_flue_gas - n2 - h2o + flue_gas.set_flow([n2, h2o, mass_co2/cmps.S_CO2.i_mass], + 'kg/hr', ('S_N2', 'H2O', 'S_CO2')) + ash.set_flow(mass_ash, 'kg/hr', self.ash_component_ID) def _init_state(self): # if multiple wastestreams exist then concentration and total inlow @@ -495,13 +499,13 @@ def _init_state(self): def _update_state(self): '''updates conditions of output stream based on conditions of the Thickener''' - self._outs[0].state = self._sludge * self._state - self._outs[1].state = self._effluent * self._state + self._outs[0].state = self._flue_gas * self._state + self._outs[1].state = self._ash * self._state def _update_dstate(self): '''updates rates of change of output stream from rates of change of the Thickener''' - self._outs[0].dstate = self._sludge * self._dstate - self._outs[1].dstate = self._effluent * self._dstate + self._outs[0].dstate = self._flue_gas * self._dstate + self._outs[1].dstate = self._ash * self._dstate @property def AE(self): From f6b45a06c07d234bedd9d267e23a71245726259f Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Wed, 2 Nov 2022 12:36:02 -0500 Subject: [PATCH 031/424] Incinerator update Corrected mass balance of incinerator --- qsdsan/sanunits/_sludge_treatment.py | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py index eddd5e6f..1e95b569 100644 --- a/qsdsan/sanunits/_sludge_treatment.py +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -458,13 +458,29 @@ def _run(self): #mass balance mass_flowrate_sludge = np.sum(sludge.mass*cmps.i_mass) + # mass_flowrate_sludge = sludge.get_flow('kg/hr') + # mass_flowrate_air = air.get_flow('kg/hr') + # mass_flowrate_fuel = fuel.get_flow('kg/hr') mass_flowrate_air = np.sum(air.mass*cmps.i_mass) mass_flowrate_fuel = np.sum(fuel.mass*cmps.i_mass) - mass_ash = sludge.get_ISS()*sludge.F_vol/1000 #in kg/hr (mg/l)*(m3/hr) = (1/1000)kg/hr + + n2 = air.imass['N2'] + h2o = sludge.imass['H2O'] + # mass_ash = sludge.get_ISS()*sludge.F_vol/1000 #in kg/hr (mg/l)*(m3/hr) = (1/1000)kg/hr + mass_ash = np.sum(sludge.mass*cmps.i_mass*(1-cmps.f_Vmass_Totmass)) \ + - h2o*(1-cmps.H2O.f_Vmass_Totmass) + # By conservation of mass mass_flue_gas = mass_flowrate_sludge + mass_flowrate_air + mass_flowrate_fuel - mass_ash + mass_co2 = mass_flue_gas - n2 - h2o + flue_gas.set_flow([n2, h2o, (mass_co2/cmps.S_CO2.i_mass)], + 'kg/hr', ('S_N2', 'H2O', 'S_CO2')) + ash_cmp_ID = self.ash_component_ID + ash_idx = cmps.index(ash_cmp_ID) + ash.set_flow([mass_ash/cmps.i_mass[ash_idx]/(1-cmps.f_Vmass_Totmass[ash_idx])], + 'kg/hr', (ash_cmp_ID)) #energy balance self.Heat_sludge = sludge.dry_mass*sludge.F_vol*self.calorific_value_sludge/1000 #in KJ/hr (mg/L)*(m3/hr)*(KJ/kg)=KJ/hr*(1/1000) self.Heat_air = mass_flowrate_air*self.Cp_air #in KJ/hr @@ -473,13 +489,7 @@ def _run(self): #By conservation of energy self.Heat_loss = self.Heat_sludge + self.Heat_air + self.Heat_fuel - self.Heat_flue_gas - - n2 = air.imass['N2'] - h2o = sludge.imass['H2O'] - mass_co2 = mass_flue_gas - n2 - h2o - flue_gas.set_flow([n2, h2o, mass_co2/cmps.S_CO2.i_mass], - 'kg/hr', ('S_N2', 'H2O', 'S_CO2')) - ash.set_flow(mass_ash, 'kg/hr', self.ash_component_ID) + def _init_state(self): # if multiple wastestreams exist then concentration and total inlow From 53b2b0e47dd45227c8c096e5d753b9ab4745b582 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Tue, 8 Nov 2022 11:47:40 -0600 Subject: [PATCH 032/424] Updated Incinerator Modified the dynamic simulation functions in incinerator to account for phase change and mass balance --- qsdsan/sanunits/_sludge_treatment.py | 124 +++++++++++++++++++-------- 1 file changed, 87 insertions(+), 37 deletions(-) diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py index 1e95b569..1147f712 100644 --- a/qsdsan/sanunits/_sludge_treatment.py +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -458,16 +458,12 @@ def _run(self): #mass balance mass_flowrate_sludge = np.sum(sludge.mass*cmps.i_mass) - # mass_flowrate_sludge = sludge.get_flow('kg/hr') - # mass_flowrate_air = air.get_flow('kg/hr') - # mass_flowrate_fuel = fuel.get_flow('kg/hr') mass_flowrate_air = np.sum(air.mass*cmps.i_mass) mass_flowrate_fuel = np.sum(fuel.mass*cmps.i_mass) n2 = air.imass['N2'] h2o = sludge.imass['H2O'] - # mass_ash = sludge.get_ISS()*sludge.F_vol/1000 #in kg/hr (mg/l)*(m3/hr) = (1/1000)kg/hr mass_ash = np.sum(sludge.mass*cmps.i_mass*(1-cmps.f_Vmass_Totmass)) \ - h2o*(1-cmps.H2O.f_Vmass_Totmass) @@ -481,6 +477,7 @@ def _run(self): ash_idx = cmps.index(ash_cmp_ID) ash.set_flow([mass_ash/cmps.i_mass[ash_idx]/(1-cmps.f_Vmass_Totmass[ash_idx])], 'kg/hr', (ash_cmp_ID)) + #energy balance self.Heat_sludge = sludge.dry_mass*sludge.F_vol*self.calorific_value_sludge/1000 #in KJ/hr (mg/L)*(m3/hr)*(KJ/kg)=KJ/hr*(1/1000) self.Heat_air = mass_flowrate_air*self.Cp_air #in KJ/hr @@ -492,31 +489,47 @@ def _run(self): def _init_state(self): - # if multiple wastestreams exist then concentration and total inlow - # would be calculated assuming perfect mixing - Qs = self._ins_QC[:,-1] - Cs = self._ins_QC[:,:-1] - self._state = np.append(Qs @ Cs / Qs.sum(), Qs.sum()) - self._dstate = self._state * 0. - flue_gas, ash = self.outs - s_flow = flue_gas.F_vol/(flue_gas.F_vol + ash.F_vol) - denominator = flue_gas.mass + ash.mass - denominator += (denominator == 0) - s = flue_gas.mass/denominator - self._flue_gas = np.append(s/s_flow, s_flow) - self._ash = np.append((1-s)/(1-s_flow), 1-s_flow) + sludge, air, fuel = self.ins + inf = sludge.mass + air.mass + fuel.mass + self._state = np.append((24*inf)/1000, 1) + self._dstate = self._state * 0. def _update_state(self): - '''updates conditions of output stream based on conditions of the Thickener''' - self._outs[0].state = self._flue_gas * self._state - self._outs[1].state = self._ash * self._state + cmps = self.components + + self._outs[0].state = np.zeros(len(self._state)) + self._outs[1].state = np.zeros(len(self._state)) + + + self._outs[0].state[cmps.index('N2')] = self._state[cmps.index('N2')] + self._outs[0].state[cmps.index('H2O')] = self._state[cmps.index('H2O')] + self._outs[0].state[cmps.index('CO2')] = self._state[cmps.index('CO2')] + self._outs[0].state[-1] = 1 + + ash_cmp_ID = self.ash_component_ID + ash_idx = cmps.index(ash_cmp_ID) + + self._outs[1].state[ash_idx] = self._state[ash_idx] + self._outs[1].state[-1] = 1 + def _update_dstate(self): - '''updates rates of change of output stream from rates of change of the Thickener''' - self._outs[0].dstate = self._flue_gas * self._dstate - self._outs[1].dstate = self._ash * self._dstate - + + cmps = self.components + self._outs[0].dstate = np.zeros(len(self._state)) + self._outs[1].dstate = np.zeros(len(self._state)) + + #Everything will be zero anyway! + self._outs[0].dstate[cmps.index('N2')] = self._dstate[cmps.index('N2')] + self._outs[0].dstate[cmps.index('H2O')] = self._dstate[cmps.index('H2O')] + self._outs[0].dstate[cmps.index('CO2')] = self._dstate[cmps.index('CO2')] + + ash_cmp_ID = self.ash_component_ID + ash_idx = cmps.index(ash_cmp_ID) + + self._outs[1].dstate[ash_idx] = self._dstate[ash_idx] + @property def AE(self): if self._AE is None: @@ -524,24 +537,61 @@ def AE(self): return self._AE def _compile_AE(self): + cmps = self.components _state = self._state _dstate = self._dstate _update_state = self._update_state _update_dstate = self._update_dstate + def yt(t, QC_ins, dQC_ins): - #Because there are multiple inlets - Q_ins = QC_ins[:, -1] - C_ins = QC_ins[:, :-1] - dQ_ins = dQC_ins[:, -1] - dC_ins = dQC_ins[:, :-1] - Q = Q_ins.sum() - C = Q_ins @ C_ins / Q - _state[-1] = Q - _state[:-1] = C - Q_dot = dQ_ins.sum() - C_dot = (dQ_ins @ C_ins + Q_ins @ dC_ins - Q_dot * C)/Q - _dstate[-1] = Q_dot - _dstate[:-1] = C_dot + + for m in range(len(QC_ins)): + QC_ins[m, :-1] = QC_ins[m, :-1]*QC_ins[m, -1] + m = m + 1 + QC_ins[:, -1] = 1 + + mass_flowrate_sludge = np.sum(QC_ins[0, :-1]*cmps.i_mass) + mass_flowrate_air = np.sum(QC_ins[1, :-1]*cmps.i_mass) + mass_flowrate_fuel = np.sum(QC_ins[2, :-1]*cmps.i_mass) + + _state[cmps.index('H2O')] = QC_ins[0, cmps.index('H2O')] + _state[cmps.index('N2')] = QC_ins[1, cmps.index('N2')] + + mass_ash = np.sum(QC_ins[0, :-1]*cmps.i_mass*(1-cmps.f_Vmass_Totmass)) \ + - QC_ins[0, cmps.index('H2O')]*(1-cmps.H2O.f_Vmass_Totmass) + mass_flue_gas = mass_flowrate_sludge + mass_flowrate_air + mass_flowrate_fuel - mass_ash + mass_co2 = mass_flue_gas - QC_ins[1, cmps.index('N2')] - QC_ins[0, cmps.index('H2O')] + + _state[cmps.index('CO2')] = mass_co2/cmps.S_CO2.i_mass + + ash_cmp_ID = self.ash_component_ID + ash_idx = cmps.index(ash_cmp_ID) + + _state[ash_idx] = mass_ash/cmps.i_mass[ash_idx]/(1-cmps.f_Vmass_Totmass[ash_idx]) + + for m in range(len(dQC_ins)): + dQC_ins[m, :-1] = dQC_ins[m, :-1]*dQC_ins[m, -1] + m = m + 1 + dQC_ins[:, -1] = 1 + + dmass_flowrate_sludge = np.sum(dQC_ins[0, :-1]*cmps.i_mass) + dmass_flowrate_air = np.sum(dQC_ins[1, :-1]*cmps.i_mass) + dmass_flowrate_fuel = np.sum(dQC_ins[2, :-1]*cmps.i_mass) + + _dstate[cmps.index('H2O')] = dQC_ins[0, cmps.index('H2O')] + _dstate[cmps.index('N2')] = dQC_ins[1, cmps.index('N2')] + + dmass_ash = np.sum(dQC_ins[0, :-1]*cmps.i_mass*(1-cmps.f_Vmass_Totmass)) \ + - dQC_ins[0, cmps.index('H2O')]*(1-cmps.H2O.f_Vmass_Totmass) + dmass_flue_gas = dmass_flowrate_sludge + dmass_flowrate_air + dmass_flowrate_fuel - dmass_ash + dmass_co2 = dmass_flue_gas - dQC_ins[1, cmps.index('N2')] - dQC_ins[0, cmps.index('H2O')] + + _dstate[cmps.index('CO2')] = dmass_co2/cmps.S_CO2.i_mass + + ash_cmp_ID = self.ash_component_ID + ash_idx = cmps.index(ash_cmp_ID) + _dstate[ash_idx] = dmass_ash/cmps.i_mass[ash_idx]/(1-cmps.f_Vmass_Totmass[ash_idx]) + _update_state() _update_dstate() self._AE = yt \ No newline at end of file From 4ef5a878fd0eb029fe9325a06ccb6d241cf0648f Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Tue, 8 Nov 2022 14:55:47 -0600 Subject: [PATCH 033/424] Updated documentation for Incinerator Added working example for Incinerator --- qsdsan/sanunits/_sludge_treatment.py | 57 ++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py index 1147f712..e7712b87 100644 --- a/qsdsan/sanunits/_sludge_treatment.py +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -371,6 +371,63 @@ class Incinerator(SanUnit): calorific_value_fuel : float The calorific value of fuel employed for combustion in KJ/kg. The default fuel is natural gas with calofific value of 50000 KJ/kg. + + Examples + -------- + + >>> import qsdsan as qs + >>> cmps = qs.Components.load_default() + >>> CO2 = qs.Component.from_chemical('S_CO2', search_ID='CO2', particle_size='Soluble', degradability='Undegradable', organic=False) + >>> cmps_test = qs.Components([cmps.S_F, cmps.S_NH4, cmps.X_OHO, cmps.H2O, cmps.S_CH4, cmps.S_O2, cmps.S_N2, cmps.S_H2, cmps.X_Ig_ISS, CO2]) + >>> cmps_test.default_compile() + >>> qs.set_thermo(cmps_test) + >>> ws = qs.WasteStream('ws', S_F=10, S_NH4=20, X_OHO=15, H2O=1000) + >>> natural_gas = qs.WasteStream('nat_gas', phase='g', S_CH4=1000) + >>> air = qs.WasteStream('air', phase='g', S_O2=210, S_N2=780, S_H2=10) + >>> from qsdsan.sanunits import Incinerator + >>> ps = Incinerator(ID='PC', ins= (ws, air, natural_gas), outs=('flu_gas', 'ash'), + isdynamic=True) + >>> ps._run() + >>> ps + + Incinerator: PC + ins... + [0] ws + phase: 'l', T: 298.15 K, P: 101325 Pa + flow (g/hr): S_F 1e+04 + S_NH4 2e+04 + X_OHO 1.5e+04 + H2O 1e+06 + WasteStream-specific properties: + pH : 7.0 + COD : 23643.1 mg/L + BOD : 14819.1 mg/L + TC : 8218.3 mg/L + TOC : 8218.3 mg/L + TN : 20167.1 mg/L + TP : 364.1 mg/L + TK : 67.6 mg/L + [1] air + phase: 'g', T: 298.15 K, P: 101325 Pa + flow (g/hr): S_O2 2.1e+05 + S_N2 7.8e+05 + S_H2 1e+04 + WasteStream-specific properties: None for non-liquid waste streams + [2] nat_gas + phase: 'g', T: 298.15 K, P: 101325 Pa + flow (g/hr): S_CH4 1e+06 + WasteStream-specific properties: None for non-liquid waste streams + outs... + [0] flu_gas + phase: 'g', T: 298.15 K, P: 101325 Pa + flow (g/hr): H2O 1e+06 + S_N2 7.8e+05 + S_CO2 4.77e+05 + WasteStream-specific properties: None for non-liquid waste streams + [1] ash + phase: 's', T: 298.15 K, P: 101325 Pa + flow (g/hr): X_Ig_ISS 2.58e+04 + WasteStream-specific properties: None for non-liquid waste streams References: ---------- From 1d639f394ccfca1a04df38b23c7b8c58cd7a9240 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Thu, 10 Nov 2022 13:57:52 -0600 Subject: [PATCH 034/424] Updated Incinerator Joy updated the compile AE function of Incinerator --- qsdsan/sanunits/_sludge_treatment.py | 78 +++++++++++++--------------- 1 file changed, 36 insertions(+), 42 deletions(-) diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py index e7712b87..ac8eff66 100644 --- a/qsdsan/sanunits/_sludge_treatment.py +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -549,17 +549,15 @@ def _init_state(self): sludge, air, fuel = self.ins inf = sludge.mass + air.mass + fuel.mass - self._state = np.append((24*inf)/1000, 1) + self._state = (24*inf)/1000 self._dstate = self._state * 0. def _update_state(self): - cmps = self.components self._outs[0].state = np.zeros(len(self._state)) self._outs[1].state = np.zeros(len(self._state)) - - + self._outs[0].state[cmps.index('N2')] = self._state[cmps.index('N2')] self._outs[0].state[cmps.index('H2O')] = self._state[cmps.index('H2O')] self._outs[0].state[cmps.index('CO2')] = self._state[cmps.index('CO2')] @@ -599,55 +597,51 @@ def _compile_AE(self): _dstate = self._dstate _update_state = self._update_state _update_dstate = self._update_dstate - + idx_h2o = cmps.index('H2O') + idx_n2 = cmps.index('N2') + idx_co2 = cmps.index('CO2') + ash_idx = cmps.index(self.ash_component_ID) + cmps_i_mass = cmps.i_mass + cmps_v2tmass = cmps.f_Vmass_Totmass + def yt(t, QC_ins, dQC_ins): + slg, air, fuel = M_ins = np.diag(QC_ins[:,-1]) @ QC_ins[:,:-1] - for m in range(len(QC_ins)): - QC_ins[m, :-1] = QC_ins[m, :-1]*QC_ins[m, -1] - m = m + 1 - QC_ins[:, -1] = 1 - - mass_flowrate_sludge = np.sum(QC_ins[0, :-1]*cmps.i_mass) - mass_flowrate_air = np.sum(QC_ins[1, :-1]*cmps.i_mass) - mass_flowrate_fuel = np.sum(QC_ins[2, :-1]*cmps.i_mass) - - _state[cmps.index('H2O')] = QC_ins[0, cmps.index('H2O')] - _state[cmps.index('N2')] = QC_ins[1, cmps.index('N2')] - - mass_ash = np.sum(QC_ins[0, :-1]*cmps.i_mass*(1-cmps.f_Vmass_Totmass)) \ - - QC_ins[0, cmps.index('H2O')]*(1-cmps.H2O.f_Vmass_Totmass) - mass_flue_gas = mass_flowrate_sludge + mass_flowrate_air + mass_flowrate_fuel - mass_ash - mass_co2 = mass_flue_gas - QC_ins[1, cmps.index('N2')] - QC_ins[0, cmps.index('H2O')] + mass_in_tot = np.sum(M_ins @ cmps_i_mass) - _state[cmps.index('CO2')] = mass_co2/cmps.S_CO2.i_mass + _state[idx_h2o] = h2o = slg[idx_h2o] + _state[idx_n2] = n2 = air[idx_n2] - ash_cmp_ID = self.ash_component_ID - ash_idx = cmps.index(ash_cmp_ID) + mass_ash = np.sum(slg*cmps_i_mass*(1-cmps_v2tmass)) \ + - h2o*(1-cmps_v2tmass[idx_h2o]) + mass_flue_gas = mass_in_tot - mass_ash + mass_co2 = mass_flue_gas - n2 - h2o - _state[ash_idx] = mass_ash/cmps.i_mass[ash_idx]/(1-cmps.f_Vmass_Totmass[ash_idx]) + _state[idx_co2] = mass_co2/cmps_i_mass[idx_co2] + _state[ash_idx] = mass_ash/cmps.i_mass[ash_idx]/(1-cmps_v2tmass[ash_idx]) - for m in range(len(dQC_ins)): - dQC_ins[m, :-1] = dQC_ins[m, :-1]*dQC_ins[m, -1] - m = m + 1 - dQC_ins[:, -1] = 1 + # for m in range(len(dQC_ins)): + # dQC_ins[m, :-1] = dQC_ins[m, :-1]*dQC_ins[m, -1] + # m = m + 1 + # dQC_ins[:, -1] = 1 - dmass_flowrate_sludge = np.sum(dQC_ins[0, :-1]*cmps.i_mass) - dmass_flowrate_air = np.sum(dQC_ins[1, :-1]*cmps.i_mass) - dmass_flowrate_fuel = np.sum(dQC_ins[2, :-1]*cmps.i_mass) + # dmass_flowrate_sludge = np.sum(dQC_ins[0, :-1]*cmps.i_mass) + # dmass_flowrate_air = np.sum(dQC_ins[1, :-1]*cmps.i_mass) + # dmass_flowrate_fuel = np.sum(dQC_ins[2, :-1]*cmps.i_mass) - _dstate[cmps.index('H2O')] = dQC_ins[0, cmps.index('H2O')] - _dstate[cmps.index('N2')] = dQC_ins[1, cmps.index('N2')] + # _dstate[cmps.index('H2O')] = dQC_ins[0, cmps.index('H2O')] + # _dstate[cmps.index('N2')] = dQC_ins[1, cmps.index('N2')] - dmass_ash = np.sum(dQC_ins[0, :-1]*cmps.i_mass*(1-cmps.f_Vmass_Totmass)) \ - - dQC_ins[0, cmps.index('H2O')]*(1-cmps.H2O.f_Vmass_Totmass) - dmass_flue_gas = dmass_flowrate_sludge + dmass_flowrate_air + dmass_flowrate_fuel - dmass_ash - dmass_co2 = dmass_flue_gas - dQC_ins[1, cmps.index('N2')] - dQC_ins[0, cmps.index('H2O')] + # dmass_ash = np.sum(dQC_ins[0, :-1]*cmps.i_mass*(1-cmps.f_Vmass_Totmass)) \ + # - dQC_ins[0, cmps.index('H2O')]*(1-cmps.H2O.f_Vmass_Totmass) + # dmass_flue_gas = dmass_flowrate_sludge + dmass_flowrate_air + dmass_flowrate_fuel - dmass_ash + # dmass_co2 = dmass_flue_gas - dQC_ins[1, cmps.index('N2')] - dQC_ins[0, cmps.index('H2O')] - _dstate[cmps.index('CO2')] = dmass_co2/cmps.S_CO2.i_mass + # _dstate[cmps.index('CO2')] = dmass_co2/cmps.S_CO2.i_mass - ash_cmp_ID = self.ash_component_ID - ash_idx = cmps.index(ash_cmp_ID) - _dstate[ash_idx] = dmass_ash/cmps.i_mass[ash_idx]/(1-cmps.f_Vmass_Totmass[ash_idx]) + # ash_cmp_ID = self.ash_component_ID + # ash_idx = cmps.index(ash_cmp_ID) + # _dstate[ash_idx] = dmass_ash/cmps.i_mass[ash_idx]/(1-cmps.f_Vmass_Totmass[ash_idx]) _update_state() _update_dstate() From 19f33670f334b3047fe339b54597472a8655c1e0 Mon Sep 17 00:00:00 2001 From: Joy Cheung Date: Thu, 10 Nov 2022 14:46:16 -0600 Subject: [PATCH 035/424] fix incinerator `_update_state` --- qsdsan/sanunits/_sludge_treatment.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py index ac8eff66..fb6967e9 100644 --- a/qsdsan/sanunits/_sludge_treatment.py +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -554,9 +554,9 @@ def _init_state(self): def _update_state(self): cmps = self.components - - self._outs[0].state = np.zeros(len(self._state)) - self._outs[1].state = np.zeros(len(self._state)) + for ws in self.outs: + if ws.state is None: + ws.state = np.zeros(len(self._state)+1) self._outs[0].state[cmps.index('N2')] = self._state[cmps.index('N2')] self._outs[0].state[cmps.index('H2O')] = self._state[cmps.index('H2O')] @@ -570,19 +570,19 @@ def _update_state(self): self._outs[1].state[-1] = 1 def _update_dstate(self): - cmps = self.components - self._outs[0].dstate = np.zeros(len(self._state)) - self._outs[1].dstate = np.zeros(len(self._state)) + for ws in self.outs: + if ws.dstate is None: + ws.dstate = np.zeros(len(self._dstate)+1) #Everything will be zero anyway! self._outs[0].dstate[cmps.index('N2')] = self._dstate[cmps.index('N2')] self._outs[0].dstate[cmps.index('H2O')] = self._dstate[cmps.index('H2O')] self._outs[0].dstate[cmps.index('CO2')] = self._dstate[cmps.index('CO2')] - + ash_cmp_ID = self.ash_component_ID ash_idx = cmps.index(ash_cmp_ID) - + self._outs[1].dstate[ash_idx] = self._dstate[ash_idx] @property @@ -604,9 +604,8 @@ def _compile_AE(self): cmps_i_mass = cmps.i_mass cmps_v2tmass = cmps.f_Vmass_Totmass - def yt(t, QC_ins, dQC_ins): + def yt(t, QC_ins, dQC_ins): slg, air, fuel = M_ins = np.diag(QC_ins[:,-1]) @ QC_ins[:,:-1] - mass_in_tot = np.sum(M_ins @ cmps_i_mass) _state[idx_h2o] = h2o = slg[idx_h2o] From d13f8a3d6279e6790aefbd8e60aab59cf0446917 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Thu, 10 Nov 2022 16:37:33 -0600 Subject: [PATCH 036/424] Updated Incinerator Modified the update_state and compile_AE functions of Incinerator --- qsdsan/sanunits/_sludge_treatment.py | 124 ++++++++++++++------------- 1 file changed, 63 insertions(+), 61 deletions(-) diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py index fb6967e9..c08c5cf6 100644 --- a/qsdsan/sanunits/_sludge_treatment.py +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -514,20 +514,24 @@ def _run(self): raise ValueError(f'The phase of fuel is expected to be gas not {fuel.phase}') #mass balance - mass_flowrate_sludge = np.sum(sludge.mass*cmps.i_mass) - mass_flowrate_air = np.sum(air.mass*cmps.i_mass) - mass_flowrate_fuel = np.sum(fuel.mass*cmps.i_mass) - - n2 = air.imass['N2'] - h2o = sludge.imass['H2O'] + # mass_flowrate_sludge = np.sum(sludge.mass*cmps.i_mass) + # mass_flowrate_air = np.sum(air.mass*cmps.i_mass) + # mass_flowrate_fuel = np.sum(fuel.mass*cmps.i_mass) + inf = (sludge.mass + air.mass + fuel.mass) + idx_n2 = cmps.index('N2') + idx_h2o = cmps.index('H2O') + + n2 = inf[idx_n2] + h2o = inf[idx_h2o] - mass_ash = np.sum(sludge.mass*cmps.i_mass*(1-cmps.f_Vmass_Totmass)) \ - - h2o*(1-cmps.H2O.f_Vmass_Totmass) + mass_ash = np.sum(inf*cmps.i_mass*(1-cmps.f_Vmass_Totmass)) \ + - h2o*cmps.H2O.i_mass*(1-cmps.H2O.f_Vmass_Totmass) # By conservation of mass - mass_flue_gas = mass_flowrate_sludge + mass_flowrate_air + mass_flowrate_fuel - mass_ash + # mass_flue_gas = mass_flowrate_sludge + mass_flowrate_air + mass_flowrate_fuel - mass_ash + mass_flue_gas = np.sum(inf*cmps.i_mass) - mass_ash - mass_co2 = mass_flue_gas - n2 - h2o + mass_co2 = mass_flue_gas - n2*cmps.N2.i_mass - h2o*cmps.H2O.i_mass flue_gas.set_flow([n2, h2o, (mass_co2/cmps.S_CO2.i_mass)], 'kg/hr', ('S_N2', 'H2O', 'S_CO2')) ash_cmp_ID = self.ash_component_ID @@ -537,20 +541,21 @@ def _run(self): #energy balance self.Heat_sludge = sludge.dry_mass*sludge.F_vol*self.calorific_value_sludge/1000 #in KJ/hr (mg/L)*(m3/hr)*(KJ/kg)=KJ/hr*(1/1000) - self.Heat_air = mass_flowrate_air*self.Cp_air #in KJ/hr - self.Heat_fuel = mass_flowrate_fuel*self.calorific_value_fuel #in KJ/hr + self.Heat_air = np.sum(air.mass*cmps.i_mass)*self.Cp_air #in KJ/hr + self.Heat_fuel = np.sum(fuel.mass*cmps.i_mass)*self.calorific_value_fuel #in KJ/hr self.Heat_flue_gas = self.process_efficiency*(self.Heat_sludge + self.Heat_air + self.Heat_fuel) #By conservation of energy self.Heat_loss = self.Heat_sludge + self.Heat_air + self.Heat_fuel - self.Heat_flue_gas - def _init_state(self): sludge, air, fuel = self.ins inf = sludge.mass + air.mass + fuel.mass self._state = (24*inf)/1000 self._dstate = self._state * 0. + self._cached_state = self._state.copy() + self._cached_t = 0 def _update_state(self): cmps = self.components @@ -558,15 +563,28 @@ def _update_state(self): if ws.state is None: ws.state = np.zeros(len(self._state)+1) - self._outs[0].state[cmps.index('N2')] = self._state[cmps.index('N2')] - self._outs[0].state[cmps.index('H2O')] = self._state[cmps.index('H2O')] - self._outs[0].state[cmps.index('CO2')] = self._state[cmps.index('CO2')] - self._outs[0].state[-1] = 1 + idx_h2o = cmps.index('H2O') + idx_n2 = cmps.index('N2') + idx_co2 = cmps.index('CO2') + ash_idx = cmps.index(self.ash_component_ID) + cmps_i_mass = cmps.i_mass + cmps_v2tmass = cmps.f_Vmass_Totmass - ash_cmp_ID = self.ash_component_ID - ash_idx = cmps.index(ash_cmp_ID) + inf = self._state + #slg, air, fuel = M_ins = np.diag(QC_ins[:,-1]) @ QC_ins[:,:-1] + mass_in_tot = np.sum(inf*cmps_i_mass) + self._outs[0].state[idx_h2o] = h2o = inf[idx_h2o] + self._outs[0].state[idx_n2] = n2 = inf[idx_n2] + + mass_ash = np.sum(inf*cmps_i_mass*(1-cmps_v2tmass)) \ + - h2o*cmps.H2O.i_mass*(1-cmps_v2tmass[idx_h2o]) + mass_flue_gas = mass_in_tot - mass_ash + mass_co2 = mass_flue_gas - n2 - h2o - self._outs[1].state[ash_idx] = self._state[ash_idx] + self._outs[0].state[idx_co2] = mass_co2/cmps_i_mass[idx_co2] + self._outs[1].state[ash_idx] = mass_ash/cmps_i_mass[ash_idx]/(1-cmps_v2tmass[ash_idx]) + + self._outs[0].state[-1] = 1 self._outs[1].state[-1] = 1 def _update_dstate(self): @@ -592,56 +610,40 @@ def AE(self): return self._AE def _compile_AE(self): - cmps = self.components + # cmps = self.components _state = self._state _dstate = self._dstate _update_state = self._update_state _update_dstate = self._update_dstate - idx_h2o = cmps.index('H2O') - idx_n2 = cmps.index('N2') - idx_co2 = cmps.index('CO2') - ash_idx = cmps.index(self.ash_component_ID) - cmps_i_mass = cmps.i_mass - cmps_v2tmass = cmps.f_Vmass_Totmass + _cached_state = self._cached_state + + # idx_h2o = cmps.index('H2O') + # idx_n2 = cmps.index('N2') + # idx_co2 = cmps.index('CO2') + # ash_idx = cmps.index(self.ash_component_ID) + # cmps_i_mass = cmps.i_mass + # cmps_v2tmass = cmps.f_Vmass_Totmass def yt(t, QC_ins, dQC_ins): - slg, air, fuel = M_ins = np.diag(QC_ins[:,-1]) @ QC_ins[:,:-1] - mass_in_tot = np.sum(M_ins @ cmps_i_mass) + # slg, air, fuel = M_ins = np.diag(QC_ins[:,-1]) @ QC_ins[:,:-1] + # mass_in_tot = np.sum(M_ins @ cmps_i_mass) + M_ins = np.diag(QC_ins[:,-1]) @ QC_ins[:,:-1] + _state[:] = np.sum(M_ins, axis=0) + # _state[idx_h2o] = h2o = slg[idx_h2o] + # _state[idx_n2] = n2 = air[idx_n2] - _state[idx_h2o] = h2o = slg[idx_h2o] - _state[idx_n2] = n2 = air[idx_n2] + # mass_ash = np.sum(slg*cmps_i_mass*(1-cmps_v2tmass)) \ + # - h2o*cmps.H2O.i_mass*(1-cmps_v2tmass[idx_h2o]) + # mass_flue_gas = mass_in_tot - mass_ash + # mass_co2 = mass_flue_gas - n2 - h2o - mass_ash = np.sum(slg*cmps_i_mass*(1-cmps_v2tmass)) \ - - h2o*(1-cmps_v2tmass[idx_h2o]) - mass_flue_gas = mass_in_tot - mass_ash - mass_co2 = mass_flue_gas - n2 - h2o + # _state[idx_co2] = mass_co2/cmps_i_mass[idx_co2] + # _state[ash_idx] = mass_ash/cmps.i_mass[ash_idx]/(1-cmps_v2tmass[ash_idx]) - _state[idx_co2] = mass_co2/cmps_i_mass[idx_co2] - _state[ash_idx] = mass_ash/cmps.i_mass[ash_idx]/(1-cmps_v2tmass[ash_idx]) - - # for m in range(len(dQC_ins)): - # dQC_ins[m, :-1] = dQC_ins[m, :-1]*dQC_ins[m, -1] - # m = m + 1 - # dQC_ins[:, -1] = 1 - - # dmass_flowrate_sludge = np.sum(dQC_ins[0, :-1]*cmps.i_mass) - # dmass_flowrate_air = np.sum(dQC_ins[1, :-1]*cmps.i_mass) - # dmass_flowrate_fuel = np.sum(dQC_ins[2, :-1]*cmps.i_mass) - - # _dstate[cmps.index('H2O')] = dQC_ins[0, cmps.index('H2O')] - # _dstate[cmps.index('N2')] = dQC_ins[1, cmps.index('N2')] - - # dmass_ash = np.sum(dQC_ins[0, :-1]*cmps.i_mass*(1-cmps.f_Vmass_Totmass)) \ - # - dQC_ins[0, cmps.index('H2O')]*(1-cmps.H2O.f_Vmass_Totmass) - # dmass_flue_gas = dmass_flowrate_sludge + dmass_flowrate_air + dmass_flowrate_fuel - dmass_ash - # dmass_co2 = dmass_flue_gas - dQC_ins[1, cmps.index('N2')] - dQC_ins[0, cmps.index('H2O')] - - # _dstate[cmps.index('CO2')] = dmass_co2/cmps.S_CO2.i_mass - - # ash_cmp_ID = self.ash_component_ID - # ash_idx = cmps.index(ash_cmp_ID) - # _dstate[ash_idx] = dmass_ash/cmps.i_mass[ash_idx]/(1-cmps.f_Vmass_Totmass[ash_idx]) - + if t > self._cached_t: + _dstate[:] = (_state - _cached_state)/(t-self._cached_t) + _cached_state[:] = _state + self._cached_t = t _update_state() _update_dstate() self._AE = yt \ No newline at end of file From b9e6bd647650940e30b292319dd9c802a1a5b192 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Fri, 11 Nov 2022 11:51:02 -0600 Subject: [PATCH 037/424] Modified Incinerator Updated the update_dstate function --- qsdsan/sanunits/_sludge_treatment.py | 79 +++++++++++++--------------- 1 file changed, 37 insertions(+), 42 deletions(-) diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py index c08c5cf6..d6f734c8 100644 --- a/qsdsan/sanunits/_sludge_treatment.py +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -513,10 +513,6 @@ def _run(self): if fuel.phase != 'g': raise ValueError(f'The phase of fuel is expected to be gas not {fuel.phase}') - #mass balance - # mass_flowrate_sludge = np.sum(sludge.mass*cmps.i_mass) - # mass_flowrate_air = np.sum(air.mass*cmps.i_mass) - # mass_flowrate_fuel = np.sum(fuel.mass*cmps.i_mass) inf = (sludge.mass + air.mass + fuel.mass) idx_n2 = cmps.index('N2') idx_h2o = cmps.index('H2O') @@ -527,8 +523,7 @@ def _run(self): mass_ash = np.sum(inf*cmps.i_mass*(1-cmps.f_Vmass_Totmass)) \ - h2o*cmps.H2O.i_mass*(1-cmps.H2O.f_Vmass_Totmass) - # By conservation of mass - # mass_flue_gas = mass_flowrate_sludge + mass_flowrate_air + mass_flowrate_fuel - mass_ash + # Conservation of mass mass_flue_gas = np.sum(inf*cmps.i_mass) - mass_ash mass_co2 = mass_flue_gas - n2*cmps.N2.i_mass - h2o*cmps.H2O.i_mass @@ -539,13 +534,13 @@ def _run(self): ash.set_flow([mass_ash/cmps.i_mass[ash_idx]/(1-cmps.f_Vmass_Totmass[ash_idx])], 'kg/hr', (ash_cmp_ID)) - #energy balance + # Energy balance self.Heat_sludge = sludge.dry_mass*sludge.F_vol*self.calorific_value_sludge/1000 #in KJ/hr (mg/L)*(m3/hr)*(KJ/kg)=KJ/hr*(1/1000) self.Heat_air = np.sum(air.mass*cmps.i_mass)*self.Cp_air #in KJ/hr self.Heat_fuel = np.sum(fuel.mass*cmps.i_mass)*self.calorific_value_fuel #in KJ/hr self.Heat_flue_gas = self.process_efficiency*(self.Heat_sludge + self.Heat_air + self.Heat_fuel) - #By conservation of energy + # Conservation of energy self.Heat_loss = self.Heat_sludge + self.Heat_air + self.Heat_fuel - self.Heat_flue_gas def _init_state(self): @@ -571,7 +566,6 @@ def _update_state(self): cmps_v2tmass = cmps.f_Vmass_Totmass inf = self._state - #slg, air, fuel = M_ins = np.diag(QC_ins[:,-1]) @ QC_ins[:,:-1] mass_in_tot = np.sum(inf*cmps_i_mass) self._outs[0].state[idx_h2o] = h2o = inf[idx_h2o] self._outs[0].state[idx_n2] = n2 = inf[idx_n2] @@ -588,21 +582,35 @@ def _update_state(self): self._outs[1].state[-1] = 1 def _update_dstate(self): + cmps = self.components + idx_h2o = cmps.index('H2O') + idx_n2 = cmps.index('N2') + idx_co2 = cmps.index('CO2') + ash_idx = cmps.index(self.ash_component_ID) + d_state = self._dstate + cmps_i_mass = cmps.i_mass + cmps_v2tmass = cmps.f_Vmass_Totmass + d_n2 = d_state[idx_n2] + d_h2o = d_state[idx_h2o] + for ws in self.outs: if ws.dstate is None: ws.dstate = np.zeros(len(self._dstate)+1) + + self._outs[0].dstate[idx_n2] = d_n2 + self._outs[0].dstate[idx_h2o] = d_h2o + + d_mass_in_tot = np.sum(d_state*cmps_i_mass) + + d_mass_ash = np.sum(d_state*cmps_i_mass*(1-cmps_v2tmass)) \ + - d_h2o*cmps.H2O.i_mass*(1-cmps_v2tmass[idx_h2o]) + d_mass_flue_gas = d_mass_in_tot - d_mass_ash + d_mass_co2 = d_mass_flue_gas - d_n2 - d_h2o + + self._outs[0].dstate[idx_co2] = d_mass_co2/cmps_i_mass[idx_co2] + self._outs[1].dstate[ash_idx] = d_mass_ash/cmps_i_mass[ash_idx]/(1-cmps_v2tmass[ash_idx]) - #Everything will be zero anyway! - self._outs[0].dstate[cmps.index('N2')] = self._dstate[cmps.index('N2')] - self._outs[0].dstate[cmps.index('H2O')] = self._dstate[cmps.index('H2O')] - self._outs[0].dstate[cmps.index('CO2')] = self._dstate[cmps.index('CO2')] - - ash_cmp_ID = self.ash_component_ID - ash_idx = cmps.index(ash_cmp_ID) - - self._outs[1].dstate[ash_idx] = self._dstate[ash_idx] - @property def AE(self): if self._AE is None: @@ -610,38 +618,25 @@ def AE(self): return self._AE def _compile_AE(self): - # cmps = self.components _state = self._state _dstate = self._dstate _update_state = self._update_state _update_dstate = self._update_dstate _cached_state = self._cached_state - - # idx_h2o = cmps.index('H2O') - # idx_n2 = cmps.index('N2') - # idx_co2 = cmps.index('CO2') - # ash_idx = cmps.index(self.ash_component_ID) - # cmps_i_mass = cmps.i_mass - # cmps_v2tmass = cmps.f_Vmass_Totmass def yt(t, QC_ins, dQC_ins): - # slg, air, fuel = M_ins = np.diag(QC_ins[:,-1]) @ QC_ins[:,:-1] - # mass_in_tot = np.sum(M_ins @ cmps_i_mass) - M_ins = np.diag(QC_ins[:,-1]) @ QC_ins[:,:-1] - _state[:] = np.sum(M_ins, axis=0) - # _state[idx_h2o] = h2o = slg[idx_h2o] - # _state[idx_n2] = n2 = air[idx_n2] - - # mass_ash = np.sum(slg*cmps_i_mass*(1-cmps_v2tmass)) \ - # - h2o*cmps.H2O.i_mass*(1-cmps_v2tmass[idx_h2o]) - # mass_flue_gas = mass_in_tot - mass_ash - # mass_co2 = mass_flue_gas - n2 - h2o - - # _state[idx_co2] = mass_co2/cmps_i_mass[idx_co2] - # _state[ash_idx] = mass_ash/cmps.i_mass[ash_idx]/(1-cmps_v2tmass[ash_idx]) + # Mass_in is basically the mass flowrate array where each row + # corresponds to the flowrates of individual components (in columns) + # This strcuture is achieved by multiplying the first (n-1) rows of + # Q_ins (which corresponds to concentration) to the nth row (which + # is the volumetric flowrate) + Mass_ins = np.diag(QC_ins[:,-1]) @ QC_ins[:,:-1] + # the _state array is formed by adding each column of the Mass_in + # array, thus providing the total massflowrate of each component + _state[:] = np.sum(Mass_ins, axis=0) if t > self._cached_t: - _dstate[:] = (_state - _cached_state)/(t-self._cached_t) + _dstate[:] = (_state - _cached_state)/(t - self._cached_t) _cached_state[:] = _state self._cached_t = t _update_state() From a8037986b138668da02059020660ee2d605505f1 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Wed, 16 Nov 2022 10:47:41 -0600 Subject: [PATCH 038/424] Edited a typo Corrected a typo in the design function --- qsdsan/sanunits/_clarifier.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index 65b98455..5c9ff6bf 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -645,14 +645,14 @@ class PrimaryClarifier(SanUnit): def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, init_with='WasteStream', Hydraulic_Retention_Time=0.04268, - ratio_uf=0.007, f_corr=0.65, F_BM_default=None, oveflow_rate=40, **kwargs): + ratio_uf=0.007, f_corr=0.65, F_BM_default=None, overflow_rate=40, **kwargs): SanUnit.__init__(self, ID, ins, outs, thermo, isdynamic=isdynamic, init_with=init_with, F_BM_default=F_BM_default) self.Hydraulic_Retention_Time = Hydraulic_Retention_Time #in days self.ratio_uf = ratio_uf self.f_corr = f_corr - self.oveflow_rate = oveflow_rate #in m3/(m2*day) + self.overflow_rate = overflow_rate #in m3/(m2*day) self.mixed = WasteStream('mixed') @property @@ -726,7 +726,7 @@ def _design(self): design = self.design_results design['Volume'] = 24*self._HRT*self.mixed.get_total_flow('m3/hr') #in m3 - design['Area'] = self.mixed.get_total_flow('m3/hr')/self.oveflow_rate #in m2 + design['Area'] = self.mixed.get_total_flow('m3/hr')/self.overflow_rate #in m2 design['Length'] = design['Volume']/design['Area'] #in m def _init_state(self): From 4b8ae18b71a510436da10be00f0a3ff5ba3fc20f Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Thu, 17 Nov 2022 16:31:26 -0600 Subject: [PATCH 039/424] Modified Dewatering Unit Allowed flexibility in number of influent streams possible in dewatering sludge unit --- qsdsan/sanunits/_sludge_treatment.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py index d6f734c8..83e94edc 100644 --- a/qsdsan/sanunits/_sludge_treatment.py +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -311,6 +311,7 @@ class DewateringUnit(Thickener): _N_ins = 1 _N_outs = 2 + _ins_size_is_fixed = False def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, init_with='WasteStream', F_BM_default=None, thickener_perc=28, TSS_removal_perc=98, From a25c2e37b4b7b562a8633cadbf29a4ccc85a78dd Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Tue, 29 Nov 2022 17:38:27 -0600 Subject: [PATCH 040/424] Updated Incinerator unit Added variables for the components of N2, H20, and CO2. Would allow the Incinerator unit to be user flexible. --- qsdsan/sanunits/_sludge_treatment.py | 37 +++++++++++++++++++--------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py index 83e94edc..a411c5af 100644 --- a/qsdsan/sanunits/_sludge_treatment.py +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -447,7 +447,8 @@ class Incinerator(SanUnit): def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, init_with='WasteStream', F_BM_default=None, process_efficiency=0.90, calorific_value_sludge= 12000, calorific_value_fuel=50000, - ash_component_ID = 'X_Ig_ISS', **kwargs): + ash_component_ID = 'X_Ig_ISS', nitrogen_ID = 'S_N2', water_ID = 'H2O', + carbon_di_oxide_ID = 'S_CO2', **kwargs): SanUnit.__init__(self, ID, ins, outs, thermo, isdynamic=isdynamic, init_with=init_with, F_BM_default=F_BM_default) @@ -455,6 +456,9 @@ def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, self.calorific_value_fuel = calorific_value_fuel #in KJ/kg (here the considered fuel is natural gas) self.process_efficiency = process_efficiency self.ash_component_ID = ash_component_ID + self.nitrogen_ID = nitrogen_ID + self.water_ID = water_ID + self.carbon_di_oxide_ID = carbon_di_oxide_ID self.Heat_air = None self.Heat_fuel = None self.Heat_sludge = None @@ -506,7 +510,10 @@ def _run(self): flue_gas.phase = 'g' ash.phase = 's' cmps = self.components - + nitrogen_ID = self.nitrogen_ID + water_ID = self.water_ID + carbon_di_oxide_ID = self.carbon_di_oxide_ID + if sludge.phase != 'l': raise ValueError(f'The phase of incoming sludge is expected to be liquid not {sludge.phase}') if air.phase != 'g': @@ -515,8 +522,8 @@ def _run(self): raise ValueError(f'The phase of fuel is expected to be gas not {fuel.phase}') inf = (sludge.mass + air.mass + fuel.mass) - idx_n2 = cmps.index('N2') - idx_h2o = cmps.index('H2O') + idx_n2 = cmps.index(nitrogen_ID) + idx_h2o = cmps.index(water_ID) n2 = inf[idx_n2] h2o = inf[idx_h2o] @@ -529,7 +536,7 @@ def _run(self): mass_co2 = mass_flue_gas - n2*cmps.N2.i_mass - h2o*cmps.H2O.i_mass flue_gas.set_flow([n2, h2o, (mass_co2/cmps.S_CO2.i_mass)], - 'kg/hr', ('S_N2', 'H2O', 'S_CO2')) + 'kg/hr', (nitrogen_ID, water_ID, carbon_di_oxide_ID)) ash_cmp_ID = self.ash_component_ID ash_idx = cmps.index(ash_cmp_ID) ash.set_flow([mass_ash/cmps.i_mass[ash_idx]/(1-cmps.f_Vmass_Totmass[ash_idx])], @@ -559,9 +566,13 @@ def _update_state(self): if ws.state is None: ws.state = np.zeros(len(self._state)+1) - idx_h2o = cmps.index('H2O') - idx_n2 = cmps.index('N2') - idx_co2 = cmps.index('CO2') + nitrogen_ID = self.nitrogen_ID + water_ID = self.water_ID + carbon_di_oxide_ID = self.carbon_di_oxide_ID + + idx_h2o = cmps.index(water_ID) + idx_n2 = cmps.index(nitrogen_ID) + idx_co2 = cmps.index(carbon_di_oxide_ID) ash_idx = cmps.index(self.ash_component_ID) cmps_i_mass = cmps.i_mass cmps_v2tmass = cmps.f_Vmass_Totmass @@ -585,9 +596,13 @@ def _update_state(self): def _update_dstate(self): cmps = self.components - idx_h2o = cmps.index('H2O') - idx_n2 = cmps.index('N2') - idx_co2 = cmps.index('CO2') + nitrogen_ID = self.nitrogen_ID + water_ID = self.water_ID + carbon_di_oxide_ID = self.carbon_di_oxide_ID + + idx_h2o = cmps.index(water_ID) + idx_n2 = cmps.index(nitrogen_ID) + idx_co2 = cmps.index(carbon_di_oxide_ID) ash_idx = cmps.index(self.ash_component_ID) d_state = self._dstate cmps_i_mass = cmps.i_mass From b6cea9ee3d42fcd6414c8efeb7bcc3d06785dac3 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Tue, 13 Dec 2022 17:01:54 -0600 Subject: [PATCH 041/424] Updated Incinerator Updated the run function of incinerator to account for mass balance --- qsdsan/sanunits/_sludge_treatment.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py index a411c5af..320a758b 100644 --- a/qsdsan/sanunits/_sludge_treatment.py +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -13,9 +13,10 @@ for license details. ''' -from .. import SanUnit +from .. import SanUnit, WasteStream import numpy as np + __all__ = ('Thickener', 'DewateringUnit', 'Incinerator') class Thickener(SanUnit): @@ -23,7 +24,6 @@ class Thickener(SanUnit): """ Thickener based on BSM2 Layout. [1] - Parameters ---------- ID : str ID for the Thickener. The default is ''. @@ -126,6 +126,7 @@ def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, self.TSS_removal_perc = TSS_removal_perc self.solids_loading_rate = solids_loading_rate self.h_cylinderical = h_cylinderical + self.mixed = WasteStream() @property def thickener_perc(self): @@ -169,7 +170,8 @@ def TSS_removal_perc(self, TSS_rmv): @property def thickener_factor(self): - inf, = self.ins + self.mixed.mix_from(self.ins) + inf = self.mixed if not self.ins: return elif inf.isempty(): return else: @@ -193,7 +195,8 @@ def thinning_factor(self): def _run(self): - inf, = self.ins + self.mixed.mix_from(self.ins) + inf = self.mixed uf, of = self.outs cmps = self.components @@ -529,11 +532,10 @@ def _run(self): h2o = inf[idx_h2o] mass_ash = np.sum(inf*cmps.i_mass*(1-cmps.f_Vmass_Totmass)) \ - - h2o*cmps.H2O.i_mass*(1-cmps.H2O.f_Vmass_Totmass) + - h2o*cmps.H2O.i_mass*(1-cmps.H2O.f_Vmass_Totmass) - n2*cmps.N2.i_mass*(1-cmps.N2.f_Vmass_Totmass) # Conservation of mass mass_flue_gas = np.sum(inf*cmps.i_mass) - mass_ash - mass_co2 = mass_flue_gas - n2*cmps.N2.i_mass - h2o*cmps.H2O.i_mass flue_gas.set_flow([n2, h2o, (mass_co2/cmps.S_CO2.i_mass)], 'kg/hr', (nitrogen_ID, water_ID, carbon_di_oxide_ID)) @@ -583,7 +585,7 @@ def _update_state(self): self._outs[0].state[idx_n2] = n2 = inf[idx_n2] mass_ash = np.sum(inf*cmps_i_mass*(1-cmps_v2tmass)) \ - - h2o*cmps.H2O.i_mass*(1-cmps_v2tmass[idx_h2o]) + - h2o*cmps.H2O.i_mass*(1-cmps_v2tmass[idx_h2o]) - n2*cmps.N2.i_mass*(1-cmps_v2tmass[idx_n2]) mass_flue_gas = mass_in_tot - mass_ash mass_co2 = mass_flue_gas - n2 - h2o @@ -620,7 +622,7 @@ def _update_dstate(self): d_mass_in_tot = np.sum(d_state*cmps_i_mass) d_mass_ash = np.sum(d_state*cmps_i_mass*(1-cmps_v2tmass)) \ - - d_h2o*cmps.H2O.i_mass*(1-cmps_v2tmass[idx_h2o]) + - d_h2o*cmps.H2O.i_mass*(1-cmps_v2tmass[idx_h2o]) - d_n2*cmps.N2.i_mass*(1-cmps_v2tmass[idx_n2]) d_mass_flue_gas = d_mass_in_tot - d_mass_ash d_mass_co2 = d_mass_flue_gas - d_n2 - d_h2o From 8cf4e048538291ffe10fda890eb4daa39fe75c27 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Wed, 8 Feb 2023 16:10:45 -0600 Subject: [PATCH 042/424] Made the #inlet and #outlet variable Made the number of inlets/outlets in Thickener as 'not fixed' --- qsdsan/sanunits/Membrane_Gas Extraction.py | 348 +++++++++++++++++++++ qsdsan/sanunits/_sludge_treatment.py | 2 + 2 files changed, 350 insertions(+) create mode 100644 qsdsan/sanunits/Membrane_Gas Extraction.py diff --git a/qsdsan/sanunits/Membrane_Gas Extraction.py b/qsdsan/sanunits/Membrane_Gas Extraction.py new file mode 100644 index 00000000..c9e54401 --- /dev/null +++ b/qsdsan/sanunits/Membrane_Gas Extraction.py @@ -0,0 +1,348 @@ +# -*- coding: utf-8 -*- +""" +QSDsan: Quantitative Sustainable Design for sanitation and resource recovery systems + +This module is developed by: + + Ian Song + + Saumitra Rai + + Joy Zhang + +This module is under the University of Illinois/NCSA Open Source License. +Please refer to https://github.com/QSD-Group/QSDsan/blob/main/LICENSE.txt +for license details. +""" + +from .. import SanUnit, WasteStream +import numpy as np + +__all__ = ('Membrane') + +class GasExtractionMembrane(SanUnit): + + _N_ins = 1 + _N_outs = 1 + + _GasPerm = { + 'H2': 650*(3.35e-16), + 'O2': 600*(3.35e-16), + 'N2': 280*(3.35e-16), + 'CO2': 3250*(3.35e-16), + 'CH4': 950*(3.35e-16), + 'H2O': 36000*(3.35e-16) + } + + _HenryPreFac = { + 'H2': 7.8e-6, + 'CO2' : 3.5e-4, + 'CH4': 1.3e-5, + 'O2': 1.2e-5, + 'N2': 6e-6, + 'H2O': 1 + } + + _HenrySlope = { + 'H2': 640, + 'CO2' : 2600, + 'CH4' : 1900, + 'O2': 1800, + 'N2': 1300, + 'H2O': 1 + } + + _WilkeChang = { + 'H2': 9.84, + 'CO2': 2.6, + 'CH4': 2.2, + 'O2': 1.90, + 'N2': 1.77, + 'H2O': 1 + } + + # Constructor: Initialize the instance variables + def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, + init_with='WasteStream', F_BM_default=None, FiberID=0.05, FiberOD=0.10, + MemThick=0.02, NumTubes=5, ShellDia=0.5, SurfArea=400, + GasID = ['H2', 'CO2', 'CH4', 'O2', 'N2', 'H2O'], PVac = 97.325, segs = 500, VolBatchTank=1000, + GasPerm = {}, HenryPreFac = {}, HenrySlope = {}, WilkeChang = {}): + + SanUnit.__init__(self, ID, ins, outs, thermo, isdynamic=isdynamic, + init_with=init_with, F_BM_default=F_BM_default) + self.FiberID = FiberID # Fiber Inner Diameter [m] + self.FiberOD = FiberOD # Fiber Outer Diameter [m] + self.MemThick = MemThick # Membrane Thickness [m] + self.NumTubes = NumTubes # Number of Tubes [] + self.ShellDia = ShellDia # Shell Diameter [m] + self.SurfArea = SurfArea # Surface Area [m^2] + self.GasID = GasID # IDs of gas used in the process + self.PVac = PVac # Operating Vacuum Pressure [-kPa] + self.segs = segs # Number of segments ??Ask Ian?? + self.Volume = VolBatchTank # Volume of the bioreactor + + dct_gas_perm = GasPerm or self._GasPerm + self.set_GasPerm(**dct_gas_perm) + dct_gas_hpf = HenryPreFac or self._HenryPreFac + self.set_HenryPreFac(**dct_gas_hpf) + dct_gas_hs = HenrySlope or self._HenrySlope + self.set_HenrySlope(**dct_gas_hs) + dct_gas_wc = WilkeChang or self._WilkeChang + self.set_WilkeChang(**dct_gas_wc) + + inf, = self.ins + cmps = inf.components + self.indexer = cmps.index + self.idx = cmps.indices(self.GasID) + for i, ID in enumerate(GasID): + if ID == 'H2O': + self.h2o_j = i + break + + def set_GasPerm(self, **kwargs): + self.set_prop('_gasp', **kwargs) + + def set_WilkeChang(self, **kwargs): + self.set_prop('_wc', **kwargs) + + def set_HenryPreFac(self, **kwargs): + self.set_prop('_hpf', **kwargs) + + def set_HenrySlope(self, **kwargs): + self.set_prop('_hs', **kwargs) + + def set_prop(self, attr_name, **kwargs): + idxr = self.indexer + try: attr = getattr(self, attr_name) + except: attr = self.__dict__[attr_name] = np.zeros(len(self.chemicals)) + for k, v in kwargs.items(): + attr[idxr(k)] = v + + # Calculate the volume fraction of the lumen to the shell. + @property + def VolFrac(self): + lumenVol = self.NumTubes*np.pi*((self.FiberID/2)**2) + shellVol = (np.pi*((self.ShellDia/2)**2)) - self.NumTubes*np.pi*((self.FiberOD/2)**2) + return lumenVol/shellVol + + # Calculate the effective length of the membrane by taking the ratio of the declared surface area, and dividing it by the area per length of the tube. + @property + def Length(self): + memSurf = self.NumTubes*np.pi*self.FiberOD + return self.SurfArea/memSurf + + # Determine the Shell cross-sectional area + @property + def ShellAc(self): + return np.pi*((self.ShellDia/2)**2) - self.NumTubes*np.pi*((self.FiberOD/2)**2) + + @property + def HeL(self): + # Define the constant properties of gas + TRefH = 298.15 # Reference T for Henry's Law [K] + NIST_HeL = self._hpf*(np.exp(self._hs*(1/self.ins[0].T - 1/TRefH))) + return 1/NIST_HeL + + @property + def Diff(self): + inf, = self.ins + cmps = inf.components + self._Vc = np.array([cmp.Vc for cmp in cmps]) + Phi = self._wc # Wilke Chang Correlation Factor + + # Define the constant properties of gas + TRefMu = 300 # Reference T for H2O Viscosity [K] + PermC = 3.35*(10**(-16)) # Conversion b/w Barrer and SI Units + MWH2O = cmps.H2O.MW # Molar Weight of the Solvent [Da] + + # Reduced Temp for Water Viscosity + Tb = self.ins[0].T/TRefMu + + # Temperature Dependent Water Viscosity [cP] + mu = (1*10**(-3))*(280.68*(Tb**(-1.9)) + 511.45*(Tb**(-7.7)) + 61.131*(Tb**(-19.6)) + 0.45903*(Tb**(-40)) ) + + # Molar Volume at Normal Boiling Point [cm^3/mol] + # Yoel: Critical models are not very reliable + # Yoel: Eqns of State or other models already available in QSDsan can be looked into + V1 = 0.285*(self._Vc*1000000)**(1.048) # unit conversion from m^3/mol (QSDsan) to cm^3/mol (here) + # Diffusion Coefficient [m^2/s] + D = 0.0001*(7.4*10**(-8))*np.sqrt(MWH2O*Phi)*self.ins[0].T/(mu*V1**(0.6)) + + return D + + def _init_state(self): + inf, = self.ins + cmps = inf.components + # ASSUMPTION: Only 1 influent + C = self._ins_QC[:-1]/cmps.chem_MW*cmps.i_mass + Cs = C[self.idx] + #Q = self._ins_QC[-1] + Seg = self.segments + numGas = len(self.GasID) + + # How to define self._dstate? + + self._state = np.zeros(2*Seg*numGas) + for i in range(0, 2*Seg*numGas, 2*numGas): + for j in range(numGas): + self._state[j+i] = Cs # conc. in mol/m^3 as defined by Ian + + self._dstate = self._state*0 + + + #def transientGasExtraction(t, C, ExpCond, GasVec, Mem, Segs, SS): + def _compile_ODE(self): + # Synthesizes the ODEs to simulate a batch reactor with side-flow gas extraction. The code takes in an object of class Membrane (Mem) and an array of objects of class Gas (GasVec). It also takes in an array of experimental conditions ExpCond. + + # Extract Operating Parameters from ExpCond + Q = self.ins.F_vol*(1000/60) # Volumetric Flowrate [L/min] + T = self.ins[0].T # Temperature [K] + P = self.PVac*1000 # Vacuum Pressure [Pa] + V = self.Volume # Volume of the Batch Tank [L] + + # Calculate vapor pressure of water at operating temperature + TCel = T-273.15 # Temperature [C] + PVapH2O = np.exp(34.494-(4924.99/(TCel+237.1)))/((TCel+105)**1.57) # Saturated Vapor Pressure of Water [Pa] + + # Define Constants + R = 8.314 # Universal Gas Constant [J/K mol] + + # Extract Membrane Properties + D = self.FiberID # Membrane Fiber ID [m] + l = self.MemThick # Membrane Thickness [m] + num_tubes = self.NumTubes # Number of Tubes in the Module [] + L = self.Length # Membrane Length [m] + Segs = self.segments # Number of segments? Ask Ian + vFrac = self.VolFrac # Lumen/Shell Volume Fraction [m^3/m^3] + ShellAc = self.ShellAc # Shell Cross-Sectional Area [m^2] + + # Pre-allocate vectors for gas thermophysical properties + numGas = len(self.GasID) + numVec = 2*numGas +# ============================================================================= +# Diff = np.zeros(numGas) +# Perm_SI = np.zeros(numGas) +# H = np.zeros(numGas) +# Cin = np.zeros(numGas) +# MM = np.zeros(numGas) +# ============================================================================= + + inf, = self.ins + cmps = inf.components + # Extract Gas Properties + #for i in range(0,numGas): + + #Diff[i] = GasVec[i].Diff() + Diff = self.Diff + + #Perm_SI[i] = Mem.PermDict[GasVec[i].Name] + Perm_SI = self._gasp + + #H[i] = GasVec[i].HeL() + H = self.HeL + + #Cin[i] = GasVec[i].Cin() + Cins = self.ins[0].conc/cmps.MW*cmps.i_mass # inlet molar concentration [mol/m^3] + Cin = Cins[self.idx] # self.idx ensures that it is only for gases + MMs = cmps.MW + MM = MMs[self.idx] # self.idx ensures that it is only for gases + + #Diff = np.array([0.0265e-7, 0.0296e-7, 0.0253e-7, 0.3199e-7]) + # Calculate dx and u + dx = L/Segs # Length of Segments [m] + u = Q/((np.pi*D**2/4)*num_tubes*1000*60) # Linear Flow Velocity [m/s] + + # Calculate the Kinematic Viscosity of Water + Tb = T/300 # Reduced Temperature [] + mu = (1e-6)*(280.68*(Tb**(-1.9)) + 511.45*(Tb**(-7.7)) + 61.131*(Tb**(-19.6)) + 0.45903*(Tb**(-40))) # Absolute Viscosity of Water [Pa s] + rho = (-13.851 + 0.64038*T - 1.9124e-3*T**2 + 1.8211e-6*T**3)*18 # Liquid Density of Water [kg/m^3] from DIPPR + nu = mu/rho # Kinematic Viscosity of Water [m^2/s] + + # Calculate the dimensionless numbers + # Reynolds + Re = u*D/nu + # Schmidt + Sc = nu/Diff + # Sherwood + Sh = 1.615*(Re*Sc*D/L)**(1/3) + + + # Calculate Mass Transfer Coefficients + KMem = Perm_SI/l + KLiq = Sh*Diff/D + KTot = 1/(1/KLiq + 1/(KMem*H)) + for j in range(0, numGas): + #if GasVec[j].Name == 'H2O': + if j == self.h2o_j: + KTot[j] = KMem[j] + + #print(H*KMem) + #print(KLiq) + #print(KTot) + # Synthesize the output vector of concentration differences + + # Initialize + C = self._state + dC = self._dstate + #dC = np.zeros(Segs*numGas*2) + + sumCp_init = P/(R*T) + sumCp_fin = np.zeros(Segs) + + + # For the first segment: + for j in range(0, numGas): + #if GasVec[j].Name == 'H2O': + if j == self.h2o_j: + dC[j+numGas] = (KTot[j]/(D/4))*(PVapH2O- (C[j+numGas]/sumCp_init)*P)*vFrac + dC[j] = 0 + else: + dC[j] = (u/dx)*(C[Segs*numVec+j] - C[j]) - (KTot[j]/(D/4))*(C[j] - (C[j+numGas]/sumCp_init)*P/H[j]) + dC[j+numGas] = (KTot[j]/(D/4))*(C[j]-(C[j+numGas]/sumCp_init)*P/H[j])*vFrac + + # Calculate the total gas concentration in the shell after the change + sumCp_fin[0] += C[j+numGas] + dC[j+numGas] + + for i in range(1,Segs): + # For the remaining segments: + # Calculate the rate of change of the shell and lumen for all remaining segments. + for j in range(0, numGas): + + # Lumen + dC[numVec*(i)+j] = (u/dx)*(C[numVec*(i-1)+j] - C[numVec*(i)+j]) - (KTot[j]/(D/4))*(C[numVec*(i)+j] - (C[numVec*(i)+j+numGas]/sumCp_init)*(P/H[j])) + + # Shell + dC[numVec*(i)+j+numGas] = (KTot[j]/(D/4))*(C[numVec*(i)+j] - (C[numVec*(i)+j+numGas]/sumCp_init)*(P/H[j]))*vFrac + + # If the gas is H2O, then it follows a different formula: + #if GasVec[j].Name == 'H2O': + if j == self.h2o_j: + dC[numVec*i+j+numGas] = (KTot[j]/(D/4))*(PVapH2O-(C[numVec*i+j+numGas]/sumCp_init)*P)*vFrac + dC[numVec*i+j] = 0 + + # Calculate the total gas concentration in the shell after the change + sumCp_fin[i] += C[numVec*(i)+j+numGas] + dC[numVec*(i)+j+numGas] + + + # Calculate changes in tank concentration based the values of SS + for j in range(0, numGas): + if SS == 0: + dC[Segs*numVec+j] = ((Q/60)/V)*(C[numVec*(Segs-1)+j] - C[j]) + if SS == 1: + dC[Segs*numVec+j] = 0 + + # Re-scale the shell concentration so that vacuum pressure stays constant during the entire process. Given the change of concentration that we have calculated above for the shell, we can re-scale the shell concentrations with the sumCp at each segment. + + for i in range(0, Segs): + for j in range(0, numGas): + # Calculate the new concentration of gases in the shell + newCp = (C[numVec*(i)+j+numGas] + dC[numVec*(i)+j+numGas]) + + # Re-scale the concentration to vacuum pressure + newCp = (newCp/sumCp_fin[i])*P/(R*T) + + # Calculate the actual difference of concentration that the function will output + dC[numVec*(i)+j+numGas] = newCp- C[numVec*(i)+j+numGas] + + # Return the difference in concentration + return dC \ No newline at end of file diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py index 320a758b..b5832298 100644 --- a/qsdsan/sanunits/_sludge_treatment.py +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -115,6 +115,8 @@ class Thickener(SanUnit): _N_ins = 1 _N_outs = 2 + _ins_size_is_fixed = False + _outs_size_is_fixed = False def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, init_with='WasteStream', F_BM_default=None, thickener_perc=7, From 2b0f7d0e16180c63889536436c8c5565c99a950a Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Fri, 10 Feb 2023 14:24:05 -0600 Subject: [PATCH 043/424] GasExtractionMembrane Adding new SanUnit called GasExtractionMembrane, for extraction of gases from wastestream. --- qsdsan/sanunits/_membrane_gas_extraction.py | 386 ++++++++++++++++++++ 1 file changed, 386 insertions(+) create mode 100644 qsdsan/sanunits/_membrane_gas_extraction.py diff --git a/qsdsan/sanunits/_membrane_gas_extraction.py b/qsdsan/sanunits/_membrane_gas_extraction.py new file mode 100644 index 00000000..efbf6969 --- /dev/null +++ b/qsdsan/sanunits/_membrane_gas_extraction.py @@ -0,0 +1,386 @@ +# -*- coding: utf-8 -*- +""" +QSDsan: Quantitative Sustainable Design for sanitation and resource recovery systems + +This module is developed by: + + Ian Song + + Saumitra Rai + + Joy Zhang + +This module is under the University of Illinois/NCSA Open Source License. +Please refer to https://github.com/QSD-Group/QSDsan/blob/main/LICENSE.txt +for license details. +""" + +from qsdsan import SanUnit +import numpy as np + +__all__ = ('Membrane') + +class GasExtractionMembrane(SanUnit): + + _N_ins = 1 + _N_outs = 1 + + # All gas properties are in form of dictionaries + + _GasPerm = { + 'H2': 650*(3.35e-16), + 'O2': 600*(3.35e-16), + 'N2': 280*(3.35e-16), + 'CO2': 3250*(3.35e-16), + 'CH4': 950*(3.35e-16), + 'H2O': 36000*(3.35e-16) + } + + _HenryPreFac = { + 'H2': 7.8e-6, + 'CO2' : 3.5e-4, + 'CH4': 1.3e-5, + 'O2': 1.2e-5, + 'N2': 6e-6, + 'H2O': 1 + } + + _HenrySlope = { + 'H2': 640, + 'CO2' : 2600, + 'CH4' : 1900, + 'O2': 1800, + 'N2': 1300, + 'H2O': 1 + } + + _WilkeChang = { + 'H2': 9.84, + 'CO2': 2.6, + 'CH4': 2.2, + 'O2': 1.90, + 'N2': 1.77, + 'H2O': 1 + } + + # Constructor: Initialize the instance variables + def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, + init_with='WasteStream', F_BM_default=None, FiberID=190e-6, + FiberOD=300e-6, NumTubes=1512, ShellDia=1.89e-2, SurfArea=0.1199, + GasID = ['H2', 'CO2', 'CH4', 'O2', 'N2', 'H2O'], PVac = 97.325, + segs = 50, GasPerm = {}, HenryPreFac = {}, HenrySlope = {}, + WilkeChang = {}): + + SanUnit.__init__(self, ID, ins, outs, thermo, isdynamic=isdynamic, + init_with=init_with, F_BM_default=F_BM_default) + self.FiberID = FiberID # Fiber Inner Diameter [m] + self.FiberOD = FiberOD # Fiber Outer Diameter [m] + self.MemThick = (FiberOD - FiberID)/2 # Membrane Thickness [m] + self.NumTubes = NumTubes # Number of Tubes [] + self.ShellDia = ShellDia # Shell Diameter [m] + self.SurfArea = SurfArea # Surface Area [m^2] + self.GasID = GasID # IDs of gas used in the process + self.PVac = PVac # Operating Vacuum Pressure [-kPa] + self.segs = segs # Number of segments ??Ask Ian?? + #self.Volume = VolBatchTank # Volume of the bioreactor (Don't think this is needed) + + dct_gas_perm = GasPerm or self._GasPerm + self.set_GasPerm(**dct_gas_perm) + dct_gas_hpf = HenryPreFac or self._HenryPreFac + self.set_HenryPreFac(**dct_gas_hpf) + dct_gas_hs = HenrySlope or self._HenrySlope + self.set_HenrySlope(**dct_gas_hs) + dct_gas_wc = WilkeChang or self._WilkeChang + self.set_WilkeChang(**dct_gas_wc) + + inf, = self.ins + cmps = inf.components + self.indexer = cmps.index + # self.idx ensures that the indexing in further code is only for gases + # and not all components in the influent + self.idx = cmps.indices(self.GasID) + # to save the index of water in the array of gases, to be used later + for i, ID in enumerate(GasID): + if ID == 'H2O': + self.h2o_j = i + break + @property + def FiberOD(self): + return self._FiberOD + + @FiberOD.setter + def FiberOD(self, FiberOD): + if FiberOD is not None: + self._FiberOD = FiberOD + else: + raise ValueError('FiberOD expected from user') + + @property + def FiberID(self): + return self._FiberID + + @FiberID.setter + def FiberID(self, FiberID): + if FiberID is not None: + self._FiberID = FiberID + else: + raise ValueError('Inner Diameter of fiber expected from user') + + @property + def NumTubes(self): + return self._NumTubes + + @NumTubes.setter + def NumTubes(self, NumTubes): + if NumTubes is not None: + self._NumTubes = NumTubes + else: + raise ValueError('Outer diameter of fiber expected from user') + + @property + def ShellDia(self): + return self._ShellDia + + @ShellDia.setter + def ShellDia(self, ShellDia): + if ShellDia is not None: + self._ShellDia = ShellDia + else: + raise ValueError('Diameter of the shell expected from user') + + @property + def SurfArea(self): + return self._SurfArea + + @SurfArea.setter + def SurfArea(self, SurfArea): + if SurfArea is not None: + self._SurfArea = SurfArea + else: + raise ValueError('Surface Area of Membrane expected from user') + + def set_GasPerm(self, **kwargs): + self.set_prop('_gasp', **kwargs) + + def set_WilkeChang(self, **kwargs): + self.set_prop('_wc', **kwargs) + + def set_HenryPreFac(self, **kwargs): + self.set_prop('_hpf', **kwargs) + + def set_HenrySlope(self, **kwargs): + self.set_prop('_hs', **kwargs) + + def set_prop(self, attr_name, **kwargs): + idxr = self.indexer + try: attr = getattr(self, attr_name) + except: attr = self.__dict__[attr_name] = np.zeros(len(self.chemicals)) + for k, v in kwargs.items(): + attr[idxr(k)] = v + + # Calculate the volume fraction of the lumen to the shell. + @property + def VolFrac(self): + lumenVol = self.NumTubes*np.pi*((self.FiberID/2)**2) + shellVol = (np.pi*((self.ShellDia/2)**2)) - self.NumTubes*np.pi*((self.FiberOD/2)**2) + return lumenVol/shellVol + + # Calculate the effective length of the membrane by taking the ratio of the + # declared surface area, and dividing it by the area per length of the tube. + @property + def Length(self): + memSurf = self.NumTubes*np.pi*self.FiberOD + return self.SurfArea/memSurf + + # Determine the Shell cross-sectional area + @property + def ShellAc(self): + return np.pi*((self.ShellDia/2)**2) - self.NumTubes*np.pi*((self.FiberOD/2)**2) + + @property + def HeL(self): + # Define the constant properties of gas + TRefH = 298.15 # Reference T for Henry's Law [K] + NIST_HeL = self._hpf*(np.exp(self._hs*(1/self.ins[0].T - 1/TRefH))) + return 1/NIST_HeL + + @property + def Diff(self): + inf, = self.ins + cmps = inf.components + self._Vc = np.array([cmp.Vc for cmp in cmps]) + Phi = self._wc # Wilke Chang Correlation Factor + + # Define the constant properties of gas + TRefMu = 300 # Reference T for H2O Viscosity [K] + MWH2O = cmps.H2O.MW # Molar Weight of the Solvent [Da] + + # Reduced Temp for Water Viscosity + Tb = self.ins[0].T/TRefMu + + # Temperature Dependent Water Viscosity [cP] + mu = (1*10**(-3))*(280.68*(Tb**(-1.9)) + 511.45*(Tb**(-7.7)) + 61.131*(Tb**(-19.6)) + 0.45903*(Tb**(-40)) ) + + # Molar Volume at Normal Boiling Point [cm^3/mol] + # Yoel: Critical models are not very reliable + # Yoel: Eqns of State or other models already available in QSDsan can be looked into + V1 = 0.285*(self._Vc*1000000)**(1.048) # unit conversion from m^3/mol (QSDsan) to cm^3/mol (here) + # Diffusion Coefficient [m^2/s] + D = 0.0001*(7.4*10**(-8))*np.sqrt(MWH2O*Phi)*self.ins[0].T/(mu*V1**(0.6)) + return D + + def _init_state(self): + inf, = self.ins + cmps = inf.components + # ASSUMPTION: Only 1 influent + C = self._ins_QC[:-1]/cmps.chem_MW*cmps.i_mass # conc. in mol/m^3 as defined by Ian + Cs = C[self.idx] #self.idx ensures its only for gases + #Q = self._ins_QC[-1] + Seg = self.segments + numGas = len(self.GasID) + + self._state = np.zeros(2*Seg*numGas) + for i in range(0, 2*Seg*numGas, 2*numGas): + for j in range(numGas): + self._state[j+i] = Cs[j] + + self._dstate = self._state*0 + + + #def transientGasExtraction(t, C, ExpCond, GasVec, Mem, Segs, SS): + def _compile_ODE(self): + # Synthesizes the ODEs to simulate a batch reactor with side-flow gas extraction. The code takes in an object of class Membrane (Mem) and an array of objects of class Gas (GasVec). It also takes in an array of experimental conditions ExpCond. + + # Extract Operating Parameters from ExpCond + Q = self.ins.F_vol*(1000/60) # Volumetric Flowrate [L/min] + T = self.ins[0].T # Temperature [K] + P = self.PVac*1000 # Vacuum Pressure [Pa] + #V = self.Volume # Volume of the Batch Tank [L] + + # Calculate vapor pressure of water at operating temperature + TCel = T-273.15 # Temperature [C] + PVapH2O = np.exp(34.494-(4924.99/(TCel+237.1)))/((TCel+105)**1.57) # Saturated Vapor Pressure of Water [Pa] + + # Define Constants + R = 8.314 # Universal Gas Constant [J/K mol] + + # Extract Membrane Properties + D = self.FiberID # Membrane Fiber ID [m] + l = self.MemThick # Membrane Thickness [m] + num_tubes = self.NumTubes # Number of Tubes in the Module [] + L = self.Length # Membrane Length [m] + Segs = self.segments # Number of segments? Ask Ian + vFrac = self.VolFrac # Lumen/Shell Volume Fraction [m^3/m^3] + + # Pre-allocate vectors for gas thermophysical properties + numGas = len(self.GasID) + numVec = 2*numGas +# ============================================================================= +# Diff = np.zeros(numGas) +# Perm_SI = np.zeros(numGas) +# H = np.zeros(numGas) +# Cin = np.zeros(numGas) +# MM = np.zeros(numGas) +# ============================================================================= + + inf, = self.ins + cmps = inf.components + # Extract Gas Properties + #for i in range(0,numGas): + + #Diff[i] = GasVec[i].Diff() + Diff = self.Diff + + #Perm_SI[i] = Mem.PermDict[GasVec[i].Name] + Perm_SI = self._gasp + + #H[i] = GasVec[i].HeL() + H = self.HeL + + #Diff = np.array([0.0265e-7, 0.0296e-7, 0.0253e-7, 0.3199e-7]) + # Calculate dx and u + dx = L/Segs # Length of Segments [m] + u = Q/((np.pi*D**2/4)*num_tubes*1000*60) # Linear Flow Velocity [m/s] + + # Calculate the Kinematic Viscosity of Water + Tb = T/300 # Reduced Temperature [] + mu = (1e-6)*(280.68*(Tb**(-1.9)) + 511.45*(Tb**(-7.7)) + 61.131*(Tb**(-19.6)) + 0.45903*(Tb**(-40))) # Absolute Viscosity of Water [Pa s] + rho = (-13.851 + 0.64038*T - 1.9124e-3*T**2 + 1.8211e-6*T**3)*18 # Liquid Density of Water [kg/m^3] from DIPPR + nu = mu/rho # Kinematic Viscosity of Water [m^2/s] + + # Calculate the dimensionless numbers + # Reynolds + Re = u*D/nu + # Schmidt + Sc = nu/Diff + # Sherwood + Sh = 1.615*(Re*Sc*D/L)**(1/3) + + + # Calculate Mass Transfer Coefficients + KMem = Perm_SI/l + KLiq = Sh*Diff/D + KTot = 1/(1/KLiq + 1/(KMem*H)) + for j in range(0, numGas): + #if GasVec[j].Name == 'H2O': + if j == self.h2o_j: + KTot[j] = KMem[j] + + # Initialize + C = self._state + dC = self._dstate + + sumCp_init = P/(R*T) + sumCp_fin = np.zeros(Segs) + + C = self._ins_QC[:-1]/cmps.chem_MW*cmps.i_mass # conc. in mol/m^3 as defined by Ian + Cs = C[self.idx] #self.idx ensures its only for gases + + # For the first segment: + for j in range(0, numGas): + #if GasVec[j].Name == 'H2O': + if j == self.h2o_j: + dC[j+numGas] = (KTot[j]/(D/4))*(PVapH2O- (C[j+numGas]/sumCp_init)*P)*vFrac + dC[j] = 0 + else: + dC[j] = (u/dx)*(Cs[j] - C[j]) - (KTot[j]/(D/4))*(C[j] - (C[j+numGas]/sumCp_init)*P/H[j]) + dC[j+numGas] = (KTot[j]/(D/4))*(C[j]-(C[j+numGas]/sumCp_init)*P/H[j])*vFrac + + # Calculate the total gas concentration in the shell after the change + sumCp_fin[0] += C[j+numGas] + dC[j+numGas] + + for i in range(1,Segs): + # For the remaining segments: + # Calculate the rate of change of the shell and lumen for all remaining segments. + for j in range(0, numGas): + + # Lumen + dC[numVec*(i)+j] = (u/dx)*(C[numVec*(i-1)+j] - C[numVec*(i)+j]) - (KTot[j]/(D/4))*(C[numVec*(i)+j] - (C[numVec*(i)+j+numGas]/sumCp_init)*(P/H[j])) + + # Shell + dC[numVec*(i)+j+numGas] = (KTot[j]/(D/4))*(C[numVec*(i)+j] - (C[numVec*(i)+j+numGas]/sumCp_init)*(P/H[j]))*vFrac + + # If the gas is H2O, then it follows a different formula: + #if GasVec[j].Name == 'H2O': + if j == self.h2o_j: + dC[numVec*i+j+numGas] = (KTot[j]/(D/4))*(PVapH2O-(C[numVec*i+j+numGas]/sumCp_init)*P)*vFrac + dC[numVec*i+j] = 0 + + # Calculate the total gas concentration in the shell after the change + sumCp_fin[i] += C[numVec*(i)+j+numGas] + dC[numVec*(i)+j+numGas] + + # Re-scale the shell concentration so that vacuum pressure stays constant during the entire process. Given the change of concentration that we have calculated above for the shell, we can re-scale the shell concentrations with the sumCp at each segment. + + for i in range(0, Segs): + for j in range(0, numGas): + # Calculate the new concentration of gases in the shell + newCp = (C[numVec*(i)+j+numGas] + dC[numVec*(i)+j+numGas]) + + # Re-scale the concentration to vacuum pressure + newCp = (newCp/sumCp_fin[i])*P/(R*T) + + # Calculate the actual difference of concentration that the function will output + dC[numVec*(i)+j+numGas] = newCp- C[numVec*(i)+j+numGas] + + # Return the difference in concentration + return dC \ No newline at end of file From 44b39bd0d77b87c46770b2696c4bfc70d25c3d9a Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Fri, 10 Feb 2023 14:39:23 -0600 Subject: [PATCH 044/424] Deleting duplicate copy Deleting duplicate copy of a SanUnit --- qsdsan/sanunits/Membrane_Gas Extraction.py | 348 -------------------- qsdsan/sanunits/_membrane_gas_extraction.py | 2 +- 2 files changed, 1 insertion(+), 349 deletions(-) delete mode 100644 qsdsan/sanunits/Membrane_Gas Extraction.py diff --git a/qsdsan/sanunits/Membrane_Gas Extraction.py b/qsdsan/sanunits/Membrane_Gas Extraction.py deleted file mode 100644 index c9e54401..00000000 --- a/qsdsan/sanunits/Membrane_Gas Extraction.py +++ /dev/null @@ -1,348 +0,0 @@ -# -*- coding: utf-8 -*- -""" -QSDsan: Quantitative Sustainable Design for sanitation and resource recovery systems - -This module is developed by: - - Ian Song - - Saumitra Rai - - Joy Zhang - -This module is under the University of Illinois/NCSA Open Source License. -Please refer to https://github.com/QSD-Group/QSDsan/blob/main/LICENSE.txt -for license details. -""" - -from .. import SanUnit, WasteStream -import numpy as np - -__all__ = ('Membrane') - -class GasExtractionMembrane(SanUnit): - - _N_ins = 1 - _N_outs = 1 - - _GasPerm = { - 'H2': 650*(3.35e-16), - 'O2': 600*(3.35e-16), - 'N2': 280*(3.35e-16), - 'CO2': 3250*(3.35e-16), - 'CH4': 950*(3.35e-16), - 'H2O': 36000*(3.35e-16) - } - - _HenryPreFac = { - 'H2': 7.8e-6, - 'CO2' : 3.5e-4, - 'CH4': 1.3e-5, - 'O2': 1.2e-5, - 'N2': 6e-6, - 'H2O': 1 - } - - _HenrySlope = { - 'H2': 640, - 'CO2' : 2600, - 'CH4' : 1900, - 'O2': 1800, - 'N2': 1300, - 'H2O': 1 - } - - _WilkeChang = { - 'H2': 9.84, - 'CO2': 2.6, - 'CH4': 2.2, - 'O2': 1.90, - 'N2': 1.77, - 'H2O': 1 - } - - # Constructor: Initialize the instance variables - def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, - init_with='WasteStream', F_BM_default=None, FiberID=0.05, FiberOD=0.10, - MemThick=0.02, NumTubes=5, ShellDia=0.5, SurfArea=400, - GasID = ['H2', 'CO2', 'CH4', 'O2', 'N2', 'H2O'], PVac = 97.325, segs = 500, VolBatchTank=1000, - GasPerm = {}, HenryPreFac = {}, HenrySlope = {}, WilkeChang = {}): - - SanUnit.__init__(self, ID, ins, outs, thermo, isdynamic=isdynamic, - init_with=init_with, F_BM_default=F_BM_default) - self.FiberID = FiberID # Fiber Inner Diameter [m] - self.FiberOD = FiberOD # Fiber Outer Diameter [m] - self.MemThick = MemThick # Membrane Thickness [m] - self.NumTubes = NumTubes # Number of Tubes [] - self.ShellDia = ShellDia # Shell Diameter [m] - self.SurfArea = SurfArea # Surface Area [m^2] - self.GasID = GasID # IDs of gas used in the process - self.PVac = PVac # Operating Vacuum Pressure [-kPa] - self.segs = segs # Number of segments ??Ask Ian?? - self.Volume = VolBatchTank # Volume of the bioreactor - - dct_gas_perm = GasPerm or self._GasPerm - self.set_GasPerm(**dct_gas_perm) - dct_gas_hpf = HenryPreFac or self._HenryPreFac - self.set_HenryPreFac(**dct_gas_hpf) - dct_gas_hs = HenrySlope or self._HenrySlope - self.set_HenrySlope(**dct_gas_hs) - dct_gas_wc = WilkeChang or self._WilkeChang - self.set_WilkeChang(**dct_gas_wc) - - inf, = self.ins - cmps = inf.components - self.indexer = cmps.index - self.idx = cmps.indices(self.GasID) - for i, ID in enumerate(GasID): - if ID == 'H2O': - self.h2o_j = i - break - - def set_GasPerm(self, **kwargs): - self.set_prop('_gasp', **kwargs) - - def set_WilkeChang(self, **kwargs): - self.set_prop('_wc', **kwargs) - - def set_HenryPreFac(self, **kwargs): - self.set_prop('_hpf', **kwargs) - - def set_HenrySlope(self, **kwargs): - self.set_prop('_hs', **kwargs) - - def set_prop(self, attr_name, **kwargs): - idxr = self.indexer - try: attr = getattr(self, attr_name) - except: attr = self.__dict__[attr_name] = np.zeros(len(self.chemicals)) - for k, v in kwargs.items(): - attr[idxr(k)] = v - - # Calculate the volume fraction of the lumen to the shell. - @property - def VolFrac(self): - lumenVol = self.NumTubes*np.pi*((self.FiberID/2)**2) - shellVol = (np.pi*((self.ShellDia/2)**2)) - self.NumTubes*np.pi*((self.FiberOD/2)**2) - return lumenVol/shellVol - - # Calculate the effective length of the membrane by taking the ratio of the declared surface area, and dividing it by the area per length of the tube. - @property - def Length(self): - memSurf = self.NumTubes*np.pi*self.FiberOD - return self.SurfArea/memSurf - - # Determine the Shell cross-sectional area - @property - def ShellAc(self): - return np.pi*((self.ShellDia/2)**2) - self.NumTubes*np.pi*((self.FiberOD/2)**2) - - @property - def HeL(self): - # Define the constant properties of gas - TRefH = 298.15 # Reference T for Henry's Law [K] - NIST_HeL = self._hpf*(np.exp(self._hs*(1/self.ins[0].T - 1/TRefH))) - return 1/NIST_HeL - - @property - def Diff(self): - inf, = self.ins - cmps = inf.components - self._Vc = np.array([cmp.Vc for cmp in cmps]) - Phi = self._wc # Wilke Chang Correlation Factor - - # Define the constant properties of gas - TRefMu = 300 # Reference T for H2O Viscosity [K] - PermC = 3.35*(10**(-16)) # Conversion b/w Barrer and SI Units - MWH2O = cmps.H2O.MW # Molar Weight of the Solvent [Da] - - # Reduced Temp for Water Viscosity - Tb = self.ins[0].T/TRefMu - - # Temperature Dependent Water Viscosity [cP] - mu = (1*10**(-3))*(280.68*(Tb**(-1.9)) + 511.45*(Tb**(-7.7)) + 61.131*(Tb**(-19.6)) + 0.45903*(Tb**(-40)) ) - - # Molar Volume at Normal Boiling Point [cm^3/mol] - # Yoel: Critical models are not very reliable - # Yoel: Eqns of State or other models already available in QSDsan can be looked into - V1 = 0.285*(self._Vc*1000000)**(1.048) # unit conversion from m^3/mol (QSDsan) to cm^3/mol (here) - # Diffusion Coefficient [m^2/s] - D = 0.0001*(7.4*10**(-8))*np.sqrt(MWH2O*Phi)*self.ins[0].T/(mu*V1**(0.6)) - - return D - - def _init_state(self): - inf, = self.ins - cmps = inf.components - # ASSUMPTION: Only 1 influent - C = self._ins_QC[:-1]/cmps.chem_MW*cmps.i_mass - Cs = C[self.idx] - #Q = self._ins_QC[-1] - Seg = self.segments - numGas = len(self.GasID) - - # How to define self._dstate? - - self._state = np.zeros(2*Seg*numGas) - for i in range(0, 2*Seg*numGas, 2*numGas): - for j in range(numGas): - self._state[j+i] = Cs # conc. in mol/m^3 as defined by Ian - - self._dstate = self._state*0 - - - #def transientGasExtraction(t, C, ExpCond, GasVec, Mem, Segs, SS): - def _compile_ODE(self): - # Synthesizes the ODEs to simulate a batch reactor with side-flow gas extraction. The code takes in an object of class Membrane (Mem) and an array of objects of class Gas (GasVec). It also takes in an array of experimental conditions ExpCond. - - # Extract Operating Parameters from ExpCond - Q = self.ins.F_vol*(1000/60) # Volumetric Flowrate [L/min] - T = self.ins[0].T # Temperature [K] - P = self.PVac*1000 # Vacuum Pressure [Pa] - V = self.Volume # Volume of the Batch Tank [L] - - # Calculate vapor pressure of water at operating temperature - TCel = T-273.15 # Temperature [C] - PVapH2O = np.exp(34.494-(4924.99/(TCel+237.1)))/((TCel+105)**1.57) # Saturated Vapor Pressure of Water [Pa] - - # Define Constants - R = 8.314 # Universal Gas Constant [J/K mol] - - # Extract Membrane Properties - D = self.FiberID # Membrane Fiber ID [m] - l = self.MemThick # Membrane Thickness [m] - num_tubes = self.NumTubes # Number of Tubes in the Module [] - L = self.Length # Membrane Length [m] - Segs = self.segments # Number of segments? Ask Ian - vFrac = self.VolFrac # Lumen/Shell Volume Fraction [m^3/m^3] - ShellAc = self.ShellAc # Shell Cross-Sectional Area [m^2] - - # Pre-allocate vectors for gas thermophysical properties - numGas = len(self.GasID) - numVec = 2*numGas -# ============================================================================= -# Diff = np.zeros(numGas) -# Perm_SI = np.zeros(numGas) -# H = np.zeros(numGas) -# Cin = np.zeros(numGas) -# MM = np.zeros(numGas) -# ============================================================================= - - inf, = self.ins - cmps = inf.components - # Extract Gas Properties - #for i in range(0,numGas): - - #Diff[i] = GasVec[i].Diff() - Diff = self.Diff - - #Perm_SI[i] = Mem.PermDict[GasVec[i].Name] - Perm_SI = self._gasp - - #H[i] = GasVec[i].HeL() - H = self.HeL - - #Cin[i] = GasVec[i].Cin() - Cins = self.ins[0].conc/cmps.MW*cmps.i_mass # inlet molar concentration [mol/m^3] - Cin = Cins[self.idx] # self.idx ensures that it is only for gases - MMs = cmps.MW - MM = MMs[self.idx] # self.idx ensures that it is only for gases - - #Diff = np.array([0.0265e-7, 0.0296e-7, 0.0253e-7, 0.3199e-7]) - # Calculate dx and u - dx = L/Segs # Length of Segments [m] - u = Q/((np.pi*D**2/4)*num_tubes*1000*60) # Linear Flow Velocity [m/s] - - # Calculate the Kinematic Viscosity of Water - Tb = T/300 # Reduced Temperature [] - mu = (1e-6)*(280.68*(Tb**(-1.9)) + 511.45*(Tb**(-7.7)) + 61.131*(Tb**(-19.6)) + 0.45903*(Tb**(-40))) # Absolute Viscosity of Water [Pa s] - rho = (-13.851 + 0.64038*T - 1.9124e-3*T**2 + 1.8211e-6*T**3)*18 # Liquid Density of Water [kg/m^3] from DIPPR - nu = mu/rho # Kinematic Viscosity of Water [m^2/s] - - # Calculate the dimensionless numbers - # Reynolds - Re = u*D/nu - # Schmidt - Sc = nu/Diff - # Sherwood - Sh = 1.615*(Re*Sc*D/L)**(1/3) - - - # Calculate Mass Transfer Coefficients - KMem = Perm_SI/l - KLiq = Sh*Diff/D - KTot = 1/(1/KLiq + 1/(KMem*H)) - for j in range(0, numGas): - #if GasVec[j].Name == 'H2O': - if j == self.h2o_j: - KTot[j] = KMem[j] - - #print(H*KMem) - #print(KLiq) - #print(KTot) - # Synthesize the output vector of concentration differences - - # Initialize - C = self._state - dC = self._dstate - #dC = np.zeros(Segs*numGas*2) - - sumCp_init = P/(R*T) - sumCp_fin = np.zeros(Segs) - - - # For the first segment: - for j in range(0, numGas): - #if GasVec[j].Name == 'H2O': - if j == self.h2o_j: - dC[j+numGas] = (KTot[j]/(D/4))*(PVapH2O- (C[j+numGas]/sumCp_init)*P)*vFrac - dC[j] = 0 - else: - dC[j] = (u/dx)*(C[Segs*numVec+j] - C[j]) - (KTot[j]/(D/4))*(C[j] - (C[j+numGas]/sumCp_init)*P/H[j]) - dC[j+numGas] = (KTot[j]/(D/4))*(C[j]-(C[j+numGas]/sumCp_init)*P/H[j])*vFrac - - # Calculate the total gas concentration in the shell after the change - sumCp_fin[0] += C[j+numGas] + dC[j+numGas] - - for i in range(1,Segs): - # For the remaining segments: - # Calculate the rate of change of the shell and lumen for all remaining segments. - for j in range(0, numGas): - - # Lumen - dC[numVec*(i)+j] = (u/dx)*(C[numVec*(i-1)+j] - C[numVec*(i)+j]) - (KTot[j]/(D/4))*(C[numVec*(i)+j] - (C[numVec*(i)+j+numGas]/sumCp_init)*(P/H[j])) - - # Shell - dC[numVec*(i)+j+numGas] = (KTot[j]/(D/4))*(C[numVec*(i)+j] - (C[numVec*(i)+j+numGas]/sumCp_init)*(P/H[j]))*vFrac - - # If the gas is H2O, then it follows a different formula: - #if GasVec[j].Name == 'H2O': - if j == self.h2o_j: - dC[numVec*i+j+numGas] = (KTot[j]/(D/4))*(PVapH2O-(C[numVec*i+j+numGas]/sumCp_init)*P)*vFrac - dC[numVec*i+j] = 0 - - # Calculate the total gas concentration in the shell after the change - sumCp_fin[i] += C[numVec*(i)+j+numGas] + dC[numVec*(i)+j+numGas] - - - # Calculate changes in tank concentration based the values of SS - for j in range(0, numGas): - if SS == 0: - dC[Segs*numVec+j] = ((Q/60)/V)*(C[numVec*(Segs-1)+j] - C[j]) - if SS == 1: - dC[Segs*numVec+j] = 0 - - # Re-scale the shell concentration so that vacuum pressure stays constant during the entire process. Given the change of concentration that we have calculated above for the shell, we can re-scale the shell concentrations with the sumCp at each segment. - - for i in range(0, Segs): - for j in range(0, numGas): - # Calculate the new concentration of gases in the shell - newCp = (C[numVec*(i)+j+numGas] + dC[numVec*(i)+j+numGas]) - - # Re-scale the concentration to vacuum pressure - newCp = (newCp/sumCp_fin[i])*P/(R*T) - - # Calculate the actual difference of concentration that the function will output - dC[numVec*(i)+j+numGas] = newCp- C[numVec*(i)+j+numGas] - - # Return the difference in concentration - return dC \ No newline at end of file diff --git a/qsdsan/sanunits/_membrane_gas_extraction.py b/qsdsan/sanunits/_membrane_gas_extraction.py index efbf6969..35c00772 100644 --- a/qsdsan/sanunits/_membrane_gas_extraction.py +++ b/qsdsan/sanunits/_membrane_gas_extraction.py @@ -64,7 +64,7 @@ class GasExtractionMembrane(SanUnit): } # Constructor: Initialize the instance variables - def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, + def __init__(self, ID='GEM', ins=None, outs=(), thermo=None, isdynamic=False, init_with='WasteStream', F_BM_default=None, FiberID=190e-6, FiberOD=300e-6, NumTubes=1512, ShellDia=1.89e-2, SurfArea=0.1199, GasID = ['H2', 'CO2', 'CH4', 'O2', 'N2', 'H2O'], PVac = 97.325, From 1034d8fbe48dd5fa1bf4b79728eeff96156581fc Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Fri, 10 Feb 2023 14:48:11 -0600 Subject: [PATCH 045/424] Adding new unit Adding new SanUnit 'Gas Extraction Membrane' --- qsdsan/sanunits/_membrane_gas_extraction.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qsdsan/sanunits/_membrane_gas_extraction.py b/qsdsan/sanunits/_membrane_gas_extraction.py index 35c00772..fcdc2401 100644 --- a/qsdsan/sanunits/_membrane_gas_extraction.py +++ b/qsdsan/sanunits/_membrane_gas_extraction.py @@ -14,6 +14,7 @@ Please refer to https://github.com/QSD-Group/QSDsan/blob/main/LICENSE.txt for license details. """ +__all__ = ('GasExtractionMembrane') from qsdsan import SanUnit import numpy as np From 60bad95ce03738ff93cf6097ead642e5a0bac6f0 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Fri, 10 Feb 2023 14:54:31 -0600 Subject: [PATCH 046/424] Adding new SanUnit to QSDsan Adding Gas Extraction Membrane as a new Sanitation Unit --- qsdsan/sanunits/__init__.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/qsdsan/sanunits/__init__.py b/qsdsan/sanunits/__init__.py index d8ef4dbd..2fb502df 100644 --- a/qsdsan/sanunits/__init__.py +++ b/qsdsan/sanunits/__init__.py @@ -40,6 +40,7 @@ from ._excretion import * from ._heat_exchanging import * from ._junction import * +from ._membrane_gas_extraction import * from ._non_reactive import * from ._pumping import * from ._screening import * @@ -50,6 +51,7 @@ from ._trucking import * from ._sludge_treatment import * + # Units that rely on other units from ._activated_sludge_process import * from ._anaerobic_reactors import * @@ -84,6 +86,7 @@ _junction, _lagoon, _membrane_bioreactors, + _membrane_gas_extraction, _non_reactive, _polishing_filter, _pumping, @@ -140,5 +143,6 @@ *_biogenic_refinery.__all__, *_reclaimer.__all__, *_eco_san.__all__, - *_sludge_treatment.__all__, + *_sludge_treatment.__all__, + *_membrane_gas_extraction.__all__, ) \ No newline at end of file From ad300e90b2f4aee8d2062ff8763fabda269c1082 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Fri, 10 Feb 2023 15:02:14 -0600 Subject: [PATCH 047/424] Deleting duplicate name Deleting duplicate name for SanUnit import --- qsdsan/sanunits/_membrane_gas_extraction.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/qsdsan/sanunits/_membrane_gas_extraction.py b/qsdsan/sanunits/_membrane_gas_extraction.py index fcdc2401..a77be3e4 100644 --- a/qsdsan/sanunits/_membrane_gas_extraction.py +++ b/qsdsan/sanunits/_membrane_gas_extraction.py @@ -19,8 +19,6 @@ from qsdsan import SanUnit import numpy as np -__all__ = ('Membrane') - class GasExtractionMembrane(SanUnit): _N_ins = 1 From 520bcf0661bcad1598dc0f3a205e11611fdcb1a2 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Fri, 10 Feb 2023 15:06:44 -0600 Subject: [PATCH 048/424] Updated GasExtractionMembrane Edited the position of '__all__' correct import --- qsdsan/sanunits/_membrane_gas_extraction.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/qsdsan/sanunits/_membrane_gas_extraction.py b/qsdsan/sanunits/_membrane_gas_extraction.py index a77be3e4..bacd5507 100644 --- a/qsdsan/sanunits/_membrane_gas_extraction.py +++ b/qsdsan/sanunits/_membrane_gas_extraction.py @@ -14,11 +14,13 @@ Please refer to https://github.com/QSD-Group/QSDsan/blob/main/LICENSE.txt for license details. """ -__all__ = ('GasExtractionMembrane') + from qsdsan import SanUnit import numpy as np +__all__ = ('GasExtractionMembrane') + class GasExtractionMembrane(SanUnit): _N_ins = 1 From f4441081372fa9251a8b8dde8a1865966f7527dd Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Fri, 10 Feb 2023 15:21:57 -0600 Subject: [PATCH 049/424] Updated Gas Extraction Membrane Added run function to Gas Extraction Membrane --- qsdsan/sanunits/_membrane_gas_extraction.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/qsdsan/sanunits/_membrane_gas_extraction.py b/qsdsan/sanunits/_membrane_gas_extraction.py index bacd5507..a16dd706 100644 --- a/qsdsan/sanunits/_membrane_gas_extraction.py +++ b/qsdsan/sanunits/_membrane_gas_extraction.py @@ -230,6 +230,9 @@ def Diff(self): D = 0.0001*(7.4*10**(-8))*np.sqrt(MWH2O*Phi)*self.ins[0].T/(mu*V1**(0.6)) return D + def _run(self): + pass + def _init_state(self): inf, = self.ins cmps = inf.components From 01d0b536adeee7983750129e7d169a2545c6ec90 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Mon, 13 Feb 2023 15:57:32 -0600 Subject: [PATCH 050/424] Added functions for Dynamic Simulations Added functions for dynamic simulation of Gas Membrane --- qsdsan/sanunits/_membrane_gas_extraction.py | 124 ++++++++++++-------- 1 file changed, 78 insertions(+), 46 deletions(-) diff --git a/qsdsan/sanunits/_membrane_gas_extraction.py b/qsdsan/sanunits/_membrane_gas_extraction.py index a16dd706..c5f8ea41 100644 --- a/qsdsan/sanunits/_membrane_gas_extraction.py +++ b/qsdsan/sanunits/_membrane_gas_extraction.py @@ -15,11 +15,10 @@ for license details. """ - from qsdsan import SanUnit import numpy as np -__all__ = ('GasExtractionMembrane') +__all__ = ('GasExtractionMembrane',) class GasExtractionMembrane(SanUnit): @@ -84,7 +83,7 @@ def __init__(self, ID='GEM', ins=None, outs=(), thermo=None, isdynamic=False, self.PVac = PVac # Operating Vacuum Pressure [-kPa] self.segs = segs # Number of segments ??Ask Ian?? #self.Volume = VolBatchTank # Volume of the bioreactor (Don't think this is needed) - + dct_gas_perm = GasPerm or self._GasPerm self.set_GasPerm(**dct_gas_perm) dct_gas_hpf = HenryPreFac or self._HenryPreFac @@ -105,6 +104,7 @@ def __init__(self, ID='GEM', ins=None, outs=(), thermo=None, isdynamic=False, if ID == 'H2O': self.h2o_j = i break + @property def FiberOD(self): return self._FiberOD @@ -159,25 +159,6 @@ def SurfArea(self, SurfArea): self._SurfArea = SurfArea else: raise ValueError('Surface Area of Membrane expected from user') - - def set_GasPerm(self, **kwargs): - self.set_prop('_gasp', **kwargs) - - def set_WilkeChang(self, **kwargs): - self.set_prop('_wc', **kwargs) - - def set_HenryPreFac(self, **kwargs): - self.set_prop('_hpf', **kwargs) - - def set_HenrySlope(self, **kwargs): - self.set_prop('_hs', **kwargs) - - def set_prop(self, attr_name, **kwargs): - idxr = self.indexer - try: attr = getattr(self, attr_name) - except: attr = self.__dict__[attr_name] = np.zeros(len(self.chemicals)) - for k, v in kwargs.items(): - attr[idxr(k)] = v # Calculate the volume fraction of the lumen to the shell. @property @@ -228,29 +209,64 @@ def Diff(self): V1 = 0.285*(self._Vc*1000000)**(1.048) # unit conversion from m^3/mol (QSDsan) to cm^3/mol (here) # Diffusion Coefficient [m^2/s] D = 0.0001*(7.4*10**(-8))*np.sqrt(MWH2O*Phi)*self.ins[0].T/(mu*V1**(0.6)) - return D - - def _run(self): - pass + return D - def _init_state(self): + def set_GasPerm(self, **kwargs): + self.set_prop('_gasp', **kwargs) + + def set_WilkeChang(self, **kwargs): + self.set_prop('_wc', **kwargs) + + def set_HenryPreFac(self, **kwargs): + self.set_prop('_hpf', **kwargs) + + def set_HenrySlope(self, **kwargs): + self.set_prop('_hs', **kwargs) + + def set_prop(self, attr_name, **kwargs): inf, = self.ins cmps = inf.components - # ASSUMPTION: Only 1 influent - C = self._ins_QC[:-1]/cmps.chem_MW*cmps.i_mass # conc. in mol/m^3 as defined by Ian - Cs = C[self.idx] #self.idx ensures its only for gases - #Q = self._ins_QC[-1] + self.indexer = cmps.index + idxr = self.indexer + try: attr = getattr(self, attr_name) + except: attr = self.__dict__[attr_name] = np.zeros(len(self.chemicals)) + for k, v in kwargs.items(): + attr[idxr(k)] = v + + def _init_state(self): + inf = self.ins + cmps = inf.components + C = self._ins_QC[:-1]/cmps.chem_MW*cmps.i_mass + Cs = C[self.idx] #idx selects only gases Seg = self.segments numGas = len(self.GasID) - self._state = np.zeros(2*Seg*numGas) for i in range(0, 2*Seg*numGas, 2*numGas): for j in range(numGas): - self._state[j+i] = Cs[j] - + self._state[j+i] = Cs[j] self._dstate = self._state*0 - - + self._cached_state = self._state.copy() + self._cached_t = 0 + + def _update_state(self): + eff, = self.outs # assuming this SanUnit has one outlet only + numGas = len(self.GasID) + eff.state[:] = self._state[-numGas:] + + def _update_dstate(self): + eff, = self.outs + numGas = len(self.GasID) + eff.dstate[:] = self._dstate[-numGas:] + + def _run(self): + pass + + @property + def ODE(self): + if self._ODE is None: + self._compile_ODE() + return self._ODE + #def transientGasExtraction(t, C, ExpCond, GasVec, Mem, Segs, SS): def _compile_ODE(self): # Synthesizes the ODEs to simulate a batch reactor with side-flow gas extraction. The code takes in an object of class Membrane (Mem) and an array of objects of class Gas (GasVec). It also takes in an array of experimental conditions ExpCond. @@ -279,13 +295,6 @@ def _compile_ODE(self): # Pre-allocate vectors for gas thermophysical properties numGas = len(self.GasID) numVec = 2*numGas -# ============================================================================= -# Diff = np.zeros(numGas) -# Perm_SI = np.zeros(numGas) -# H = np.zeros(numGas) -# Cin = np.zeros(numGas) -# MM = np.zeros(numGas) -# ============================================================================= inf, = self.ins cmps = inf.components @@ -320,7 +329,6 @@ def _compile_ODE(self): # Sherwood Sh = 1.615*(Re*Sc*D/L)**(1/3) - # Calculate Mass Transfer Coefficients KMem = Perm_SI/l KLiq = Sh*Diff/D @@ -385,6 +393,30 @@ def _compile_ODE(self): # Calculate the actual difference of concentration that the function will output dC[numVec*(i)+j+numGas] = newCp- C[numVec*(i)+j+numGas] + # Return the difference in concentration + return dC - # Return the difference in concentration - return dC \ No newline at end of file + def dy_dt(t, QC_ins, dQC_ins): + inf = self.ins + cmps = inf.components + + _update_state = self._update_state + _update_dstate = self._update_dstate + _cached_state = self._cached_state + + C = self._ins_QC[:-1]/cmps.chem_MW*cmps.i_mass + Cs = C[self.idx] #idx selects only gases + Seg = self.segments + numGas = len(self.GasID) + _state = np.zeros(2*Seg*numGas) + for i in range(0, 2*Seg*numGas, 2*numGas): + for j in range(numGas): + _state[j+i] = Cs[j] + + if t > self._cached_t: + _dstate[:] = (_state - _cached_state)/(t - self._cached_t) + _cached_state[:] = _state + self._cached_t = t + _update_state() + _update_dstate() + self._ODE = dy_dt \ No newline at end of file From 070d39b2abb016494ef17b482def7fc5ac71ed9f Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Tue, 14 Feb 2023 15:10:24 -0600 Subject: [PATCH 051/424] Comment on ODE units Points to remember while writing ODE units --- qsdsan/sanunits/_membrane_gas_extraction.py | 37 ++++++++------------- 1 file changed, 13 insertions(+), 24 deletions(-) diff --git a/qsdsan/sanunits/_membrane_gas_extraction.py b/qsdsan/sanunits/_membrane_gas_extraction.py index c5f8ea41..2680e532 100644 --- a/qsdsan/sanunits/_membrane_gas_extraction.py +++ b/qsdsan/sanunits/_membrane_gas_extraction.py @@ -241,16 +241,20 @@ def _init_state(self): Seg = self.segments numGas = len(self.GasID) self._state = np.zeros(2*Seg*numGas) + for i in range(0, 2*Seg*numGas, 2*numGas): for j in range(numGas): self._state[j+i] = Cs[j] self._dstate = self._state*0 - self._cached_state = self._state.copy() - self._cached_t = 0 +# ============================================================================= +# self._cached_state = self._state.copy() +# self._cached_t = 0 +# ============================================================================= def _update_state(self): eff, = self.outs # assuming this SanUnit has one outlet only numGas = len(self.GasID) + # Need to add effluent streams for liquid (this includes all cmps of influent ws) and gas (the multiplication should give out kg/hr values) eff.state[:] = self._state[-numGas:] def _update_dstate(self): @@ -395,28 +399,13 @@ def _compile_ODE(self): dC[numVec*(i)+j+numGas] = newCp- C[numVec*(i)+j+numGas] # Return the difference in concentration return dC - - def dy_dt(t, QC_ins, dQC_ins): - inf = self.ins - cmps = inf.components - - _update_state = self._update_state - _update_dstate = self._update_dstate - _cached_state = self._cached_state + + _dstate = self._dstate + _update_dstate = self._update_dstate - C = self._ins_QC[:-1]/cmps.chem_MW*cmps.i_mass - Cs = C[self.idx] #idx selects only gases - Seg = self.segments - numGas = len(self.GasID) - _state = np.zeros(2*Seg*numGas) - for i in range(0, 2*Seg*numGas, 2*numGas): - for j in range(numGas): - _state[j+i] = Cs[j] + def dy_dt(t, QC_ins, QC, dQC_ins): + # QC is exactly the state as we define in _init_ + C = QC - if t > self._cached_t: - _dstate[:] = (_state - _cached_state)/(t - self._cached_t) - _cached_state[:] = _state - self._cached_t = t - _update_state() _update_dstate() - self._ODE = dy_dt \ No newline at end of file + self._ODE = dy_dt \ No newline at end of file From 043c49a039f79797af19477a7ec291c89f1ea1c9 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Tue, 14 Feb 2023 15:13:28 -0600 Subject: [PATCH 052/424] Thickener now compatible with junction Added thermo object to mixed waste stream for compatibility with junction --- qsdsan/sanunits/_sludge_treatment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py index b5832298..fffc2b7a 100644 --- a/qsdsan/sanunits/_sludge_treatment.py +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -128,7 +128,7 @@ def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, self.TSS_removal_perc = TSS_removal_perc self.solids_loading_rate = solids_loading_rate self.h_cylinderical = h_cylinderical - self.mixed = WasteStream() + self.mixed = WasteStream(thermo=thermo) @property def thickener_perc(self): From fa4c7785433c66f35f35faf81b4bd6ed45623366 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Tue, 14 Feb 2023 15:13:51 -0600 Subject: [PATCH 053/424] No change No change --- qsdsan/_sanunit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qsdsan/_sanunit.py b/qsdsan/_sanunit.py index 251ca0bf..d77e8b4a 100644 --- a/qsdsan/_sanunit.py +++ b/qsdsan/_sanunit.py @@ -202,7 +202,7 @@ def __init__(self, ID='', ins=None, outs=(), thermo=None, init_with='WasteStream if not kwargs.get('skip_property_package_check'): self._assert_compatible_property_package() - + self._utility_cost = None ##### qsdsan-specific ##### From 62f8bd6f280939fe3e370cc981cc15d6c51a9a53 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Tue, 14 Feb 2023 15:28:40 -0600 Subject: [PATCH 054/424] Notes on ODE units Shifted update_dstate( ) to the dy/dt function. To remember: 1) the 'dy/dt' function within 'compile_ODE' only updates the _dstate, while the 'dy' function within 'compile_AE' function updates both 'state' and 'dstate' --- qsdsan/sanunits/_membrane_gas_extraction.py | 96 ++++++++++----------- 1 file changed, 48 insertions(+), 48 deletions(-) diff --git a/qsdsan/sanunits/_membrane_gas_extraction.py b/qsdsan/sanunits/_membrane_gas_extraction.py index 2680e532..94ca1e88 100644 --- a/qsdsan/sanunits/_membrane_gas_extraction.py +++ b/qsdsan/sanunits/_membrane_gas_extraction.py @@ -352,54 +352,6 @@ def _compile_ODE(self): C = self._ins_QC[:-1]/cmps.chem_MW*cmps.i_mass # conc. in mol/m^3 as defined by Ian Cs = C[self.idx] #self.idx ensures its only for gases - # For the first segment: - for j in range(0, numGas): - #if GasVec[j].Name == 'H2O': - if j == self.h2o_j: - dC[j+numGas] = (KTot[j]/(D/4))*(PVapH2O- (C[j+numGas]/sumCp_init)*P)*vFrac - dC[j] = 0 - else: - dC[j] = (u/dx)*(Cs[j] - C[j]) - (KTot[j]/(D/4))*(C[j] - (C[j+numGas]/sumCp_init)*P/H[j]) - dC[j+numGas] = (KTot[j]/(D/4))*(C[j]-(C[j+numGas]/sumCp_init)*P/H[j])*vFrac - - # Calculate the total gas concentration in the shell after the change - sumCp_fin[0] += C[j+numGas] + dC[j+numGas] - - for i in range(1,Segs): - # For the remaining segments: - # Calculate the rate of change of the shell and lumen for all remaining segments. - for j in range(0, numGas): - - # Lumen - dC[numVec*(i)+j] = (u/dx)*(C[numVec*(i-1)+j] - C[numVec*(i)+j]) - (KTot[j]/(D/4))*(C[numVec*(i)+j] - (C[numVec*(i)+j+numGas]/sumCp_init)*(P/H[j])) - - # Shell - dC[numVec*(i)+j+numGas] = (KTot[j]/(D/4))*(C[numVec*(i)+j] - (C[numVec*(i)+j+numGas]/sumCp_init)*(P/H[j]))*vFrac - - # If the gas is H2O, then it follows a different formula: - #if GasVec[j].Name == 'H2O': - if j == self.h2o_j: - dC[numVec*i+j+numGas] = (KTot[j]/(D/4))*(PVapH2O-(C[numVec*i+j+numGas]/sumCp_init)*P)*vFrac - dC[numVec*i+j] = 0 - - # Calculate the total gas concentration in the shell after the change - sumCp_fin[i] += C[numVec*(i)+j+numGas] + dC[numVec*(i)+j+numGas] - - # Re-scale the shell concentration so that vacuum pressure stays constant during the entire process. Given the change of concentration that we have calculated above for the shell, we can re-scale the shell concentrations with the sumCp at each segment. - - for i in range(0, Segs): - for j in range(0, numGas): - # Calculate the new concentration of gases in the shell - newCp = (C[numVec*(i)+j+numGas] + dC[numVec*(i)+j+numGas]) - - # Re-scale the concentration to vacuum pressure - newCp = (newCp/sumCp_fin[i])*P/(R*T) - - # Calculate the actual difference of concentration that the function will output - dC[numVec*(i)+j+numGas] = newCp- C[numVec*(i)+j+numGas] - # Return the difference in concentration - return dC - _dstate = self._dstate _update_dstate = self._update_dstate @@ -407,5 +359,53 @@ def dy_dt(t, QC_ins, QC, dQC_ins): # QC is exactly the state as we define in _init_ C = QC + # For the first segment: + for j in range(0, numGas): + #if GasVec[j].Name == 'H2O': + if j == self.h2o_j: + dC[j+numGas] = (KTot[j]/(D/4))*(PVapH2O- (C[j+numGas]/sumCp_init)*P)*vFrac + dC[j] = 0 + else: + dC[j] = (u/dx)*(Cs[j] - C[j]) - (KTot[j]/(D/4))*(C[j] - (C[j+numGas]/sumCp_init)*P/H[j]) + dC[j+numGas] = (KTot[j]/(D/4))*(C[j]-(C[j+numGas]/sumCp_init)*P/H[j])*vFrac + + # Calculate the total gas concentration in the shell after the change + sumCp_fin[0] += C[j+numGas] + dC[j+numGas] + + for i in range(1,Segs): + # For the remaining segments: + # Calculate the rate of change of the shell and lumen for all remaining segments. + for j in range(0, numGas): + + # Lumen + dC[numVec*(i)+j] = (u/dx)*(C[numVec*(i-1)+j] - C[numVec*(i)+j]) - (KTot[j]/(D/4))*(C[numVec*(i)+j] - (C[numVec*(i)+j+numGas]/sumCp_init)*(P/H[j])) + + # Shell + dC[numVec*(i)+j+numGas] = (KTot[j]/(D/4))*(C[numVec*(i)+j] - (C[numVec*(i)+j+numGas]/sumCp_init)*(P/H[j]))*vFrac + + # If the gas is H2O, then it follows a different formula: + #if GasVec[j].Name == 'H2O': + if j == self.h2o_j: + dC[numVec*i+j+numGas] = (KTot[j]/(D/4))*(PVapH2O-(C[numVec*i+j+numGas]/sumCp_init)*P)*vFrac + dC[numVec*i+j] = 0 + + # Calculate the total gas concentration in the shell after the change + sumCp_fin[i] += C[numVec*(i)+j+numGas] + dC[numVec*(i)+j+numGas] + + # Re-scale the shell concentration so that vacuum pressure stays constant during the entire process. Given the change of concentration that we have calculated above for the shell, we can re-scale the shell concentrations with the sumCp at each segment. + + for i in range(0, Segs): + for j in range(0, numGas): + # Calculate the new concentration of gases in the shell + newCp = (C[numVec*(i)+j+numGas] + dC[numVec*(i)+j+numGas]) + + # Re-scale the concentration to vacuum pressure + newCp = (newCp/sumCp_fin[i])*P/(R*T) + + # Calculate the actual difference of concentration that the function will output + dC[numVec*(i)+j+numGas] = newCp- C[numVec*(i)+j+numGas] + # Return the difference in concentration + + #return dC _update_dstate() self._ODE = dy_dt \ No newline at end of file From ec64b363588df1d585a8a8a4a03947bca156152c Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Thu, 16 Feb 2023 10:52:21 -0600 Subject: [PATCH 055/424] Added _update_state( ) to Gas Extraction Membrane Defined the state of gas and liquid effluent streams (as defined by Ian) based on the state of the unit --- qsdsan/sanunits/_membrane_gas_extraction.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/qsdsan/sanunits/_membrane_gas_extraction.py b/qsdsan/sanunits/_membrane_gas_extraction.py index 94ca1e88..e908ac64 100644 --- a/qsdsan/sanunits/_membrane_gas_extraction.py +++ b/qsdsan/sanunits/_membrane_gas_extraction.py @@ -254,9 +254,16 @@ def _init_state(self): def _update_state(self): eff, = self.outs # assuming this SanUnit has one outlet only numGas = len(self.GasID) - # Need to add effluent streams for liquid (this includes all cmps of influent ws) and gas (the multiplication should give out kg/hr values) - eff.state[:] = self._state[-numGas:] - + # Need to add effluent streams for liquid (this includes all cmps of influent ws) + # and gas (the multiplication should give out kg/hr values) + + # The state of effluent Gas in the extraction membrane is the difference + # between lumen concentration of the last and first segment + self._outs[0].state = self._state[ -2*numGas: -numGas] - self._state[ :numGas] + # The state of effluent Liquid stream is simply the concentration of + # the last lumen segment in the extraction membrane + self._outs[1].state = self._state[-2*numGas: -numGas] + def _update_dstate(self): eff, = self.outs numGas = len(self.GasID) @@ -356,7 +363,7 @@ def _compile_ODE(self): _update_dstate = self._update_dstate def dy_dt(t, QC_ins, QC, dQC_ins): - # QC is exactly the state as we define in _init_ + # QC is exactly 'the state' as we define in _init_ C = QC # For the first segment: From 493b66dad446c687a4b76f090b47b5eca966c7d9 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Thu, 16 Feb 2023 10:52:55 -0600 Subject: [PATCH 056/424] Typo correction Corrected a typo in Primary Clarifier description --- qsdsan/sanunits/_clarifier.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index 5c9ff6bf..2cfbd4d9 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -554,7 +554,7 @@ class PrimaryClarifier(SanUnit): Hydraulic Retention time : float Hydraulic Retention Time in days. The default is 0.04268 days, based on IWA report.[1] ratio_uf : float - The ratio of sludge to primary influent. The default is 0.65, based on IWA report.[1] + The ratio of sludge to primary influent. The default is 0.007, based on IWA report.[1] f_corr : float Dimensionless correction factor for removal efficiency in the primary clarifier.[1] oveflow_rate : float From 627eb6764c500e9715fa68a060eddfcaba855d77 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Wed, 1 Mar 2023 12:48:53 -0600 Subject: [PATCH 057/424] Allowed multiple influents during Dynamic Simulation in Thickeners Changed the dynamic simulation function to allow multiple influents --- qsdsan/sanunits/_sludge_treatment.py | 50 ++++++++++++++++++++++------ 1 file changed, 39 insertions(+), 11 deletions(-) diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py index fffc2b7a..389f7526 100644 --- a/qsdsan/sanunits/_sludge_treatment.py +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -179,7 +179,8 @@ def thickener_factor(self): else: TSS_in = inf.get_TSS() if TSS_in > 0: - thickener_factor = self._tp*10000/self.ins[0].get_TSS() + #thickener_factor = self._tp*10000/self.ins[0].get_TSS() + thickener_factor = self._tp*10000/inf.get_TSS() if thickener_factor<1: thickener_factor=1 return thickener_factor @@ -206,8 +207,11 @@ def _run(self): thinning_factor = self.thinning_factor thickener_factor = self.thickener_factor - Ze = (1 - thinning_factor)/(thickener_factor - thinning_factor)*inf.mass*cmps.s - Zs = (thickener_factor - 1)/(thickener_factor - thinning_factor)*inf.mass*cmps.s + # Ze = (1 - thinning_factor)/(thickener_factor - thinning_factor)*inf.mass*cmps.s + # Zs = (thickener_factor - 1)/(thickener_factor - thinning_factor)*inf.mass*cmps.s + + Zs = (1 - thinning_factor)/(thickener_factor - thinning_factor)*inf.mass*cmps.s + Ze = (thickener_factor - 1)/(thickener_factor - thinning_factor)*inf.mass*cmps.s Xe = (1 - TSS_rmv/100)*inf.mass*cmps.x Xs = (TSS_rmv/100)*inf.mass*cmps.x @@ -230,8 +234,13 @@ def _design(self): design['Curved Surface Area'] = np.pi*design['Diameter']*self.h_cylinderical #in m2 def _init_state(self): - # if only 1 inlet then simply copy the state of the influent wastestream - self._state = self._ins_QC[0] + # # if only 1 inlet then simply copy the state of the influent wastestream + # self._state = self._ins_QC[0] + # self._dstate = self._state * 0. + + Qs = self._ins_QC[:,-1] + Cs = self._ins_QC[:,:-1] + self._state = np.append(Qs @ Cs / Qs.sum(), Qs.sum()) self._dstate = self._state * 0. uf, of = self.outs @@ -244,13 +253,18 @@ def _init_state(self): def _update_state(self): '''updates conditions of output stream based on conditions of the Thickener''' - self._outs[0].state = self._sludge * self._state - self._outs[1].state = self._effluent * self._state + # self._state is mixed influent stream's state + # multiply the particulates by thickener factor, the solubles by 1, and + # flowrate by Qu factor + + # self._outs[0].state = self._sludge * self._state + # self._outs[1].state = self._effluent * self._state def _update_dstate(self): '''updates rates of change of output stream from rates of change of the Thickener''' - self._outs[0].dstate = self._sludge * self._dstate - self._outs[1].dstate = self._effluent * self._dstate + + # self._outs[0].dstate = self._sludge * self._dstate + # self._outs[1].dstate = self._effluent * self._dstate @property def AE(self): @@ -264,8 +278,22 @@ def _compile_AE(self): _update_state = self._update_state _update_dstate = self._update_dstate def yt(t, QC_ins, dQC_ins): - _state[:] = QC_ins[0] - _dstate[:] = dQC_ins[0] + # _state[:] = QC_ins[0] + # _dstate[:] = dQC_ins[0] + + Q_ins = QC_ins[:, -1] + C_ins = QC_ins[:, :-1] + dQ_ins = dQC_ins[:, -1] + dC_ins = dQC_ins[:, :-1] + Q = Q_ins.sum() + C = Q_ins @ C_ins / Q + _state[-1] = Q + _state[:-1] = C + Q_dot = dQ_ins.sum() + C_dot = (dQ_ins @ C_ins + Q_ins @ dC_ins - Q_dot * C)/Q + _dstate[-1] = Q_dot + _dstate[:-1] = C_dot + _update_state() _update_dstate() self._AE = yt From fff352b36a37d02579fffd699224b75bb7738fdd Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Wed, 1 Mar 2023 12:49:59 -0600 Subject: [PATCH 058/424] Updated wwt design To facilitate SRT calculation --- qsdsan/utils/wwt_design.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qsdsan/utils/wwt_design.py b/qsdsan/utils/wwt_design.py index 7eae2b84..2ce467a1 100644 --- a/qsdsan/utils/wwt_design.py +++ b/qsdsan/utils/wwt_design.py @@ -44,7 +44,7 @@ def get_SRT(system, biomass_IDs, active_unit_IDs=None): `bsm1 system `_ """ waste = sum([ws.composite('solids', subgroup=biomass_IDs)*ws.F_vol*24 \ - for ws in system.products if ws.phase in ('l','s')]) + for ws in system.products if ws.phase=='l']) units = system.units if active_unit_IDs is None \ else [u for u in system.units if u.ID in active_unit_IDs] retain = sum([u.get_retained_mass(biomass_IDs) for u in units if u.isdynamic]) From a40f2da4d308d764f690ff258dce319ea068066c Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Wed, 1 Mar 2023 16:56:27 -0600 Subject: [PATCH 059/424] Facilitated dynamic simulations Made multiple changes to ensure that Thickeners and Dewatering Units can run smoothly with inputs from ODE units. --- qsdsan/sanunits/_sludge_treatment.py | 134 ++++++++++++++++++++------- 1 file changed, 101 insertions(+), 33 deletions(-) diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py index 389f7526..0585e58e 100644 --- a/qsdsan/sanunits/_sludge_treatment.py +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -174,28 +174,57 @@ def TSS_removal_perc(self, TSS_rmv): def thickener_factor(self): self.mixed.mix_from(self.ins) inf = self.mixed + _cal_thickener_factor = self._cal_thickener_factor if not self.ins: return elif inf.isempty(): return else: TSS_in = inf.get_TSS() - if TSS_in > 0: - #thickener_factor = self._tp*10000/self.ins[0].get_TSS() - thickener_factor = self._tp*10000/inf.get_TSS() - if thickener_factor<1: - thickener_factor=1 - return thickener_factor - else: return None + thickener_factor = _cal_thickener_factor(TSS_in) + return thickener_factor @property def thinning_factor(self): - thickener_factor = self.thickener_factor + self.mixed.mix_from(self.ins) + inf = self.mixed + TSS_in = inf.get_TSS() + _cal_thickener_factor = self._cal_thickener_factor + thickener_factor = _cal_thickener_factor(TSS_in) + _cal_parameters = self._cal_parameters + Qu_factor, thinning_factor = _cal_parameters(thickener_factor) + return thinning_factor + + def _cal_thickener_factor(self, TSS_in): + if TSS_in > 0: + thickener_factor = self._tp*10000/TSS_in + if thickener_factor<1: + thickener_factor=1 + return thickener_factor + else: return None + + def _cal_parameters(self, thickener_factor): if thickener_factor<1: + Qu_factor = 1 thinning_factor=0 else: Qu_factor = self._TSS_rmv/(100*thickener_factor) thinning_factor = (1 - (self._TSS_rmv/100))/(1 - Qu_factor) - return thinning_factor + return Qu_factor, thinning_factor + + def _update_parameters(self): + + # Thickener_factor, Thinning_factor, and Qu_factor need to be + # updated again and again. while dynamic simulations + + cmps = self.components + TSS_in = np.sum(self._state[:-1]*cmps.i_mass*cmps.x) + _cal_thickener_factor = self._cal_thickener_factor + self.updated_thickener_factor = _cal_thickener_factor(TSS_in) + _cal_parameters = self._cal_parameters + + updated_thickener_factor = self.updated_thickener_factor + self.updated_Qu_factor, self.updated_thinning_factor = _cal_parameters(updated_thickener_factor) + def _run(self): self.mixed.mix_from(self.ins) @@ -207,15 +236,16 @@ def _run(self): thinning_factor = self.thinning_factor thickener_factor = self.thickener_factor - # Ze = (1 - thinning_factor)/(thickener_factor - thinning_factor)*inf.mass*cmps.s - # Zs = (thickener_factor - 1)/(thickener_factor - thinning_factor)*inf.mass*cmps.s + # The following are splits by mass of particulates and solubles + # Note: (1 - thinning_factor)/(thickener_factor - thinning_factor) = Qu_factor Zs = (1 - thinning_factor)/(thickener_factor - thinning_factor)*inf.mass*cmps.s Ze = (thickener_factor - 1)/(thickener_factor - thinning_factor)*inf.mass*cmps.s Xe = (1 - TSS_rmv/100)*inf.mass*cmps.x Xs = (TSS_rmv/100)*inf.mass*cmps.x + # e stands for effluent, s stands for sludge Ce = Ze + Xe Cs = Zs + Xs @@ -234,37 +264,73 @@ def _design(self): design['Curved Surface Area'] = np.pi*design['Diameter']*self.h_cylinderical #in m2 def _init_state(self): - # # if only 1 inlet then simply copy the state of the influent wastestream - # self._state = self._ins_QC[0] - # self._dstate = self._state * 0. - + + # This function is run only once during dynamic simulations + + # Since there could be multiple influents, the state of the unit is + # obtained assuming perfect mixing Qs = self._ins_QC[:,-1] Cs = self._ins_QC[:,:-1] self._state = np.append(Qs @ Cs / Qs.sum(), Qs.sum()) self._dstate = self._state * 0. - uf, of = self.outs - s_flow = uf.F_vol/(uf.F_vol+of.F_vol) - denominator = uf.mass + of.mass - denominator += (denominator == 0) - s = uf.mass/denominator - self._sludge = np.append(s/s_flow, s_flow) - self._effluent = np.append((1-s)/(1-s_flow), 1-s_flow) + # To initialize the updated_thickener_factor, updated_thinning_factor + # and updated_Qu_factor for dynamic simulation + self._update_parameters() def _update_state(self): '''updates conditions of output stream based on conditions of the Thickener''' - # self._state is mixed influent stream's state - # multiply the particulates by thickener factor, the solubles by 1, and - # flowrate by Qu factor - # self._outs[0].state = self._sludge * self._state - # self._outs[1].state = self._effluent * self._state + # This function is run multiple times during dynamic simulation + + # Remember that here we are updating the state array of size n, which is made up + # of component concentrations in the first (n-1) cells and the last cell is flowrate. + + # So, while in the run function the effluent and sludge are split by mass, + # here they are split by concentration. Therefore, the split factors are different. + + # Updated intrinsic modelling parameters are used for dynamic simulation + thickener_factor = self.updated_thickener_factor + thinning_factor = self.updated_thinning_factor + Qu_factor = self.updated_Qu_factor + cmps = self.components + + # For sludge, the particulate concentrations are multipled by thickener factor, and + # flowrate is multiplied by Qu_factor. The soluble concentrations remains same. + self._outs[0].state[:-1] = self._state[:-1]*cmps.s*1 + self._state[:-1]*cmps.x*thickener_factor + self._outs[0].state[-1] = self._state[-1]*Qu_factor + + # For effluent, the particulate concentrations are multipled by thinning factor, and + # flowrate is multiplied by Qu_factor. The soluble concentrations remains same. + self._outs[1].state[:-1] = self._state[:-1]*cmps.s*1 + self._state[:-1]*cmps.x*thinning_factor + self._outs[1].state[-1] = self._state[-1]*(1 - Qu_factor) def _update_dstate(self): '''updates rates of change of output stream from rates of change of the Thickener''' - # self._outs[0].dstate = self._sludge * self._dstate - # self._outs[1].dstate = self._effluent * self._dstate + # This function is run multiple times during dynamic simulation + + # Remember that here we are updating the state array of size n, which is made up + # of component concentrations in the first (n-1) cells and the last cell is flowrate. + + # So, while in the run function the effluent and sludge are split by mass, + # here they are split by concentration. Therefore, the split factors are different. + + # Updated intrinsic modelling parameters are used for dynamic simulation + thickener_factor = self.updated_thickener_factor + thinning_factor = self.updated_thinning_factor + Qu_factor = self.updated_Qu_factor + cmps = self.components + + # For sludge, the particulate concentrations are multipled by thickener factor, and + # flowrate is multiplied by Qu_factor. The soluble concentrations remains same. + self._outs[0].dstate[:-1] = self._dstate[:-1]*cmps.s*1 + self._dstate[:-1]*cmps.x*thickener_factor + self._outs[0].dstate[-1] = self._dstate[-1]*Qu_factor + + # For effluent, the particulate concentrations are multipled by thinning factor, and + # flowrate is multiplied by Qu_factor. The soluble concentrations remains same. + self._outs[1].dstate[:-1] = self._dstate[:-1]*cmps.s*1 + self._dstate[:-1]*cmps.x*thinning_factor + self._outs[1].dstate[-1] = self._dstate[-1]*(1 - Qu_factor) @property def AE(self): @@ -273,14 +339,15 @@ def AE(self): return self._AE def _compile_AE(self): + + # This function is run multiple times during dynamic simulation + _state = self._state _dstate = self._dstate _update_state = self._update_state _update_dstate = self._update_dstate + _update_parameters = self._update_parameters def yt(t, QC_ins, dQC_ins): - # _state[:] = QC_ins[0] - # _dstate[:] = dQC_ins[0] - Q_ins = QC_ins[:, -1] C_ins = QC_ins[:, :-1] dQ_ins = dQC_ins[:, -1] @@ -293,7 +360,8 @@ def yt(t, QC_ins, dQC_ins): C_dot = (dQ_ins @ C_ins + Q_ins @ dC_ins - Q_dot * C)/Q _dstate[-1] = Q_dot _dstate[:-1] = C_dot - + + _update_parameters() _update_state() _update_dstate() self._AE = yt From 526d08dd4fa293286a9b949fb77d16921213dd15 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Tue, 14 Mar 2023 16:30:29 -0500 Subject: [PATCH 060/424] Updated get_SRT (Joy) Change made by Joy to enable use of user-defined waste-streams for SRT calculation. Co-Authored-By: Joy Zhang <36645879+joyxyz1994@users.noreply.github.com> --- qsdsan/utils/wwt_design.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/qsdsan/utils/wwt_design.py b/qsdsan/utils/wwt_design.py index 2ce467a1..5bc2a99b 100644 --- a/qsdsan/utils/wwt_design.py +++ b/qsdsan/utils/wwt_design.py @@ -13,7 +13,7 @@ __all__ = ('get_SRT',) -def get_SRT(system, biomass_IDs, active_unit_IDs=None): +def get_SRT(system, biomass_IDs, wastage=None, active_unit_IDs=None): """ Estimate sludge residence time (SRT) of an activated sludge system. @@ -23,6 +23,8 @@ def get_SRT(system, biomass_IDs, active_unit_IDs=None): The system whose SRT will be calculated for. biomass_IDs : tuple[str] Component IDs of active biomass + wastage : iterable[:class:`WasteStream`] + Streams with wasted biomass. active_unit_IDs : tuple[str], optional IDs of activated sludge units. The default is None, meaning to include all units in the system. @@ -43,8 +45,9 @@ def get_SRT(system, biomass_IDs, active_unit_IDs=None): -------- `bsm1 system `_ """ + if wastage is None: wastage = [ws for ws in system.products if ws.phase in ('l','s')] waste = sum([ws.composite('solids', subgroup=biomass_IDs)*ws.F_vol*24 \ - for ws in system.products if ws.phase=='l']) + for ws in wastage]) units = system.units if active_unit_IDs is None \ else [u for u in system.units if u.ID in active_unit_IDs] retain = sum([u.get_retained_mass(biomass_IDs) for u in units if u.isdynamic]) From 9b49862b5eb43c095c37503905e61c4496d4b7aa Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Thu, 23 Mar 2023 16:10:04 -0500 Subject: [PATCH 061/424] Create _adm1_modified Adding a new modified ADM1 (based on http://dx.doi.org/10.1016/j.watres.2016.03.012) --- qsdsan/processes/_adm1_modified.py | 669 +++++++++++++++++++++++++++++ 1 file changed, 669 insertions(+) create mode 100644 qsdsan/processes/_adm1_modified.py diff --git a/qsdsan/processes/_adm1_modified.py b/qsdsan/processes/_adm1_modified.py new file mode 100644 index 00000000..6e99ff63 --- /dev/null +++ b/qsdsan/processes/_adm1_modified.py @@ -0,0 +1,669 @@ +# -*- coding: utf-8 -*- +''' +QSDsan: Quantitative Sustainable Design for sanitation and resource recovery systems + +This module is developed by: + Joy Zhang + +Part of this module is based on the Thermosteam package: +https://github.com/BioSTEAMDevelopmentGroup/thermosteam + +This module is under the University of Illinois/NCSA Open Source License. +Please refer to https://github.com/QSD-Group/QSDsan/blob/main/LICENSE.txt +for license details. +''' + +from thermosteam.utils import chemicals_user +from thermosteam import settings +from chemicals.elements import molecular_weight as get_mw +from qsdsan import Component, Components, Process, Processes, CompiledProcesses +import numpy as np +from qsdsan.utils import ospath, data_path +from scipy.optimize import brenth +from warnings import warn + +__all__ = ('create_adm1_cmps', 'ADM1', + 'non_compet_inhibit', 'substr_inhibit', + 'T_correction_factor', + 'pH_inhibit', 'Hill_inhibit', + 'rhos_adm1') + +_path = ospath.join(data_path, 'process_data/_adm1.tsv') +_load_components = settings.get_default_chemicals + +#%% +# ============================================================================= +# ADM1-specific components +# ============================================================================= + +C_mw = get_mw({'C':1}) +N_mw = get_mw({'N':1}) + +def create_adm1_cmps(set_thermo=True): + cmps_all = Components.load_default() + + # varies + X_c = cmps_all.X_OHO.copy('X_c') + X_c.description = 'Composite' + X_c.i_C = 0.02786 * C_mw + X_c.i_N = 0.0376 + X_ch = Component.from_chemical('X_ch', chemical='glycogen', Tc=1011.4, # glucan + description='Carbohydrates', + measured_as='COD', + particle_size='Particulate', + degradability='Slowly', + organic=True) + # X_ch = cmps_all.X_B_Subst.copy('X_ch') + # X_ch.i_N = 0 + # X_ch.i_C = 0.0313 * C_mw + + # varies + X_pr = cmps_all.X_B_Subst.copy('X_pr') + X_pr.i_N = 0.007 * N_mw + X_pr.i_C = 0.03 * C_mw + + X_li = Component.from_chemical('X_li', chemical='tripalmitin', + description='Lipids', + measured_as='COD', + particle_size='Particulate', + degradability='Slowly', + organic=True) + + # both varies + X_I = cmps_all.X_U_Inf.copy('X_I') + S_I = cmps_all.S_U_Inf.copy('S_I') + X_I.i_C = S_I.i_C = 0.03 * C_mw + X_I.i_N = S_I.i_N = 0.06 + + S_su = Component.from_chemical('S_su', chemical='glucose', + description='Monosaccharides', + measured_as='COD', + particle_size='Soluble', + degradability='Readily', + organic=True) + # S_su = cmps_all.S_F.copy('S_su') + # S_su.i_N = 0 + # S_su.i_C = 0.0313 * 12 + + # varies + S_aa = cmps_all.S_F.copy('S_aa') + S_aa.i_N = 0.007 * N_mw + S_aa.i_P = 0 + S_aa.i_C = 0.03 * C_mw + + S_fa = Component.from_chemical('S_fa', chemical='palmitate', + description='Total long-chain fatty acids', + measured_as='COD', + particle_size='Soluble', + degradability='Readily', + organic=True) + + S_va = Component.from_chemical('S_va', chemical='valerate', + description='Total valerate', + measured_as='COD', + particle_size='Soluble', + degradability='Readily', + organic=True) + + S_bu = Component.from_chemical('S_bu', chemical='butyrate', + description='Total butyrate', + measured_as='COD', + particle_size='Soluble', + degradability='Readily', + organic=True) + + S_pro = cmps_all.S_Prop.copy('S_pro') + S_ac = cmps_all.S_Ac.copy('S_ac') + S_h2 = cmps_all.S_H2.copy('S_h2') + S_ch4 = cmps_all.S_CH4.copy('S_ch4') + + S_IC = Component.from_chemical('S_IC', chemical='CO2', + measured_as='C', + description='Inorganic carbon', + particle_size='Dissolved gas', + degradability='Undegradable', + organic=False) + S_IC.copy_models_from(S_ch4, ('Cn',)) + + S_IN = Component.from_chemical('S_IN', chemical='NH3', + measured_as='N', + description='Inorganic nitrogen', + particle_size='Soluble', + degradability='Undegradable', + organic=False) + + X_su = cmps_all.X_FO.copy('X_su') + X_su.description = 'Biomass uptaking sugars' + + X_aa = cmps_all.X_FO.copy('X_aa') + X_aa.description = 'Biomass uptaking amino acids' + + X_fa = cmps_all.X_FO.copy('X_fa') + X_fa.description = 'Biomass uptaking long chain fatty acids' + + X_c4 = cmps_all.X_FO.copy('X_c4') + X_c4.description = 'Biomass uptaking c4 fatty acids' + + X_pro = cmps_all.X_PRO.copy('X_pro') + X_ac = cmps_all.X_ACO.copy('X_ac') + X_h2 = cmps_all.X_HMO.copy('X_h2') + + for bio in (X_su, X_aa, X_fa, X_c4, X_pro, X_ac, X_h2): + # bio.formula = 'C5H7O2N' + bio.i_C = 0.0313 * C_mw + bio.i_N = 0.08 + + S_cat = cmps_all.S_CAT.copy('S_cat') + S_an = cmps_all.S_AN.copy('S_an') + S_cat.i_mass = S_an.i_mass = 1 + + cmps_adm1 = Components([S_su, S_aa, S_fa, S_va, S_bu, S_pro, S_ac, S_h2, + S_ch4, S_IC, S_IN, S_I, X_c, X_ch, X_pr, X_li, + X_su, X_aa, X_fa, X_c4, X_pro, X_ac, X_h2, X_I, + S_cat, S_an, cmps_all.H2O]) + cmps_adm1.default_compile() + if set_thermo: settings.set_thermo(cmps_adm1) + return cmps_adm1 + +# create_adm1_cmps() + + +#%% +# ============================================================================= +# kinetic rate functions +# ============================================================================= + +R = 8.3145e-2 # Universal gas constant, [bar/M/K] + +def non_compet_inhibit(Si, Ki): + return Ki/(Ki+Si) + +def substr_inhibit(Si, Ki): + return Si/(Ki+Si) + +def mass2mol_conversion(cmps): + '''conversion factor from kg[measured_as]/m3 to mol[component]/L''' + return cmps.i_mass / cmps.chem_MW + +# def T_correction_factor(T1, T2, theta): +# return np.exp(theta * (T2-T1)) + +def T_correction_factor(T1, T2, delta_H): + return np.exp(delta_H/(R*100) * (1/T1 - 1/T2)) # R converted to SI + +# def calc_Kas(pKas, T_base, T_op, theta): +# pKas = np.asarray(pKas) +# return 10**(-pKas) * T_correction_factor(T_base, T_op, theta) + +def acid_base_rxn(h_ion, weak_acids_tot, Kas): + # h, nh4, hco3, ac, pr, bu, va = mols + # S_cat, S_an, S_IN, S_IC, S_ac, S_pro, S_bu, S_va = weak_acids_tot # in M + S_cat, S_an, S_IN = weak_acids_tot[:3] + # Kw, Ka_nh, Ka_co2, Ka_ac, Ka_pr, Ka_bu, Ka_va = Kas + Kw = Kas[0] + oh_ion = Kw/h_ion + nh3, hco3, ac, pro, bu, va = Kas[1:] * weak_acids_tot[2:] / (Kas[1:] + h_ion) + return S_cat + h_ion + (S_IN - nh3) - S_an - oh_ion - hco3 - ac - pro - bu - va + +def fprime_abr(h_ion, weak_acids_tot, Kas): + S_cat, S_an, S_IN = weak_acids_tot[:3] + Kw = Kas[0] + doh_ion = - Kw / h_ion ** 2 + dnh3, dhco3, dac, dpro, dbu, dva = - Kas[1:] * weak_acids_tot[2:] / (Kas[1:] + h_ion)**2 + return 1 + (-dnh3) - doh_ion - dhco3 - dhco3 - dac - dpro - dbu - dva + +def pH_inhibit(pH, ul, ll, lower_only=True): + if lower_only: + # if pH >= ul: return 1 + # else: return exp(-3 * ((pH-ul)/(ul-ll))**2) + low_by = np.minimum(pH-ul, 0) + return np.exp(-3 * (low_by/(ul-ll))**2) + else: + return (1+2*10**(0.5*(ll-ul)))/(1+10**(pH-ul)+10**(ll-pH)) + +def Hill_inhibit(H_ion, ul, ll): + n = 3/(ul-ll) + K = 10**(-(ul+ll)/2) + return 1/(1+(H_ion/K) ** n) + +rhos = np.zeros(22) # 22 kinetic processes +Cs = np.empty(19) + +def rhos_adm1(state_arr, params): + ks = params['rate_constants'] + Ks = params['half_sat_coeffs'] + cmps = params['components'] + # n = len(cmps) + pH_ULs = params['pH_ULs'] + pH_LLs = params['pH_LLs'] + KS_IN = params['KS_IN'] + KI_nh3 = params['KI_nh3'] + KIs_h2 = params['KIs_h2'] + KHb = params['K_H_base'] + Kab = params['Ka_base'] + KH_dH = params['K_H_dH'] + Ka_dH = params['Ka_dH'] + kLa = params['kLa'] + T_base = params['T_base'] + root = params['root'] + + # Cs_ids = cmps.indices(['X_c', 'X_ch', 'X_pr', 'X_li', 'X_su', 'X_aa', + # 'X_fa', 'X_c4', 'X_c4', 'X_pro', 'X_ac', 'X_h2', + # 'X_su', 'X_aa', 'X_fa', 'X_c4', 'X_pro', 'X_ac', 'X_h2']) + # Cs = state_arr[Cs_ids] + Cs[:8] = state_arr[12:20] + Cs[8:12] = state_arr[19:23] + Cs[12:] = state_arr[16:23] + # substrates_ids = cmps.indices(['S_su', 'S_aa', 'S_fa', 'S_va', + # 'S_bu', 'S_pro', 'S_ac', 'S_h2']) + # substrates = state_arr[substrates_ids] + substrates = state_arr[:8] + # S_va, S_bu, S_h2, S_IN = state_arr[cmps.indices(['S_va', 'S_bu', 'S_h2', 'S_IN'])] + # S_va, S_bu, S_h2, S_ch4, S_IC, S_IN = state_arr[[3,4,7,8,9,10]] + S_va, S_bu, S_h2, S_IN = state_arr[[3,4,7,10]] + unit_conversion = mass2mol_conversion(cmps) + cmps_in_M = state_arr[:27] * unit_conversion + weak_acids = cmps_in_M[[24, 25, 10, 9, 6, 5, 4, 3]] + + T_op = state_arr[-1] + biogas_S = state_arr[7:10].copy() + biogas_p = R * T_op * state_arr[27:30] + Kas = Kab * T_correction_factor(T_base, T_op, Ka_dH) + KH = KHb * T_correction_factor(T_base, T_op, KH_dH) / unit_conversion[7:10] + + rhos[:-3] = ks * Cs + rhos[4:12] *= substr_inhibit(substrates, Ks) + if S_va > 0: rhos[7] *= 1/(1+S_bu/S_va) + if S_bu > 0: rhos[8] *= 1/(1+S_va/S_bu) + + h = brenth(acid_base_rxn, 1e-14, 1.0, + args=(weak_acids, Kas), + xtol=1e-12, maxiter=100) + + nh3 = Kas[1] * weak_acids[2] / (Kas[1] + h) + co2 = weak_acids[3] - Kas[2] * weak_acids[3] / (Kas[2] + h) + biogas_S[-1] = co2 / unit_conversion[9] + + Iph = Hill_inhibit(h, pH_ULs, pH_LLs) + Ih2 = non_compet_inhibit(S_h2, KIs_h2) + root.data = [-np.log10(h), Iph, Ih2] + rhos[4:12] *= Iph * substr_inhibit(S_IN, KS_IN) + rhos[6:10] *= Ih2 + # rhos[4:12] *= Hill_inhibit(h, pH_ULs, pH_LLs) * substr_inhibit(S_IN, KS_IN) + # rhos[6:10] *= non_compet_inhibit(S_h2, KIs_h2) + rhos[10] *= non_compet_inhibit(nh3, KI_nh3) + rhos[-3:] = kLa * (biogas_S - KH * biogas_p) + # print(rhos) + return rhos + +#%% +# ============================================================================= +# ADM1 class +# ============================================================================= +class TempState: + def __init__(self): + self.data = [] + + # def append(self, value): + # self.data += [value] + +@chemicals_user +class ADM1(CompiledProcesses): + """ + Anaerobic Digestion Model No.1. [1]_, [2]_ + + Parameters + ---------- + components : class:`CompiledComponents`, optional + Components corresponding to each entry in the stoichiometry array, + defaults to thermosteam.settings.chemicals. + path : str, optional + Alternative file path for the Petersen matrix. The default is None. + N_xc : float, optional + Nitrogen content of composite materials [kmol N/kg COD]. The default is 2.686e-3. + N_I : float, optional + Nitrogen content of inert organics [kmol N/kg COD]. The default is 4.286e-3. + N_aa : float, optional + Nitrogen content of amino acids [kmol N/kg COD]. The default is 7e-3. + f_ch_xc : float, optional + Fraction of carbohydrates from composite disintegration [kg COD/kg COD]. + The default is 0.2. + f_pr_xc : float, optional + Fraction of proteins from composite disintegration [kg COD/kg COD]. + The default is 0.2. + f_li_xc : float, optional + Fraction of lipids from composite disintegration [kg COD/kg COD]. + The default is 0.3. + f_xI_xc : float, optional + Fraction of inert particulates from composite disintegration + [kg COD/kg COD]. The default is 0.2. + f_fa_li : float, optional + Fraction of long chain fatty acids (LCFAs) from hydrolysis of lipids + [kg COD/kg COD]. The default is 0.95. + f_bu_su : float, optional + Fraction of butyrate from sugars [kg COD/kg COD]. The default is 0.13. + f_pro_su : float, optional + Fraction of propionate from sugars [kg COD/kg COD]. The default is 0.27. + f_ac_su : float, optional + Fraction of acetate from sugars [kg COD/kg COD]. The default is 0.41. + f_va_aa : float, optional + Fraction of valerate from amino acids [kg COD/kg COD]. The default is 0.23. + f_bu_aa : float, optional + Fraction of butyrate from amino acids [kg COD/kg COD]. The default is 0.26. + f_pro_aa : float, optional + Fraction of propionate from amino acids [kg COD/kg COD]. The default is 0.05. + f_ac_aa : float, optional + Fraction of acetate from amino acids [kg COD/kg COD]. The default is 0.4. + f_ac_fa : float, optional + Fraction of acetate from LCFAs [kg COD/kg COD]. The default is 0.7. + f_pro_va : float, optional + Fraction of propionate from LCFAs [kg COD/kg COD]. The default is 0.54. + f_ac_va : float, optional + Fraction of acetate from valerate [kg COD/kg COD]. The default is 0.31. + f_ac_bu : float, optional + Fraction of acetate from butyrate [kg COD/kg COD]. The default is 0.8. + f_ac_pro : float, optional + Fraction of acetate from propionate [kg COD/kg COD]. The default is 0.57. + Y_su : float, optional + Biomass yield of sugar uptake [kg COD/kg COD]. The default is 0.1. + Y_aa : float, optional + Biomass yield of amino acid uptake [kg COD/kg COD]. The default is 0.08. + Y_fa : float, optional + Biomass yield of LCFA uptake [kg COD/kg COD]. The default is 0.06. + Y_c4 : float, optional + Biomass yield of butyrate or valerate uptake [kg COD/kg COD]. + The default is 0.06. + Y_pro : float, optional + Biomass yield of propionate uptake [kg COD/kg COD]. The default is 0.04. + Y_ac : float, optional + Biomass yield of acetate uptake [kg COD/kg COD]. The default is 0.05. + Y_h2 : float, optional + Biomass yield of H2 uptake [kg COD/kg COD]. The default is 0.06. + q_dis : float, optional + Composites disintegration rate constant [d^(-1)]. The default is 0.5. + q_ch_hyd : float, optional + Carbohydrate hydrolysis rate constant [d^(-1)]. The default is 10. + q_pr_hyd : float, optional + Protein hydrolysis rate constant [d^(-1)]. The default is 10. + q_li_hyd : float, optional + Lipid hydrolysis rate constant [d^(-1)]. The default is 10. + k_su : float, optional + Sugar uptake rate constant [d^(-1)]. The default is 30. + k_aa : float, optional + Amino acid uptake rate constant [d^(-1)]. The default is 50. + k_fa : float, optional + LCFA uptake rate constant [d^(-1)]. The default is 6. + k_c4 : float, optional + Butyrate or valerate uptake rate constant [d^(-1)]. The default is 20. + k_pro : float, optional + Propionate uptake rate constant [d^(-1)]. The default is 13. + k_ac : float, optional + Acetate uptake rate constant [d^(-1)]. The default is 8. + k_h2 : float, optional + H2 uptake rate constant [d^(-1)]. The default is 35. + K_su : float, optional + Half saturation coefficient of sugar uptake [kg COD/m3]. + The default is 0.5. + K_aa : float, optional + Half saturation coefficient of amino acid uptake [kg COD/m3]. + The default is 0.3. + K_fa : float, optional + Half saturation coefficient of LCFA uptake [kg COD/m3]. + The default is 0.4. + K_c4 : float, optional + Half saturation coefficient of butyrate or valerate uptake [kg COD/m3]. + The default is 0.2. + K_pro : float, optional + Half saturation coefficient of propionate uptake [kg COD/m3]. + The default is 0.1. + K_ac : float, optional + Half saturation coefficient of acetate uptake [kg COD/m3]. + The default is 0.15. + K_h2 : float, optional + Half saturation coefficient of H2 uptake [kg COD/m3]. + The default is 7e-6. + b_su : float, optional + Decay rate constant of sugar-uptaking biomass [d^(-1)]. + The default is 0.02. + b_aa : float, optional + Decay rate constant of amino-acid-uptaking biomass [d^(-1)]. + The default is 0.02. + b_fa : float, optional + Decay rate constant of LCFA-uptaking biomass [d^(-1)]. + The default is 0.02. + b_c4 : float, optional + Decay rate constant of valerate- or butyrate-uptaking biomass [d^(-1)]. + The default is 0.02. + b_pro : float, optional + Decay rate constant of propionate-uptaking biomass [d^(-1)]. + The default is 0.02. + b_ac : float, optional + Decay rate constant of acetate-uptaking biomass [d^(-1)]. + The default is 0.02. + b_h2 : float, optional + Decay rate constant of H2-uptaking biomass [d^(-1)]. The default is 0.02. + KI_h2_fa : float, optional + H2 inhibition coefficient for LCFA uptake [kg COD/m3]. The default is 5e-6. + KI_h2_c4 : float, optional + H2 inhibition coefficient for butyrate or valerate uptake [kg COD/m3]. + The default is 1e-5. + KI_h2_pro : float, optional + H2 inhibition coefficient for propionate uptake [kg COD/m3]. + The default is 3.5e-6. + KI_nh3 : float, optional + Free ammonia inhibition coefficient for acetate uptake [M]. + The default is 1.8e-3. + KS_IN : float, optional + Inorganic nitrogen (nutrient) inhibition coefficient for soluble + substrate uptake [M]. The default is 1e-4. + pH_limits_aa : 2-tuple, optional + Lower and upper limits of pH inhibition for acidogens and acetogens, + unitless. The default is (4,5.5). + pH_limits_ac : 2-tuple, optional + Lower and upper limits of pH inhibition for aceticlastic methanogens, + unitless. The default is (6,7). + pH_limits_h2 : 2-tuple, optional + Lower and upper limits of pH inhibition for H2-utilizing methanogens, + unitless. The default is (5,6). + T_base : float, optional + Base temperature for kinetic parameters [K]. The default is 298.15. + pKa_base : iterable[float], optional + pKa (equilibrium coefficient) values of acid-base pairs at the base + temperature, unitless, following the order of `ADM1._acid_base_pairs`. + The default is [14, 9.25, 6.35, 4.76, 4.88, 4.82, 4.86]. + Ka_dH : iterable[float], optional + Heat of reaction of each acid-base pair at base temperature [J/mol], + following the order of `ADM1._acid_base_pairs`. The default is + [55900, 51965, 7646, 0, 0, 0, 0]. + kLa : float, optional + Liquid-gas mass transfer coefficient [d^(-1)]. The default is 200. + K_H_base : iterable[float], optional + Henry's Law coefficients of biogas species at the base temperature + [M dissolved in liquid/bar]. Follows the order of `ADM1._biogas_IDs`. + The default is [7.8e-4, 1.4e-3, 3.5e-2]. + K_H_dH : iterable[float], optional + Heat of reaction of liquid-gas transfer of biogas species [J/mol]. + Follows the order of `ADM1._biogas_IDs`. The default is + [-4180, -14240, -19410]. + + Examples + -------- + >>> from qsdsan import processes as pc + >>> cmps = pc.create_adm1_cmps() + >>> adm1 = pc.ADM1() + >>> adm1.show() + ADM1([disintegration, hydrolysis_carbs, hydrolysis_proteins, hydrolysis_lipids, uptake_sugars, uptake_amino_acids, uptake_LCFA, uptake_valerate, uptake_butyrate, uptake_propionate, uptake_acetate, uptake_h2, decay_Xsu, decay_Xaa, decay_Xfa, decay_Xc4, decay_Xpro, decay_Xac, decay_Xh2, h2_transfer, ch4_transfer, IC_transfer]) + + References + ---------- + .. [1] Batstone, D. J.; Keller, J.; Angelidaki, I.; Kalyuzhnyi, S. V; + Pavlostathis, S. G.; Rozzi, A.; Sanders, W. T. M.; Siegrist, H.; + Vavilin, V. A. The IWA Anaerobic Digestion Model No 1 (ADM1). + Water Sci. Technol. 2002, 45 (10), 65–73. + .. [2] Rosen, C.; Jeppsson, U. Aspects on ADM1 Implementation within + the BSM2 Framework; Lund, 2006. + """ + + _stoichio_params = ('f_ch_xc', 'f_pr_xc', 'f_li_xc', 'f_xI_xc', 'f_sI_xc', + 'f_fa_li', 'f_bu_su', 'f_pro_su', 'f_ac_su', 'f_h2_su', + 'f_va_aa', 'f_bu_aa', 'f_pro_aa', 'f_ac_aa', 'f_h2_aa', + 'f_ac_fa', 'f_h2_fa', 'f_pro_va', 'f_ac_va', 'f_h2_va', + 'f_ac_bu', 'f_h2_bu', 'f_ac_pro', 'f_h2_pro', + 'Y_su', 'Y_aa', 'Y_fa', 'Y_c4', 'Y_pro', 'Y_ac', 'Y_h2') + _kinetic_params = ('rate_constants', 'half_sat_coeffs', 'pH_ULs', 'pH_LLs', + 'KS_IN', 'KI_nh3', 'KIs_h2', + 'Ka_base', 'Ka_dH', 'K_H_base', 'K_H_dH', 'kLa', + 'T_base', 'components', 'root') + _acid_base_pairs = (('H+', 'OH-'), ('NH4+', 'NH3'), ('CO2', 'HCO3-'), + ('HAc', 'Ac-'), ('HPr', 'Pr-'), + ('HBu', 'Bu-'), ('HVa', 'Va-')) + _biogas_IDs = ('S_h2', 'S_ch4', 'S_IC') + + def __new__(cls, components=None, path=None, N_xc=2.686e-3, N_I=4.286e-3, N_aa=7e-3, + f_ch_xc=0.2, f_pr_xc=0.2, f_li_xc=0.3, f_xI_xc=0.2, + f_fa_li=0.95, f_bu_su=0.13, f_pro_su=0.27, f_ac_su=0.41, + f_va_aa=0.23, f_bu_aa=0.26, f_pro_aa=0.05, f_ac_aa=0.4, + f_ac_fa=0.7, f_pro_va=0.54, f_ac_va=0.31, f_ac_bu=0.8, f_ac_pro=0.57, + Y_su=0.1, Y_aa=0.08, Y_fa=0.06, Y_c4=0.06, Y_pro=0.04, Y_ac=0.05, Y_h2=0.06, + q_dis=0.5, q_ch_hyd=10, q_pr_hyd=10, q_li_hyd=10, + k_su=30, k_aa=50, k_fa=6, k_c4=20, k_pro=13, k_ac=8, k_h2=35, + K_su=0.5, K_aa=0.3, K_fa=0.4, K_c4=0.2, K_pro=0.1, K_ac=0.15, K_h2=7e-6, + b_su=0.02, b_aa=0.02, b_fa=0.02, b_c4=0.02, b_pro=0.02, b_ac=0.02, b_h2=0.02, + KI_h2_fa=5e-6, KI_h2_c4=1e-5, KI_h2_pro=3.5e-6, KI_nh3=1.8e-3, KS_IN=1e-4, + pH_limits_aa=(4,5.5), pH_limits_ac=(6,7), pH_limits_h2=(5,6), + T_base=298.15, pKa_base=[14, 9.25, 6.35, 4.76, 4.88, 4.82, 4.86], + Ka_dH=[55900, 51965, 7646, 0, 0, 0, 0], + kLa=200, K_H_base=[7.8e-4, 1.4e-3, 3.5e-2], + K_H_dH=[-4180, -14240, -19410], + **kwargs): + + cmps = _load_components(components) + cmps.X_c.i_N = N_xc * N_mw + cmps.X_I.i_N = cmps.S_I.i_N = N_I * N_mw + cmps.S_aa.i_N = cmps.X_pr.i_N = N_aa * N_mw + + if not path: path = _path + self = Processes.load_from_file(path, + components=cmps, + conserved_for=('C', 'N'), + parameters=cls._stoichio_params, + compile=False) + + gas_transfer = [] + for i in cls._biogas_IDs: + new_p = Process('%s_transfer' % i.lstrip('S_'), + reaction={i:-1}, + ref_component=i, + conserved_for=(), + parameters=()) + gas_transfer.append(new_p) + self.extend(gas_transfer) + self.compile(to_class=cls) + + stoichio_vals = (f_ch_xc, f_pr_xc, f_li_xc, f_xI_xc, 1-f_ch_xc-f_pr_xc-f_li_xc-f_xI_xc, + f_fa_li, f_bu_su, f_pro_su, f_ac_su, 1-f_bu_su-f_pro_su-f_ac_su, + f_va_aa, f_bu_aa, f_pro_aa, f_ac_aa, 1-f_va_aa-f_bu_aa-f_pro_aa-f_ac_aa, + f_ac_fa, 1-f_ac_fa, f_pro_va, f_ac_va, 1-f_pro_va-f_ac_va, + f_ac_bu, 1-f_ac_bu, f_ac_pro, 1-f_ac_pro, + Y_su, Y_aa, Y_fa, Y_c4, Y_pro, Y_ac, Y_h2) + pH_LLs = np.array([pH_limits_aa[0]]*6 + [pH_limits_ac[0], pH_limits_h2[0]]) + pH_ULs = np.array([pH_limits_aa[1]]*6 + [pH_limits_ac[1], pH_limits_h2[1]]) + ks = np.array((q_dis, q_ch_hyd, q_pr_hyd, q_li_hyd, + k_su, k_aa, k_fa, k_c4, k_c4, k_pro, k_ac, k_h2, + b_su, b_aa, b_fa, b_c4, b_pro, b_ac, b_h2)) + Ks = np.array((K_su, K_aa, K_fa, K_c4, K_c4, K_pro, K_ac, K_h2)) + KIs_h2 = np.array((KI_h2_fa, KI_h2_c4, KI_h2_c4, KI_h2_pro)) + K_H_base = np.array(K_H_base) + K_H_dH = np.array(K_H_dH) + Ka_base = np.array([10**(-pKa) for pKa in pKa_base]) + Ka_dH = np.array(Ka_dH) + root = TempState() + # root.data = 10**(-7.4655) + dct = self.__dict__ + dct.update(kwargs) + + self.set_rate_function(rhos_adm1) + dct['_parameters'] = dict(zip(cls._stoichio_params, stoichio_vals)) + self.rate_function._params = dict(zip(cls._kinetic_params, + [ks, Ks, pH_ULs, pH_LLs, KS_IN*N_mw, + KI_nh3, KIs_h2, Ka_base, Ka_dH, + K_H_base, K_H_dH, kLa, + T_base, self._components, root])) + + return self + + def set_pKas(self, pKas): + '''Set the pKa values of the acid-base reactions at the base temperature.''' + if len(pKas) != 7: + raise ValueError(f'pKas must be an array of 7 elements, one for each ' + f'acid-base pair, not {len(pKas)} elements.') + dct = self.rate_function._params + dct['Ka_base'] = np.array([10**(-pKa) for pKa in pKas]) + + def _find_index(self, process): + isa = isinstance + if isa(process, int): return process + elif isa(process, str): return self.index(process) + elif isa(process, Process): return self.index(process.ID) + else: raise TypeError(f'must input an int or str or :class:`Process`, ' + f'not {type(process)}') + + def set_rate_constant(self, k, process): + '''Set the reaction rate constant [d^(-1)] for a process given its ID.''' + i = self._find_index(process) + self.rate_function._params['rate_constants'][i] = k + + def set_half_sat_K(self, K, process): + '''Set the substrate half saturation coefficient [kg/m3] for a process given its ID.''' + i = self._find_index(process) + self.rate_function._params['half_sat_coeffs'][i-4] = K + + def set_pH_inhibit_bounds(self, process, lower=None, upper=None): + '''Set the upper and/or lower limit(s) of pH inhibition [unitless] for a process given its ID.''' + i = self._find_index(process) + dct = self.rate_function._params + if lower is None: lower = dct['pH_LLs'][i-4] + else: dct['pH_LLs'][i-4] = lower + if upper is None: upper = dct['pH_ULs'][i-4] + else: dct['pH_ULs'][i-4] = upper + if lower >= upper: + raise ValueError(f'lower limit for pH inhibition of {process} must ' + f'be lower than the upper limit, not {[lower, upper]}') + + def set_h2_inhibit_K(self, KI, process): + '''Set the H2 inhibition coefficient [kg/m3] for a process given its ID.''' + i = self._find_index(process) + self.rate_function._params['KIs_h2'][i-6] = KI + + def set_KS_IN(self, K): + '''Set inhibition coefficient for inorganic nitrogen as a secondary + substrate [M nitrogen].''' + self.rate_function._params['KS_IN'] = K * N_mw + + def set_KI_nh3(self, K): + '''Set inhibition coefficient for free ammonia [M].''' + self.rate_function._params['KI_nh3'] = K + + def set_parameters(self, **parameters): + '''Set values to stoichiometric parameters in `ADM1._stoichio_params`.''' + non_stoichio = {} + for k,v in parameters.items(): + if k in self._stoichio_params: + if v >= 0 : self._parameters[k] = v + else: raise ValueError(f'{k} must >= 0, not {v}') + else: non_stoichio[k] = v + if non_stoichio: + warn(f'ignored value setting for non-stoichiometric parameters {non_stoichio}') + self.check_stoichiometric_parameters() + if self._stoichio_lambdified is not None: + self.__dict__['_stoichio_lambdified'] = None + + def check_stoichiometric_parameters(self): + '''Check whether product COD fractions sum up to 1 for each process.''' + stoichio = self.parameters + subst = ('xc', 'su', 'aa', 'fa', 'va', 'bu', 'pro') + for s in subst: + f_tot = sum([stoichio[k] for k in self._stoichio_params[:-7] \ + if k.endswith(s)]) + if f_tot != 1: + raise ValueError(f"the sum of 'f_()_{s}' values must equal 1") From 5a2d040453027eaad3d480473092681eb5f651e7 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Thu, 23 Mar 2023 16:13:32 -0500 Subject: [PATCH 062/424] Remove X_c X_c (composites) has been removed in the modified ADM1 --- qsdsan/processes/_adm1_modified.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/qsdsan/processes/_adm1_modified.py b/qsdsan/processes/_adm1_modified.py index 6e99ff63..1c4bb518 100644 --- a/qsdsan/processes/_adm1_modified.py +++ b/qsdsan/processes/_adm1_modified.py @@ -43,10 +43,11 @@ def create_adm1_cmps(set_thermo=True): cmps_all = Components.load_default() # varies - X_c = cmps_all.X_OHO.copy('X_c') - X_c.description = 'Composite' - X_c.i_C = 0.02786 * C_mw - X_c.i_N = 0.0376 + # X_c = cmps_all.X_OHO.copy('X_c') + # X_c.description = 'Composite' + # X_c.i_C = 0.02786 * C_mw + # X_c.i_N = 0.0376 + X_ch = Component.from_chemical('X_ch', chemical='glycogen', Tc=1011.4, # glucan description='Carbohydrates', measured_as='COD', From ee36ee9b0f7aab5843abed028a41c87f03850f9e Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Thu, 23 Mar 2023 17:25:30 -0500 Subject: [PATCH 063/424] Adding new components Added new components (S_IP, X_PHA, X_PP, X_PAO, S_K, S_Mg) as suggested in the modified ADM1 --- qsdsan/processes/_adm1_modified.py | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/qsdsan/processes/_adm1_modified.py b/qsdsan/processes/_adm1_modified.py index 1c4bb518..6b95e702 100644 --- a/qsdsan/processes/_adm1_modified.py +++ b/qsdsan/processes/_adm1_modified.py @@ -132,7 +132,9 @@ def create_adm1_cmps(set_thermo=True): particle_size='Soluble', degradability='Undegradable', organic=False) - + + S_IP = cmps_all.S_PO4.copy('S_IP') + X_su = cmps_all.X_FO.copy('X_su') X_su.description = 'Biomass uptaking sugars' @@ -148,6 +150,26 @@ def create_adm1_cmps(set_thermo=True): X_pro = cmps_all.X_PRO.copy('X_pro') X_ac = cmps_all.X_ACO.copy('X_ac') X_h2 = cmps_all.X_HMO.copy('X_h2') + + X_PHA = cmps_all.X_PAO_PHA.copy('X_PHA') + + X_PP = cmps_all.X_PAO_PP_Lo.copy('X_PP') + + X_PAO = cmps_all.X_PAO.copy('X_PAO') + + S_K = Component.from_chemical('S_K', chemical='K', + measured_as='K', + description='Potassium', + particle_size='Soluble', + degradability='Undegradable', + organic=False) + + S_Mg = Component.from_chemical('S_Mg', chemical='Mg', + measured_as='Mg', + description='Magnesium', + particle_size='Soluble', + degradability='Undegradable', + organic=False) for bio in (X_su, X_aa, X_fa, X_c4, X_pro, X_ac, X_h2): # bio.formula = 'C5H7O2N' @@ -159,8 +181,9 @@ def create_adm1_cmps(set_thermo=True): S_cat.i_mass = S_an.i_mass = 1 cmps_adm1 = Components([S_su, S_aa, S_fa, S_va, S_bu, S_pro, S_ac, S_h2, - S_ch4, S_IC, S_IN, S_I, X_c, X_ch, X_pr, X_li, + S_ch4, S_IC, S_IN, S_I, S_IP, X_ch, X_pr, X_li, X_su, X_aa, X_fa, X_c4, X_pro, X_ac, X_h2, X_I, + X_PHA, X_PP, X_PAO, S_K, S_Mg, S_cat, S_an, cmps_all.H2O]) cmps_adm1.default_compile() if set_thermo: settings.set_thermo(cmps_adm1) From 7d29f31455282e8e26c19f329e5ed8022122c68b Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Fri, 24 Mar 2023 08:24:53 -0500 Subject: [PATCH 064/424] Correction in Primary Clarifier Removing the limitation of f_correction factor since couldn't locate the condition in literature --- qsdsan/sanunits/_clarifier.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index 2cfbd4d9..330162c0 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -687,8 +687,8 @@ def f_corr(self): @f_corr.setter def f_corr(self, corr): if corr is not None: - if corr > 1 or corr < 0: - raise ValueError(f'correction factor must be within [0, 1], not {corr}') + # if corr > 1 or corr < 0: + # raise ValueError(f'correction factor must be within [0, 1], not {corr}') self._corr = corr else: raise ValueError('correction factor expected from user') From 6b9b5d95da5aba3794fdee49c76be84700a80210 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Fri, 24 Mar 2023 08:26:04 -0500 Subject: [PATCH 065/424] Edit in thickener documentation Thickener percentage refers to percentage of solids in underflow, not 'suspended sludge' --- qsdsan/sanunits/_sludge_treatment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py index 0585e58e..042b99c5 100644 --- a/qsdsan/sanunits/_sludge_treatment.py +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -32,7 +32,7 @@ class Thickener(SanUnit): outs : class:`WasteStream` Treated effluent and sludge. thickener_perc : float - The percentage of Suspended Sludge in the underflow of the thickener.[1] + The percentage of solids in the underflow of the thickener.[1] TSS_removal_perc : float The percentage of suspended solids removed in the thickener.[1] solids_loading_rate : float From 7b2f2dccc7838a14faaf3ac7b06330e5e30da0b3 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Fri, 24 Mar 2023 09:19:04 -0500 Subject: [PATCH 066/424] Updated Gas Extraction Membrane Added documentation and further steps to write _update_state( ), and _update_dstate( ) --- qsdsan/sanunits/_membrane_gas_extraction.py | 66 +++++++++++++++++---- 1 file changed, 53 insertions(+), 13 deletions(-) diff --git a/qsdsan/sanunits/_membrane_gas_extraction.py b/qsdsan/sanunits/_membrane_gas_extraction.py index e908ac64..bbfebeac 100644 --- a/qsdsan/sanunits/_membrane_gas_extraction.py +++ b/qsdsan/sanunits/_membrane_gas_extraction.py @@ -22,8 +22,45 @@ class GasExtractionMembrane(SanUnit): + """ + A Primary clarifier based on BSM2 Layout. [1] + + Parameters + ---------- + ID : str + ID for the Gas Extraction Membrane. The default is 'GEM'. + ins : class:`WasteStream` + Influent to the Gas Extraction Membrane. Expected number of influent is 3. + outs : class:`WasteStream` + Gas and Liquid streams are expected effluents. + FiberID : float + Inner diameter of the membrane [m]. + FiberOD : float + Outer diameter of the membrane [m]. + NumTubes : float + The number of fibers in the membrane. + ShellDia : float + The diameter of the shell [m]. + SurfArea : float + Surface area of membrane [m^2]. + GasID : array + Array containing IDs of gases to be extracted. + PVac : float + Operating vaccum pressure in the membrane [Pa]. + segs : float + Number of segments considered in the membrane. + GasPerm : dict + Dictionary of permeability of gases. + HenryPreFac : dict + Dictionary of Henry's Law Factor for gases. + HenrySlope : dict + Dictionary of Henry's Slope for gases. + WilkeChang : dict + Dictionary of Wilke Chang correlation for gases. + """ + _N_ins = 1 - _N_outs = 1 + _N_outs = 2 # All gas properties are in form of dictionaries @@ -182,7 +219,7 @@ def ShellAc(self): @property def HeL(self): # Define the constant properties of gas - TRefH = 298.15 # Reference T for Henry's Law [K] + TRefH = 298.15 # Reference T for Henry's Law [K] NIST_HeL = self._hpf*(np.exp(self._hs*(1/self.ins[0].T - 1/TRefH))) return 1/NIST_HeL @@ -246,28 +283,30 @@ def _init_state(self): for j in range(numGas): self._state[j+i] = Cs[j] self._dstate = self._state*0 -# ============================================================================= -# self._cached_state = self._state.copy() -# self._cached_t = 0 -# ============================================================================= + def _update_state(self): eff, = self.outs # assuming this SanUnit has one outlet only numGas = len(self.GasID) - # Need to add effluent streams for liquid (this includes all cmps of influent ws) - # and gas (the multiplication should give out kg/hr values) + # Need to add effluent streams for liquid (this includes all cmps of influent ws) and gas (the multiplication should give out kg/hr values) - # The state of effluent Gas in the extraction membrane is the difference - # between lumen concentration of the last and first segment + # THE STATE OF THE GAS IN EXTRACTION MEMBRANE is the difference between lumen concentration between the last + # and first segment self._outs[0].state = self._state[ -2*numGas: -numGas] - self._state[ :numGas] - # The state of effluent Liquid stream is simply the concentration of - # the last lumen segment in the extraction membrane self._outs[1].state = self._state[-2*numGas: -numGas] + # Gas flowrate = Liquid flowrate = Qin + + def _update_dstate(self): eff, = self.outs numGas = len(self.GasID) - eff.dstate[:] = self._dstate[-numGas:] + + #eff.dstate[:] = self._dstate[-numGas:] + self._outs[0].dstate = self._dstate[ -2*numGas: -numGas] - self._dstate[ :numGas] + + self._outs[1].dstate = self._dstate[-2*numGas: -numGas] + def _run(self): pass @@ -401,6 +440,7 @@ def dy_dt(t, QC_ins, QC, dQC_ins): # Re-scale the shell concentration so that vacuum pressure stays constant during the entire process. Given the change of concentration that we have calculated above for the shell, we can re-scale the shell concentrations with the sumCp at each segment. + # This FOR LOOP maintains consistent pressure in the shell for i in range(0, Segs): for j in range(0, numGas): # Calculate the new concentration of gases in the shell From b2c940f60f945b5dca72c579099de74b7157ba79 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Sat, 25 Mar 2023 17:16:06 -0500 Subject: [PATCH 067/424] Added update_state to Gas Extraction Membrane Added (with comments) update_state( ) to Gas Extraction Membrane --- qsdsan/sanunits/_membrane_gas_extraction.py | 43 ++++++++++++++++----- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/qsdsan/sanunits/_membrane_gas_extraction.py b/qsdsan/sanunits/_membrane_gas_extraction.py index bbfebeac..953f3b4d 100644 --- a/qsdsan/sanunits/_membrane_gas_extraction.py +++ b/qsdsan/sanunits/_membrane_gas_extraction.py @@ -286,18 +286,42 @@ def _init_state(self): def _update_state(self): - eff, = self.outs # assuming this SanUnit has one outlet only + + inf = self.ins + cmps = inf.components + self.indexer = cmps.index + self.idx = cmps.indices(self.GasID) numGas = len(self.GasID) - # Need to add effluent streams for liquid (this includes all cmps of influent ws) and gas (the multiplication should give out kg/hr values) + # Need to add effluent streams for liquid (this includes all cmps of influent ws) and gas + # The multiplication of any of the first n-1 array element with last element should give out kg/hr values + + self._outs[0].state = np.zeros(len(cmps) + 1) + # The of the effluent gas in extraction membrane is the difference + # between lumen concentration in the last and first segment + gas_state_in_unit = self._state[ -2*numGas: -numGas] - self._state[ :numGas] # in mol/m3 + gas_state_in_unit = (gas_state_in_unit*cmps.chem_MW[self.idx])/cmps.i_mass[self.idx] # (mol/m3)*(g/mol) = g/m3 = mg/l + for i in range(0, len(cmps), 1): + for j in self.idx: + if i == j: + self._outs[0].state[i] = gas_state_in_unit[np.where(self.idx == j)] + + # Q_gas = ??Ian?? + # self._outs[0].state[-1] = Q_gas - # THE STATE OF THE GAS IN EXTRACTION MEMBRANE is the difference between lumen concentration between the last - # and first segment - self._outs[0].state = self._state[ -2*numGas: -numGas] - self._state[ :numGas] - self._outs[1].state = self._state[-2*numGas: -numGas] + self._outs[1].state = np.zeros(len(cmps) + 1) + # The state of effluent Liquid stream is simply the concentration of + # the last lumen segment in the extraction membrane + liquid_state_in_unit = self._state[-2*numGas: -numGas] # in mol/m3 + liquid_state_in_unit = (liquid_state_in_unit*cmps.chem_MW[self.idx])/cmps.i_mass[self.idx] # (mol/m3)*(g/mol) = g/m3 = mg/l + for i in range(0, len(cmps), 1): + for j in self.idx: + if i == j: + self._outs[1].state[i] = liquid_state_in_unit[np.where(self.idx == j)] + else: + self._outs[1].state[i] = self._ins_QC[i] + + self._outs[1].state[i] = self._ins_QC[-1] - # Gas flowrate = Liquid flowrate = Qin - - def _update_dstate(self): eff, = self.outs numGas = len(self.GasID) @@ -307,7 +331,6 @@ def _update_dstate(self): self._outs[1].dstate = self._dstate[-2*numGas: -numGas] - def _run(self): pass From c66b9cfb5741a52b15c4bb46ff11196e3b245755 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Mon, 27 Mar 2023 08:36:55 -0500 Subject: [PATCH 068/424] Added update_dstate to Gas Extraction Membrane Added the update_dstate( ) function to Extraction membrane --- qsdsan/sanunits/_membrane_gas_extraction.py | 44 +++++++++++++++++---- 1 file changed, 37 insertions(+), 7 deletions(-) diff --git a/qsdsan/sanunits/_membrane_gas_extraction.py b/qsdsan/sanunits/_membrane_gas_extraction.py index 953f3b4d..7c42c279 100644 --- a/qsdsan/sanunits/_membrane_gas_extraction.py +++ b/qsdsan/sanunits/_membrane_gas_extraction.py @@ -292,8 +292,9 @@ def _update_state(self): self.indexer = cmps.index self.idx = cmps.indices(self.GasID) numGas = len(self.GasID) - # Need to add effluent streams for liquid (this includes all cmps of influent ws) and gas - # The multiplication of any of the first n-1 array element with last element should give out kg/hr values + + # Need to add effluent streams for liquid (this includes all cmps of influent ws) and gas. + # The multiplication of any of the first n-1 array element with last element should give out kg/hr values. self._outs[0].state = np.zeros(len(cmps) + 1) # The of the effluent gas in extraction membrane is the difference @@ -320,16 +321,45 @@ def _update_state(self): else: self._outs[1].state[i] = self._ins_QC[i] - self._outs[1].state[i] = self._ins_QC[-1] + self._outs[1].state[-1] = self._ins_QC[-1] def _update_dstate(self): - eff, = self.outs + + inf = self.ins + cmps = inf.components + self.indexer = cmps.index + self.idx = cmps.indices(self.GasID) numGas = len(self.GasID) - #eff.dstate[:] = self._dstate[-numGas:] - self._outs[0].dstate = self._dstate[ -2*numGas: -numGas] - self._dstate[ :numGas] + # Need to add effluent streams for liquid (this includes all cmps of influent ws) and gas. + # The multiplication of any of the first n-1 array element with last element should give out kg/hr values. + + self._outs[0].dstate = np.zeros(len(cmps) + 1) + # The of the effluent gas in extraction membrane is the difference + # between lumen concentration in the last and first segment + gas_dstate_in_unit = self._dstate[ -2*numGas: -numGas] - self._dstate[ :numGas] # in mol/m3 + gas_dstate_in_unit = (gas_dstate_in_unit*cmps.chem_MW[self.idx])/cmps.i_mass[self.idx] # (mol/m3)*(g/mol) = g/m3 = mg/l + for i in range(0, len(cmps), 1): + for j in self.idx: + if i == j: + self._outs[0].dstate[i] = gas_dstate_in_unit[np.where(self.idx == j)] + + # Q_gas = ??Ian?? + # self._outs[0].state[-1] = Q_gas + + self._outs[1].dstate = np.zeros(len(cmps) + 1) + # The state of effluent Liquid stream is simply the concentration of + # the last lumen segment in the extraction membrane + liquid_dstate_in_unit = self._dstate[-2*numGas: -numGas] # in mol/m3 + liquid_dstate_in_unit = (liquid_dstate_in_unit*cmps.chem_MW[self.idx])/cmps.i_mass[self.idx] # (mol/m3)*(g/mol) = g/m3 = mg/l + for i in range(0, len(cmps), 1): + for j in self.idx: + if i == j: + self._outs[1].dstate[i] = liquid_dstate_in_unit[np.where(self.idx == j)] + else: + self._outs[1].dstate[i] = self._ins_dQC[i] - self._outs[1].dstate = self._dstate[-2*numGas: -numGas] + self._outs[1].dstate[-1] = self._ins_dQC[-1] def _run(self): pass From c8a0311813895b33cff7195c88c29ac29e9b870e Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Mon, 27 Mar 2023 17:38:18 -0500 Subject: [PATCH 069/424] Updated gas_extraction_membrane Made changes to update_state, update_dstate, and run functions --- qsdsan/sanunits/_membrane_gas_extraction.py | 44 ++++++++------------- 1 file changed, 16 insertions(+), 28 deletions(-) diff --git a/qsdsan/sanunits/_membrane_gas_extraction.py b/qsdsan/sanunits/_membrane_gas_extraction.py index 7c42c279..eddfc9b3 100644 --- a/qsdsan/sanunits/_membrane_gas_extraction.py +++ b/qsdsan/sanunits/_membrane_gas_extraction.py @@ -289,8 +289,7 @@ def _update_state(self): inf = self.ins cmps = inf.components - self.indexer = cmps.index - self.idx = cmps.indices(self.GasID) + idx = self.idx numGas = len(self.GasID) # Need to add effluent streams for liquid (this includes all cmps of influent ws) and gas. @@ -301,10 +300,8 @@ def _update_state(self): # between lumen concentration in the last and first segment gas_state_in_unit = self._state[ -2*numGas: -numGas] - self._state[ :numGas] # in mol/m3 gas_state_in_unit = (gas_state_in_unit*cmps.chem_MW[self.idx])/cmps.i_mass[self.idx] # (mol/m3)*(g/mol) = g/m3 = mg/l - for i in range(0, len(cmps), 1): - for j in self.idx: - if i == j: - self._outs[0].state[i] = gas_state_in_unit[np.where(self.idx == j)] + + self._outs[0].state[idx] = gas_state_in_unit # Q_gas = ??Ian?? # self._outs[0].state[-1] = Q_gas @@ -314,35 +311,28 @@ def _update_state(self): # the last lumen segment in the extraction membrane liquid_state_in_unit = self._state[-2*numGas: -numGas] # in mol/m3 liquid_state_in_unit = (liquid_state_in_unit*cmps.chem_MW[self.idx])/cmps.i_mass[self.idx] # (mol/m3)*(g/mol) = g/m3 = mg/l - for i in range(0, len(cmps), 1): - for j in self.idx: - if i == j: - self._outs[1].state[i] = liquid_state_in_unit[np.where(self.idx == j)] - else: - self._outs[1].state[i] = self._ins_QC[i] - self._outs[1].state[-1] = self._ins_QC[-1] + self._outs[1].state = self._ins_QC + self._outs[1].state[idx] = liquid_state_in_unit + + # self._outs[1].state[-1] = self._ins_QC[-1] def _update_dstate(self): inf = self.ins cmps = inf.components - self.indexer = cmps.index - self.idx = cmps.indices(self.GasID) numGas = len(self.GasID) + idx = self.idx # Need to add effluent streams for liquid (this includes all cmps of influent ws) and gas. # The multiplication of any of the first n-1 array element with last element should give out kg/hr values. - self._outs[0].dstate = np.zeros(len(cmps) + 1) # The of the effluent gas in extraction membrane is the difference # between lumen concentration in the last and first segment gas_dstate_in_unit = self._dstate[ -2*numGas: -numGas] - self._dstate[ :numGas] # in mol/m3 gas_dstate_in_unit = (gas_dstate_in_unit*cmps.chem_MW[self.idx])/cmps.i_mass[self.idx] # (mol/m3)*(g/mol) = g/m3 = mg/l - for i in range(0, len(cmps), 1): - for j in self.idx: - if i == j: - self._outs[0].dstate[i] = gas_dstate_in_unit[np.where(self.idx == j)] + + self._outs[0].dstate[idx] = gas_dstate_in_unit # Q_gas = ??Ian?? # self._outs[0].state[-1] = Q_gas @@ -352,17 +342,15 @@ def _update_dstate(self): # the last lumen segment in the extraction membrane liquid_dstate_in_unit = self._dstate[-2*numGas: -numGas] # in mol/m3 liquid_dstate_in_unit = (liquid_dstate_in_unit*cmps.chem_MW[self.idx])/cmps.i_mass[self.idx] # (mol/m3)*(g/mol) = g/m3 = mg/l - for i in range(0, len(cmps), 1): - for j in self.idx: - if i == j: - self._outs[1].dstate[i] = liquid_dstate_in_unit[np.where(self.idx == j)] - else: - self._outs[1].dstate[i] = self._ins_dQC[i] - self._outs[1].dstate[-1] = self._ins_dQC[-1] + self._outs[1].dstate = self._ins_dQC + self._outs[0].dstate[idx] = liquid_dstate_in_unit + # self._outs[1].dstate[-1] = self._ins_dQC[-1] def _run(self): - pass + s_in, = self.ins + gas, liq = self.outs + liq.copy(s_in) @property def ODE(self): From 24e11d00d5aee3886136301cd55a797961696110 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Tue, 28 Mar 2023 12:50:44 -0500 Subject: [PATCH 070/424] Deleting xc in modified ADM1 Removing stiochiometric parameters related to xc --- qsdsan/processes/_adm1_modified.py | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/qsdsan/processes/_adm1_modified.py b/qsdsan/processes/_adm1_modified.py index 6b95e702..f59d5bf7 100644 --- a/qsdsan/processes/_adm1_modified.py +++ b/qsdsan/processes/_adm1_modified.py @@ -4,6 +4,7 @@ This module is developed by: Joy Zhang + Part of this module is based on the Thermosteam package: https://github.com/BioSTEAMDevelopmentGroup/thermosteam @@ -181,7 +182,7 @@ def create_adm1_cmps(set_thermo=True): S_cat.i_mass = S_an.i_mass = 1 cmps_adm1 = Components([S_su, S_aa, S_fa, S_va, S_bu, S_pro, S_ac, S_h2, - S_ch4, S_IC, S_IN, S_I, S_IP, X_ch, X_pr, X_li, + S_ch4, S_IC, S_IN, S_IP, S_I, X_ch, X_pr, X_li, X_su, X_aa, X_fa, X_c4, X_pro, X_ac, X_h2, X_I, X_PHA, X_PP, X_PAO, S_K, S_Mg, S_cat, S_an, cmps_all.H2O]) @@ -349,18 +350,6 @@ class ADM1(CompiledProcesses): Nitrogen content of inert organics [kmol N/kg COD]. The default is 4.286e-3. N_aa : float, optional Nitrogen content of amino acids [kmol N/kg COD]. The default is 7e-3. - f_ch_xc : float, optional - Fraction of carbohydrates from composite disintegration [kg COD/kg COD]. - The default is 0.2. - f_pr_xc : float, optional - Fraction of proteins from composite disintegration [kg COD/kg COD]. - The default is 0.2. - f_li_xc : float, optional - Fraction of lipids from composite disintegration [kg COD/kg COD]. - The default is 0.3. - f_xI_xc : float, optional - Fraction of inert particulates from composite disintegration - [kg COD/kg COD]. The default is 0.2. f_fa_li : float, optional Fraction of long chain fatty acids (LCFAs) from hydrolysis of lipids [kg COD/kg COD]. The default is 0.95. @@ -528,8 +517,7 @@ class ADM1(CompiledProcesses): the BSM2 Framework; Lund, 2006. """ - _stoichio_params = ('f_ch_xc', 'f_pr_xc', 'f_li_xc', 'f_xI_xc', 'f_sI_xc', - 'f_fa_li', 'f_bu_su', 'f_pro_su', 'f_ac_su', 'f_h2_su', + _stoichio_params = ('f_fa_li', 'f_bu_su', 'f_pro_su', 'f_ac_su', 'f_h2_su', 'f_va_aa', 'f_bu_aa', 'f_pro_aa', 'f_ac_aa', 'f_h2_aa', 'f_ac_fa', 'f_h2_fa', 'f_pro_va', 'f_ac_va', 'f_h2_va', 'f_ac_bu', 'f_h2_bu', 'f_ac_pro', 'f_h2_pro', @@ -569,7 +557,7 @@ def __new__(cls, components=None, path=None, N_xc=2.686e-3, N_I=4.286e-3, N_aa=7 if not path: path = _path self = Processes.load_from_file(path, components=cmps, - conserved_for=('C', 'N'), + conserved_for=('C', 'N', 'P'), parameters=cls._stoichio_params, compile=False) From 4b74c73951e15876c8e1f6bdad3b57d543cf7884 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Tue, 28 Mar 2023 15:41:54 -0500 Subject: [PATCH 071/424] Added stoichiometric coefficients (P extension ADM1) Added new stoichiometric coefficients for P extension in ADM1 such that sum of all coefficients in a particular row would equal to 1. --- qsdsan/processes/_adm1_modified.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/qsdsan/processes/_adm1_modified.py b/qsdsan/processes/_adm1_modified.py index f59d5bf7..5c178e39 100644 --- a/qsdsan/processes/_adm1_modified.py +++ b/qsdsan/processes/_adm1_modified.py @@ -517,10 +517,12 @@ class ADM1(CompiledProcesses): the BSM2 Framework; Lund, 2006. """ - _stoichio_params = ('f_fa_li', 'f_bu_su', 'f_pro_su', 'f_ac_su', 'f_h2_su', + _stoichio_params = ('f_sI_xb', 'f_ch_xb', 'f_pr_xb', 'f_li_xb', 'f_xI_xb', + 'f_fa_li', 'f_bu_su', 'f_pro_su', 'f_ac_su', 'f_h2_su', 'f_va_aa', 'f_bu_aa', 'f_pro_aa', 'f_ac_aa', 'f_h2_aa', 'f_ac_fa', 'f_h2_fa', 'f_pro_va', 'f_ac_va', 'f_h2_va', 'f_ac_bu', 'f_h2_bu', 'f_ac_pro', 'f_h2_pro', + 'f_ac_PHA', 'f_bu_PHA', 'f_pro_PHA', 'f_va_PHA', 'Y_su', 'Y_aa', 'Y_fa', 'Y_c4', 'Y_pro', 'Y_ac', 'Y_h2') _kinetic_params = ('rate_constants', 'half_sat_coeffs', 'pH_ULs', 'pH_LLs', 'KS_IN', 'KI_nh3', 'KIs_h2', @@ -532,10 +534,11 @@ class ADM1(CompiledProcesses): _biogas_IDs = ('S_h2', 'S_ch4', 'S_IC') def __new__(cls, components=None, path=None, N_xc=2.686e-3, N_I=4.286e-3, N_aa=7e-3, - f_ch_xc=0.2, f_pr_xc=0.2, f_li_xc=0.3, f_xI_xc=0.2, + f_sI_xb=0, f_ch_xb=0.275, f_pr_xb=0.275, f_li_xb=0.350, f_fa_li=0.95, f_bu_su=0.13, f_pro_su=0.27, f_ac_su=0.41, f_va_aa=0.23, f_bu_aa=0.26, f_pro_aa=0.05, f_ac_aa=0.4, f_ac_fa=0.7, f_pro_va=0.54, f_ac_va=0.31, f_ac_bu=0.8, f_ac_pro=0.57, + f_ac_PHA=0.4, f_bu_PHA=0.1, f_pro_PHA=0.4, Y_su=0.1, Y_aa=0.08, Y_fa=0.06, Y_c4=0.06, Y_pro=0.04, Y_ac=0.05, Y_h2=0.06, q_dis=0.5, q_ch_hyd=10, q_pr_hyd=10, q_li_hyd=10, k_su=30, k_aa=50, k_fa=6, k_c4=20, k_pro=13, k_ac=8, k_h2=35, @@ -572,11 +575,12 @@ def __new__(cls, components=None, path=None, N_xc=2.686e-3, N_I=4.286e-3, N_aa=7 self.extend(gas_transfer) self.compile(to_class=cls) - stoichio_vals = (f_ch_xc, f_pr_xc, f_li_xc, f_xI_xc, 1-f_ch_xc-f_pr_xc-f_li_xc-f_xI_xc, + stoichio_vals = (f_sI_xb, f_ch_xb, f_pr_xb, f_li_xb, 1-f_sI_xb-f_ch_xb-f_pr_xb-f_li_xb, f_fa_li, f_bu_su, f_pro_su, f_ac_su, 1-f_bu_su-f_pro_su-f_ac_su, f_va_aa, f_bu_aa, f_pro_aa, f_ac_aa, 1-f_va_aa-f_bu_aa-f_pro_aa-f_ac_aa, f_ac_fa, 1-f_ac_fa, f_pro_va, f_ac_va, 1-f_pro_va-f_ac_va, f_ac_bu, 1-f_ac_bu, f_ac_pro, 1-f_ac_pro, + f_ac_PHA, f_bu_PHA, f_pro_PHA, 1-f_ac_PHA-f_bu_PHA-f_pro_PHA, Y_su, Y_aa, Y_fa, Y_c4, Y_pro, Y_ac, Y_h2) pH_LLs = np.array([pH_limits_aa[0]]*6 + [pH_limits_ac[0], pH_limits_h2[0]]) pH_ULs = np.array([pH_limits_aa[1]]*6 + [pH_limits_ac[1], pH_limits_h2[1]]) @@ -673,7 +677,7 @@ def set_parameters(self, **parameters): def check_stoichiometric_parameters(self): '''Check whether product COD fractions sum up to 1 for each process.''' stoichio = self.parameters - subst = ('xc', 'su', 'aa', 'fa', 'va', 'bu', 'pro') + subst = ('xb', 'su', 'aa', 'fa', 'va', 'bu', 'pro', 'PHA') for s in subst: f_tot = sum([stoichio[k] for k in self._stoichio_params[:-7] \ if k.endswith(s)]) From bb21a50967be735a93d53521690ffd42bfabfcec Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Tue, 28 Mar 2023 16:30:10 -0500 Subject: [PATCH 072/424] Updated rate constants (P extension ADM1) Removed rate constant for disintegration, and added rate constants for new processes in ADM1 --- qsdsan/processes/_adm1_modified.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/qsdsan/processes/_adm1_modified.py b/qsdsan/processes/_adm1_modified.py index 5c178e39..f43f4244 100644 --- a/qsdsan/processes/_adm1_modified.py +++ b/qsdsan/processes/_adm1_modified.py @@ -544,6 +544,7 @@ def __new__(cls, components=None, path=None, N_xc=2.686e-3, N_I=4.286e-3, N_aa=7 k_su=30, k_aa=50, k_fa=6, k_c4=20, k_pro=13, k_ac=8, k_h2=35, K_su=0.5, K_aa=0.3, K_fa=0.4, K_c4=0.2, K_pro=0.1, K_ac=0.15, K_h2=7e-6, b_su=0.02, b_aa=0.02, b_fa=0.02, b_c4=0.02, b_pro=0.02, b_ac=0.02, b_h2=0.02, + q_PHA=3, b_PAO=0.2, b_PP=0.2, b_PHA=0.2, KI_h2_fa=5e-6, KI_h2_c4=1e-5, KI_h2_pro=3.5e-6, KI_nh3=1.8e-3, KS_IN=1e-4, pH_limits_aa=(4,5.5), pH_limits_ac=(6,7), pH_limits_h2=(5,6), T_base=298.15, pKa_base=[14, 9.25, 6.35, 4.76, 4.88, 4.82, 4.86], @@ -584,9 +585,12 @@ def __new__(cls, components=None, path=None, N_xc=2.686e-3, N_I=4.286e-3, N_aa=7 Y_su, Y_aa, Y_fa, Y_c4, Y_pro, Y_ac, Y_h2) pH_LLs = np.array([pH_limits_aa[0]]*6 + [pH_limits_ac[0], pH_limits_h2[0]]) pH_ULs = np.array([pH_limits_aa[1]]*6 + [pH_limits_ac[1], pH_limits_h2[1]]) - ks = np.array((q_dis, q_ch_hyd, q_pr_hyd, q_li_hyd, + + ks = np.array((q_ch_hyd, q_pr_hyd, q_li_hyd, k_su, k_aa, k_fa, k_c4, k_c4, k_pro, k_ac, k_h2, - b_su, b_aa, b_fa, b_c4, b_pro, b_ac, b_h2)) + b_su, b_aa, b_fa, b_c4, b_pro, b_ac, b_h2, + q_PHA, q_PHA, q_PHA, q_PHA, b_PAO, b_PP, b_PHA)) + Ks = np.array((K_su, K_aa, K_fa, K_c4, K_c4, K_pro, K_ac, K_h2)) KIs_h2 = np.array((KI_h2_fa, KI_h2_c4, KI_h2_c4, KI_h2_pro)) K_H_base = np.array(K_H_base) From 1cc79959cc838e0270a137047e7e3d3d4e0eb27f Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Tue, 28 Mar 2023 16:44:05 -0500 Subject: [PATCH 073/424] Updated yield (P extension ADM 1) Added yield of biomass on phosphate. --- qsdsan/processes/_adm1_modified.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qsdsan/processes/_adm1_modified.py b/qsdsan/processes/_adm1_modified.py index f43f4244..3d296f6b 100644 --- a/qsdsan/processes/_adm1_modified.py +++ b/qsdsan/processes/_adm1_modified.py @@ -523,7 +523,7 @@ class ADM1(CompiledProcesses): 'f_ac_fa', 'f_h2_fa', 'f_pro_va', 'f_ac_va', 'f_h2_va', 'f_ac_bu', 'f_h2_bu', 'f_ac_pro', 'f_h2_pro', 'f_ac_PHA', 'f_bu_PHA', 'f_pro_PHA', 'f_va_PHA', - 'Y_su', 'Y_aa', 'Y_fa', 'Y_c4', 'Y_pro', 'Y_ac', 'Y_h2') + 'Y_su', 'Y_aa', 'Y_fa', 'Y_c4', 'Y_pro', 'Y_ac', 'Y_h2', 'Y_po4') _kinetic_params = ('rate_constants', 'half_sat_coeffs', 'pH_ULs', 'pH_LLs', 'KS_IN', 'KI_nh3', 'KIs_h2', 'Ka_base', 'Ka_dH', 'K_H_base', 'K_H_dH', 'kLa', @@ -539,7 +539,7 @@ def __new__(cls, components=None, path=None, N_xc=2.686e-3, N_I=4.286e-3, N_aa=7 f_va_aa=0.23, f_bu_aa=0.26, f_pro_aa=0.05, f_ac_aa=0.4, f_ac_fa=0.7, f_pro_va=0.54, f_ac_va=0.31, f_ac_bu=0.8, f_ac_pro=0.57, f_ac_PHA=0.4, f_bu_PHA=0.1, f_pro_PHA=0.4, - Y_su=0.1, Y_aa=0.08, Y_fa=0.06, Y_c4=0.06, Y_pro=0.04, Y_ac=0.05, Y_h2=0.06, + Y_su=0.1, Y_aa=0.08, Y_fa=0.06, Y_c4=0.06, Y_pro=0.04, Y_ac=0.05, Y_h2=0.06, Y_po4=0.013, q_dis=0.5, q_ch_hyd=10, q_pr_hyd=10, q_li_hyd=10, k_su=30, k_aa=50, k_fa=6, k_c4=20, k_pro=13, k_ac=8, k_h2=35, K_su=0.5, K_aa=0.3, K_fa=0.4, K_c4=0.2, K_pro=0.1, K_ac=0.15, K_h2=7e-6, @@ -582,7 +582,7 @@ def __new__(cls, components=None, path=None, N_xc=2.686e-3, N_I=4.286e-3, N_aa=7 f_ac_fa, 1-f_ac_fa, f_pro_va, f_ac_va, 1-f_pro_va-f_ac_va, f_ac_bu, 1-f_ac_bu, f_ac_pro, 1-f_ac_pro, f_ac_PHA, f_bu_PHA, f_pro_PHA, 1-f_ac_PHA-f_bu_PHA-f_pro_PHA, - Y_su, Y_aa, Y_fa, Y_c4, Y_pro, Y_ac, Y_h2) + Y_su, Y_aa, Y_fa, Y_c4, Y_pro, Y_ac, Y_h2, Y_po4) pH_LLs = np.array([pH_limits_aa[0]]*6 + [pH_limits_ac[0], pH_limits_h2[0]]) pH_ULs = np.array([pH_limits_aa[1]]*6 + [pH_limits_ac[1], pH_limits_h2[1]]) From 940add6269b7cd8663f1b353e8063f52b956eb92 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Wed, 29 Mar 2023 09:43:39 -0500 Subject: [PATCH 074/424] Documentation (P extension ADM 1) Added documentation for new parameters (which are added as of this comment) in modified ADM1. --- qsdsan/processes/_adm1_modified.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/qsdsan/processes/_adm1_modified.py b/qsdsan/processes/_adm1_modified.py index 3d296f6b..a8e1fd37 100644 --- a/qsdsan/processes/_adm1_modified.py +++ b/qsdsan/processes/_adm1_modified.py @@ -350,6 +350,16 @@ class ADM1(CompiledProcesses): Nitrogen content of inert organics [kmol N/kg COD]. The default is 4.286e-3. N_aa : float, optional Nitrogen content of amino acids [kmol N/kg COD]. The default is 7e-3. + f_sI_xb : float, optional + fraction of soluble inerts from biomass. The default is 0. + f_ch_xb : float, optional + fraction of carbohydrates from biomass. The default is 0.275. + f_pr_xb : float, optional + fraction of proteins from biomass. The default is 0.275. + f_li_xb : float, optional + fraction of lipids from biomass. The default is 0.35. + f_xI_xb : float, optional + fraction of particulate inerts from biomass. The default is 0.1. f_fa_li : float, optional Fraction of long chain fatty acids (LCFAs) from hydrolysis of lipids [kg COD/kg COD]. The default is 0.95. @@ -377,6 +387,14 @@ class ADM1(CompiledProcesses): Fraction of acetate from butyrate [kg COD/kg COD]. The default is 0.8. f_ac_pro : float, optional Fraction of acetate from propionate [kg COD/kg COD]. The default is 0.57. + f_ac_PHA : float, optional + Yield of acetate on PHA [kg COD/kg COD]. The default is 0.4. + f_bu_PHA : float, optional + Yield of butyrate on PHA [kg COD/kg COD]. The default is 0.1. + f_pro_PHA : float, optional + Yield of propionate on PHA [kg COD/kg COD]. The default is 0.4. + f_va_PHA : float, optional + Yield of valerate on PHA [kg COD/kg COD]. The default is 0.1. Y_su : float, optional Biomass yield of sugar uptake [kg COD/kg COD]. The default is 0.1. Y_aa : float, optional @@ -455,6 +473,14 @@ class ADM1(CompiledProcesses): The default is 0.02. b_h2 : float, optional Decay rate constant of H2-uptaking biomass [d^(-1)]. The default is 0.02. + q_PHA : float, optional + Rate constant for storage of PHA [d^(-1)]. The default is 3. + b_PAO : float, optional + Lysis rate of PAOs [d^(-1)]. The default is 0.2. + b_PP : float, optional + Lysis rate of polyphosphates [d^(-1)]. The default is 0.2. + b_PHA : float, optional + Lysis rate of PHAs [d^(-1)]. The default is 0.2. KI_h2_fa : float, optional H2 inhibition coefficient for LCFA uptake [kg COD/m3]. The default is 5e-6. KI_h2_c4 : float, optional @@ -564,6 +590,8 @@ def __new__(cls, components=None, path=None, N_xc=2.686e-3, N_I=4.286e-3, N_aa=7 conserved_for=('C', 'N', 'P'), parameters=cls._stoichio_params, compile=False) + + # Need to add 5 processes separately for stoichiometry. See ASM2d for reference. gas_transfer = [] for i in cls._biogas_IDs: From 024f827609e8c56e93b69624f4f43df8dfd7cb30 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Wed, 29 Mar 2023 09:59:46 -0500 Subject: [PATCH 075/424] Documentation (P extension ADM1) Added documentation for Y_PO4 (yield of biomass on phosphate) in modified ADM1. --- qsdsan/processes/_adm1_modified.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/qsdsan/processes/_adm1_modified.py b/qsdsan/processes/_adm1_modified.py index a8e1fd37..acb372a5 100644 --- a/qsdsan/processes/_adm1_modified.py +++ b/qsdsan/processes/_adm1_modified.py @@ -410,6 +410,8 @@ class ADM1(CompiledProcesses): Biomass yield of acetate uptake [kg COD/kg COD]. The default is 0.05. Y_h2 : float, optional Biomass yield of H2 uptake [kg COD/kg COD]. The default is 0.06. + Y_po4 : float, optional + Yield of biomass on phosphate [kmol P/kg COD]. The default is 0.013. q_dis : float, optional Composites disintegration rate constant [d^(-1)]. The default is 0.5. q_ch_hyd : float, optional From 9f2321b4383a1d8502a2c9c2b96b927c5423fc6e Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Wed, 29 Mar 2023 13:43:50 -0500 Subject: [PATCH 076/424] New Processes (P extension ADM1) Added new processes accounting for P extension --- qsdsan/processes/_adm1_modified.py | 66 ++++++++++++++++++++++++++++-- 1 file changed, 62 insertions(+), 4 deletions(-) diff --git a/qsdsan/processes/_adm1_modified.py b/qsdsan/processes/_adm1_modified.py index acb372a5..5011f7e0 100644 --- a/qsdsan/processes/_adm1_modified.py +++ b/qsdsan/processes/_adm1_modified.py @@ -283,6 +283,7 @@ def rhos_adm1(state_arr, params): # 'S_bu', 'S_pro', 'S_ac', 'S_h2']) # substrates = state_arr[substrates_ids] substrates = state_arr[:8] + # S_va, S_bu, S_h2, S_IN = state_arr[cmps.indices(['S_va', 'S_bu', 'S_h2', 'S_IN'])] # S_va, S_bu, S_h2, S_ch4, S_IC, S_IN = state_arr[[3,4,7,8,9,10]] S_va, S_bu, S_h2, S_IN = state_arr[[3,4,7,10]] @@ -300,6 +301,14 @@ def rhos_adm1(state_arr, params): rhos[4:12] *= substr_inhibit(substrates, Ks) if S_va > 0: rhos[7] *= 1/(1+S_bu/S_va) if S_bu > 0: rhos[8] *= 1/(1+S_va/S_bu) + + + substartes_modified = state_arr[:] + + substrates_ids = cmps.indices(['S_va', 'S_bu', 'S_pro', 'S_ac']) + substrates_modified = state_arr[substrates_ids] + Ks_modified = Ks[-4:] + rhos[18:21] *= substr_inhibit(substrates_modified, Ks_modified) h = brenth(acid_base_rxn, 1e-14, 1.0, args=(weak_acids, Kas), @@ -455,6 +464,12 @@ class ADM1(CompiledProcesses): K_h2 : float, optional Half saturation coefficient of H2 uptake [kg COD/m3]. The default is 7e-6. + K_a : float, optional + Saturation coefficient for acetate [kg COD/m3]. + The default is 0.004. + K_pp : float, optional + Saturation coefficient for polyphosphate [kg COD/m3]. + The default is 0.00032. b_su : float, optional Decay rate constant of sugar-uptaking biomass [d^(-1)]. The default is 0.02. @@ -533,7 +548,7 @@ class ADM1(CompiledProcesses): >>> cmps = pc.create_adm1_cmps() >>> adm1 = pc.ADM1() >>> adm1.show() - ADM1([disintegration, hydrolysis_carbs, hydrolysis_proteins, hydrolysis_lipids, uptake_sugars, uptake_amino_acids, uptake_LCFA, uptake_valerate, uptake_butyrate, uptake_propionate, uptake_acetate, uptake_h2, decay_Xsu, decay_Xaa, decay_Xfa, decay_Xc4, decay_Xpro, decay_Xac, decay_Xh2, h2_transfer, ch4_transfer, IC_transfer]) + ADM1([hydrolysis_carbs, hydrolysis_proteins, hydrolysis_lipids, uptake_sugars, uptake_amino_acids, uptake_LCFA, uptake_valerate, uptake_butyrate, uptake_propionate, uptake_acetate, uptake_h2, decay_Xsu, decay_Xaa, decay_Xfa, decay_Xc4, decay_Xpro, decay_Xac, decay_Xh2, storage_Sva_in_XPHA, storage_Sbu_in_XPHA, storage_Spro_in_XPHA, storage_Sac_in_XPHA, lysis_XPAO, lysis_XPP, lysis_XPHA, h2_transfer, ch4_transfer, IC_transfer]) References ---------- @@ -559,6 +574,7 @@ class ADM1(CompiledProcesses): _acid_base_pairs = (('H+', 'OH-'), ('NH4+', 'NH3'), ('CO2', 'HCO3-'), ('HAc', 'Ac-'), ('HPr', 'Pr-'), ('HBu', 'Bu-'), ('HVa', 'Va-')) + _biogas_IDs = ('S_h2', 'S_ch4', 'S_IC') def __new__(cls, components=None, path=None, N_xc=2.686e-3, N_I=4.286e-3, N_aa=7e-3, @@ -570,7 +586,7 @@ def __new__(cls, components=None, path=None, N_xc=2.686e-3, N_I=4.286e-3, N_aa=7 Y_su=0.1, Y_aa=0.08, Y_fa=0.06, Y_c4=0.06, Y_pro=0.04, Y_ac=0.05, Y_h2=0.06, Y_po4=0.013, q_dis=0.5, q_ch_hyd=10, q_pr_hyd=10, q_li_hyd=10, k_su=30, k_aa=50, k_fa=6, k_c4=20, k_pro=13, k_ac=8, k_h2=35, - K_su=0.5, K_aa=0.3, K_fa=0.4, K_c4=0.2, K_pro=0.1, K_ac=0.15, K_h2=7e-6, + K_su=0.5, K_aa=0.3, K_fa=0.4, K_c4=0.2, K_pro=0.1, K_ac=0.15, K_h2=7e-6, K_a=4e-3, K_pp=32e-5, b_su=0.02, b_aa=0.02, b_fa=0.02, b_c4=0.02, b_pro=0.02, b_ac=0.02, b_h2=0.02, q_PHA=3, b_PAO=0.2, b_PP=0.2, b_PHA=0.2, KI_h2_fa=5e-6, KI_h2_c4=1e-5, KI_h2_pro=3.5e-6, KI_nh3=1.8e-3, KS_IN=1e-4, @@ -594,7 +610,49 @@ def __new__(cls, components=None, path=None, N_xc=2.686e-3, N_I=4.286e-3, N_aa=7 compile=False) # Need to add 5 processes separately for stoichiometry. See ASM2d for reference. - + if path == _path: + _p19 = Process('storage_Sva_in_XPHA', + 'S_va + [Y_po4]X_PP -> X_PHA + [?]S_K + [?]S_Mg', + components=cmps, + ref_component='X_PHA', + rate_equation='q_PHA * S_va/(K_a+S_va) * (X_PP/X_PAO)/(K_PP+(X_PP/X_PAO)) * X_PAO * S_va/(S_va+S_bu+S_pro+S_ac)', + parameters=('Y_po4', 'q_PHA', 'K_a', 'K_PP'), + conserved_for=('K', 'Mg')) + + _p20 = Process('storage_Sbu_in_XPHA', + 'S_bu + [Y_po4]X_PP -> X_PHA + [?]S_K + [?]S_Mg', + components=cmps, + ref_component='X_PHA', + rate_equation='q_PHA * S_bu/(K_a+S_bu) * (X_PP/X_PAO)/(K_PP+(X_PP/X_PAO)) * X_PAO * S_bu/(S_va+S_bu+S_pro+S_ac)', + parameters=('Y_po4', 'q_PHA', 'K_a', 'K_PP'), + conserved_for=('K', 'Mg')) + + _p21 = Process('storage_Spro_in_XPHA', + 'S_pro + [Y_po4]X_PP -> X_PHA + [?]S_K + [?]S_Mg', + components=cmps, + ref_component='X_PHA', + rate_equation='q_PHA * S_pro/(K_a+S_pro) * (X_PP/X_PAO)/(K_PP+(X_PP/X_PAO)) * X_PAO * S_pro/(S_va+S_bu+S_pro+S_ac)', + parameters=('Y_po4', 'q_PHA', 'K_a', 'K_PP'), + conserved_for=('K', 'Mg')) + + _p22 = Process('storage_Sac_in_XPHA', + 'S_ac + [Y_po4]X_PP -> X_PHA + [?]S_K + [?]S_Mg', + components=cmps, + ref_component='X_PHA', + rate_equation='q_PHA * S_ac/(K_a+S_ac) * (X_PP/X_PAO)/(K_PP+(X_PP/X_PAO)) * X_PAO * S_ac/(S_va+S_bu+S_pro+S_ac)', + parameters=('Y_po4', 'q_PHA', 'K_a', 'K_PP'), + conserved_for=('K', 'Mg')) + + _p24 = Process('lysis_XPP', + 'X_pp -> [?]S_K + [?]S_Mg', + components=cmps, + ref_component='X_PP', + rate_equation='b_PP*X_PP', + parameters=('b_PP'), + conserved_for=('K', 'Mg')) + + self.extend([_p19, _p20, _p21, _p22, _p24]) + gas_transfer = [] for i in cls._biogas_IDs: new_p = Process('%s_transfer' % i.lstrip('S_'), @@ -621,7 +679,7 @@ def __new__(cls, components=None, path=None, N_xc=2.686e-3, N_I=4.286e-3, N_aa=7 b_su, b_aa, b_fa, b_c4, b_pro, b_ac, b_h2, q_PHA, q_PHA, q_PHA, q_PHA, b_PAO, b_PP, b_PHA)) - Ks = np.array((K_su, K_aa, K_fa, K_c4, K_c4, K_pro, K_ac, K_h2)) + Ks = np.array((K_su, K_aa, K_fa, K_c4, K_c4, K_pro, K_ac, K_h2, K_a, K_pp)) KIs_h2 = np.array((KI_h2_fa, KI_h2_c4, KI_h2_c4, KI_h2_pro)) K_H_base = np.array(K_H_base) K_H_dH = np.array(K_H_dH) From 5604879e843e60fe460b3f93602912352e95eb97 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Wed, 29 Mar 2023 14:38:42 -0500 Subject: [PATCH 077/424] Process Transformations (P extension ADM1) Transformations for added processes in modified ADM1 (Table 2.2, Appendix https://doi.org/10.1016/j.watres.2016.03.012) --- qsdsan/processes/_adm1_modified.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/qsdsan/processes/_adm1_modified.py b/qsdsan/processes/_adm1_modified.py index 5011f7e0..e016b20b 100644 --- a/qsdsan/processes/_adm1_modified.py +++ b/qsdsan/processes/_adm1_modified.py @@ -302,14 +302,23 @@ def rhos_adm1(state_arr, params): if S_va > 0: rhos[7] *= 1/(1+S_bu/S_va) if S_bu > 0: rhos[8] *= 1/(1+S_va/S_bu) + # substrates_ids = cmps.indices(['S_va', 'S_bu', 'S_pro', 'S_ac']) + # substrates_modified = state_arr[substrates_ids] + substrates_modified = state_arr[3:7] + # Ka = K_a + Ka = Ks[-2] + rhos[18:22] *= substr_inhibit(substrates_modified, Ka) - substartes_modified = state_arr[:] + # PP_PAO = X_PP/X_PAO + PP_PAO = state_arr[25]/state_arr[26] + # Kpp = K_pp + Kpp = Ks[-1] + rhos[18:22] *= substr_inhibit(PP_PAO, Kpp) + + # Multiplication by {Sva, Sbu, Spro, Sac}/(Sva + Sbu + Spro + Sac) + transformation_array = state_arr[3:7]/sum(state_arr[3:7]) + rhos[18:22] *= transformation_array - substrates_ids = cmps.indices(['S_va', 'S_bu', 'S_pro', 'S_ac']) - substrates_modified = state_arr[substrates_ids] - Ks_modified = Ks[-4:] - rhos[18:21] *= substr_inhibit(substrates_modified, Ks_modified) - h = brenth(acid_base_rxn, 1e-14, 1.0, args=(weak_acids, Kas), xtol=1e-12, maxiter=100) From 1cb317c933f9c629bd6fa4cd74c17ead81bc4963 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Wed, 29 Mar 2023 15:34:52 -0500 Subject: [PATCH 078/424] Redefined the Cs array (P extension ADM1) Redefined the Cs array to account for changes in ADM1 --- qsdsan/processes/_adm1_modified.py | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/qsdsan/processes/_adm1_modified.py b/qsdsan/processes/_adm1_modified.py index e016b20b..0abb1b51 100644 --- a/qsdsan/processes/_adm1_modified.py +++ b/qsdsan/processes/_adm1_modified.py @@ -271,14 +271,24 @@ def rhos_adm1(state_arr, params): kLa = params['kLa'] T_base = params['T_base'] root = params['root'] - - # Cs_ids = cmps.indices(['X_c', 'X_ch', 'X_pr', 'X_li', 'X_su', 'X_aa', + + # state_arr_cmps stated just for readability of code + # state_arr_cmps = [S_su, S_aa, S_fa, S_va, S_bu, S_pro, S_ac, S_h2, S_ch4, S_IC, S_IN, + # S_IP, S_I, X_ch, X_pr, X_li, X_su, X_aa, X_fa, X_c4, X_pro, X_ac, + # X_h2, X_I, X_PHA, X_PP, X_PAO, S_K, S_Mg] + + # Cs_ids = cmps.indices(['X_ch', 'X_pr', 'X_li', 'X_su', 'X_aa', # 'X_fa', 'X_c4', 'X_c4', 'X_pro', 'X_ac', 'X_h2', - # 'X_su', 'X_aa', 'X_fa', 'X_c4', 'X_pro', 'X_ac', 'X_h2']) + # 'X_su', 'X_aa', 'X_fa', 'X_c4', 'X_pro', 'X_ac', 'X_h2', + # 'X_PAO', 'X_PAO', 'X_PAO', 'X_PAO', 'X_PAO', 'X_PP', 'X_PHA']) # Cs = state_arr[Cs_ids] - Cs[:8] = state_arr[12:20] - Cs[8:12] = state_arr[19:23] - Cs[12:] = state_arr[16:23] + Cs[:7] = state_arr[13:20] + Cs[7:11] = state_arr[19:23] + Cs[11:18] = state_arr[16:23] + Cs[18:23] = state_arr[26] + Cs[23] = state_arr[25] + Cs[24] = state_arr[24] + # substrates_ids = cmps.indices(['S_su', 'S_aa', 'S_fa', 'S_va', # 'S_bu', 'S_pro', 'S_ac', 'S_h2']) # substrates = state_arr[substrates_ids] @@ -659,8 +669,12 @@ def __new__(cls, components=None, path=None, N_xc=2.686e-3, N_I=4.286e-3, N_aa=7 rate_equation='b_PP*X_PP', parameters=('b_PP'), conserved_for=('K', 'Mg')) - - self.extend([_p19, _p20, _p21, _p22, _p24]) + + self.insert(18, _p19) + self.insert(19, _p20) + self.insert(20, _p21) + self.insert(21, _p22) + self.insert(23, _p24) gas_transfer = [] for i in cls._biogas_IDs: From 2cd7fd588b19925b4cfb011f86691fe52f6523f5 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Wed, 29 Mar 2023 15:57:25 -0500 Subject: [PATCH 079/424] Changed indices (P extension ADM1) Changed the sizes and positions of the process transformation matrix to account for ADM1 modifications --- qsdsan/processes/_adm1_modified.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/qsdsan/processes/_adm1_modified.py b/qsdsan/processes/_adm1_modified.py index 0abb1b51..43e381c6 100644 --- a/qsdsan/processes/_adm1_modified.py +++ b/qsdsan/processes/_adm1_modified.py @@ -251,8 +251,8 @@ def Hill_inhibit(H_ion, ul, ll): K = 10**(-(ul+ll)/2) return 1/(1+(H_ion/K) ** n) -rhos = np.zeros(22) # 22 kinetic processes -Cs = np.empty(19) +rhos = np.zeros(28) # 28 kinetic processes (25 as defined in modified ADM1 + 3 for gases) +Cs = np.empty(25) # 25 processes as defined in modified ADM1 def rhos_adm1(state_arr, params): ks = params['rate_constants'] @@ -308,9 +308,9 @@ def rhos_adm1(state_arr, params): KH = KHb * T_correction_factor(T_base, T_op, KH_dH) / unit_conversion[7:10] rhos[:-3] = ks * Cs - rhos[4:12] *= substr_inhibit(substrates, Ks) - if S_va > 0: rhos[7] *= 1/(1+S_bu/S_va) - if S_bu > 0: rhos[8] *= 1/(1+S_va/S_bu) + rhos[3:11] *= substr_inhibit(substrates, Ks) + if S_va > 0: rhos[6] *= 1/(1+S_bu/S_va) + if S_bu > 0: rhos[7] *= 1/(1+S_va/S_bu) # substrates_ids = cmps.indices(['S_va', 'S_bu', 'S_pro', 'S_ac']) # substrates_modified = state_arr[substrates_ids] @@ -340,11 +340,11 @@ def rhos_adm1(state_arr, params): Iph = Hill_inhibit(h, pH_ULs, pH_LLs) Ih2 = non_compet_inhibit(S_h2, KIs_h2) root.data = [-np.log10(h), Iph, Ih2] - rhos[4:12] *= Iph * substr_inhibit(S_IN, KS_IN) - rhos[6:10] *= Ih2 + rhos[3:11] *= Iph * substr_inhibit(S_IN, KS_IN) + rhos[5:9] *= Ih2 # rhos[4:12] *= Hill_inhibit(h, pH_ULs, pH_LLs) * substr_inhibit(S_IN, KS_IN) # rhos[6:10] *= non_compet_inhibit(S_h2, KIs_h2) - rhos[10] *= non_compet_inhibit(nh3, KI_nh3) + rhos[9] *= non_compet_inhibit(nh3, KI_nh3) rhos[-3:] = kLa * (biogas_S - KH * biogas_p) # print(rhos) return rhos From 4cdeced2943ecb2136ca17e79091f851ee9b9d37 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Wed, 29 Mar 2023 17:20:53 -0500 Subject: [PATCH 080/424] Process Inhibition (P extension ADM1) Added Inhibition due to limiting Phosphorus --- qsdsan/processes/_adm1_modified.py | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/qsdsan/processes/_adm1_modified.py b/qsdsan/processes/_adm1_modified.py index 43e381c6..b9ebf6f8 100644 --- a/qsdsan/processes/_adm1_modified.py +++ b/qsdsan/processes/_adm1_modified.py @@ -39,6 +39,7 @@ C_mw = get_mw({'C':1}) N_mw = get_mw({'N':1}) +P_mw = get_mw({'P':1}) def create_adm1_cmps(set_thermo=True): cmps_all = Components.load_default() @@ -220,6 +221,7 @@ def T_correction_factor(T1, T2, delta_H): # pKas = np.asarray(pKas) # return 10**(-pKas) * T_correction_factor(T_base, T_op, theta) +# What about this? Any changes needed? (Saumitra) def acid_base_rxn(h_ion, weak_acids_tot, Kas): # h, nh4, hco3, ac, pr, bu, va = mols # S_cat, S_an, S_IN, S_IC, S_ac, S_pro, S_bu, S_va = weak_acids_tot # in M @@ -262,6 +264,7 @@ def rhos_adm1(state_arr, params): pH_ULs = params['pH_ULs'] pH_LLs = params['pH_LLs'] KS_IN = params['KS_IN'] + KS_IP = params['KS_IP'] KI_nh3 = params['KI_nh3'] KIs_h2 = params['KIs_h2'] KHb = params['K_H_base'] @@ -296,7 +299,7 @@ def rhos_adm1(state_arr, params): # S_va, S_bu, S_h2, S_IN = state_arr[cmps.indices(['S_va', 'S_bu', 'S_h2', 'S_IN'])] # S_va, S_bu, S_h2, S_ch4, S_IC, S_IN = state_arr[[3,4,7,8,9,10]] - S_va, S_bu, S_h2, S_IN = state_arr[[3,4,7,10]] + S_va, S_bu, S_h2, S_IN, S_IP = state_arr[[3,4,7,10,11]] unit_conversion = mass2mol_conversion(cmps) cmps_in_M = state_arr[:27] * unit_conversion weak_acids = cmps_in_M[[24, 25, 10, 9, 6, 5, 4, 3]] @@ -340,15 +343,17 @@ def rhos_adm1(state_arr, params): Iph = Hill_inhibit(h, pH_ULs, pH_LLs) Ih2 = non_compet_inhibit(S_h2, KIs_h2) root.data = [-np.log10(h), Iph, Ih2] - rhos[3:11] *= Iph * substr_inhibit(S_IN, KS_IN) + rhos[3:11] *= Iph * substr_inhibit(S_IN, KS_IN) * substr_inhibit(S_IP, KS_IP) rhos[5:9] *= Ih2 # rhos[4:12] *= Hill_inhibit(h, pH_ULs, pH_LLs) * substr_inhibit(S_IN, KS_IN) # rhos[6:10] *= non_compet_inhibit(S_h2, KIs_h2) rhos[9] *= non_compet_inhibit(nh3, KI_nh3) + + + rhos[-3:] = kLa * (biogas_S - KH * biogas_p) # print(rhos) return rhos - #%% # ============================================================================= # ADM1 class @@ -531,6 +536,9 @@ class ADM1(CompiledProcesses): KS_IN : float, optional Inorganic nitrogen (nutrient) inhibition coefficient for soluble substrate uptake [M]. The default is 1e-4. + KS_IP : float, optional + P limitation for inorganic phosphorous [kmol P/m3]. + The default is 2e-5. pH_limits_aa : 2-tuple, optional Lower and upper limits of pH inhibition for acidogens and acetogens, unitless. The default is (4,5.5). @@ -587,7 +595,7 @@ class ADM1(CompiledProcesses): 'f_ac_PHA', 'f_bu_PHA', 'f_pro_PHA', 'f_va_PHA', 'Y_su', 'Y_aa', 'Y_fa', 'Y_c4', 'Y_pro', 'Y_ac', 'Y_h2', 'Y_po4') _kinetic_params = ('rate_constants', 'half_sat_coeffs', 'pH_ULs', 'pH_LLs', - 'KS_IN', 'KI_nh3', 'KIs_h2', + 'KS_IN', 'KS_IP', 'KI_nh3', 'KIs_h2', 'Ka_base', 'Ka_dH', 'K_H_base', 'K_H_dH', 'kLa', 'T_base', 'components', 'root') _acid_base_pairs = (('H+', 'OH-'), ('NH4+', 'NH3'), ('CO2', 'HCO3-'), @@ -608,7 +616,7 @@ def __new__(cls, components=None, path=None, N_xc=2.686e-3, N_I=4.286e-3, N_aa=7 K_su=0.5, K_aa=0.3, K_fa=0.4, K_c4=0.2, K_pro=0.1, K_ac=0.15, K_h2=7e-6, K_a=4e-3, K_pp=32e-5, b_su=0.02, b_aa=0.02, b_fa=0.02, b_c4=0.02, b_pro=0.02, b_ac=0.02, b_h2=0.02, q_PHA=3, b_PAO=0.2, b_PP=0.2, b_PHA=0.2, - KI_h2_fa=5e-6, KI_h2_c4=1e-5, KI_h2_pro=3.5e-6, KI_nh3=1.8e-3, KS_IN=1e-4, + KI_h2_fa=5e-6, KI_h2_c4=1e-5, KI_h2_pro=3.5e-6, KI_nh3=1.8e-3, KS_IN=1e-4, KS_IP=2e-5, pH_limits_aa=(4,5.5), pH_limits_ac=(6,7), pH_limits_h2=(5,6), T_base=298.15, pKa_base=[14, 9.25, 6.35, 4.76, 4.88, 4.82, 4.86], Ka_dH=[55900, 51965, 7646, 0, 0, 0, 0], @@ -617,6 +625,7 @@ def __new__(cls, components=None, path=None, N_xc=2.686e-3, N_I=4.286e-3, N_aa=7 **kwargs): cmps = _load_components(components) + # Sure that some things are missing here! (Saumitra) cmps.X_c.i_N = N_xc * N_mw cmps.X_I.i_N = cmps.S_I.i_N = N_I * N_mw cmps.S_aa.i_N = cmps.X_pr.i_N = N_aa * N_mw @@ -716,7 +725,7 @@ def __new__(cls, components=None, path=None, N_xc=2.686e-3, N_I=4.286e-3, N_aa=7 self.set_rate_function(rhos_adm1) dct['_parameters'] = dict(zip(cls._stoichio_params, stoichio_vals)) self.rate_function._params = dict(zip(cls._kinetic_params, - [ks, Ks, pH_ULs, pH_LLs, KS_IN*N_mw, + [ks, Ks, pH_ULs, pH_LLs, KS_IN*N_mw, KS_IP*P_mw, KI_nh3, KIs_h2, Ka_base, Ka_dH, K_H_base, K_H_dH, kLa, T_base, self._components, root])) @@ -770,6 +779,11 @@ def set_KS_IN(self, K): '''Set inhibition coefficient for inorganic nitrogen as a secondary substrate [M nitrogen].''' self.rate_function._params['KS_IN'] = K * N_mw + + def set_KS_IP(self, K): + '''Set inhibition coefficient for inorganic phosphorous as a secondary + substrate [M phosphorous].''' + self.rate_function._params['KS_IP'] = K * P_mw def set_KI_nh3(self, K): '''Set inhibition coefficient for free ammonia [M].''' From 05db6c5ba23a1a03a64d6d5499e1f57521e179ce Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Wed, 29 Mar 2023 17:28:20 -0500 Subject: [PATCH 081/424] Reference for modified ADM1 Added reference for modified ADM1 --- qsdsan/processes/_adm1_modified.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/qsdsan/processes/_adm1_modified.py b/qsdsan/processes/_adm1_modified.py index b9ebf6f8..205c346c 100644 --- a/qsdsan/processes/_adm1_modified.py +++ b/qsdsan/processes/_adm1_modified.py @@ -368,7 +368,7 @@ def __init__(self): @chemicals_user class ADM1(CompiledProcesses): """ - Anaerobic Digestion Model No.1. [1]_, [2]_ + Anaerobic Digestion Model No.1. [1]_, [2]_, [3]_ Parameters ---------- @@ -585,6 +585,11 @@ class ADM1(CompiledProcesses): Water Sci. Technol. 2002, 45 (10), 65–73. .. [2] Rosen, C.; Jeppsson, U. Aspects on ADM1 Implementation within the BSM2 Framework; Lund, 2006. + .. [3] Flores-Alsina, X.; Solon, K.; Kazadi Mbamba, C.; Tait, S.; + Gernaey, K. V.; Jeppsson, U.; Batstone, D. J. + Modelling phosphorus (P), sulfur (S) and iron (FE) interactions for + dynamic simulations of anaerobic digestion processes. Water Research. 2016, + 95, 370–382. """ _stoichio_params = ('f_sI_xb', 'f_ch_xb', 'f_pr_xb', 'f_li_xb', 'f_xI_xb', From afb0bd6d31630cea510f1a02ee951f856af36718 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Thu, 30 Mar 2023 14:36:11 -0500 Subject: [PATCH 082/424] Updated names (P extension ADM1) Replaced 'adm1' with 'adm1_p_extension' in all classes and functions --- qsdsan/processes/_adm1_modified.py | 33 +++++++++++++++--------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/qsdsan/processes/_adm1_modified.py b/qsdsan/processes/_adm1_modified.py index 205c346c..a93dd1c1 100644 --- a/qsdsan/processes/_adm1_modified.py +++ b/qsdsan/processes/_adm1_modified.py @@ -4,6 +4,7 @@ This module is developed by: Joy Zhang + Saumitra Rai Part of this module is based on the Thermosteam package: @@ -23,25 +24,25 @@ from scipy.optimize import brenth from warnings import warn -__all__ = ('create_adm1_cmps', 'ADM1', +__all__ = ('create_adm1_p_extension_cmps', 'ADM1_p_extension', 'non_compet_inhibit', 'substr_inhibit', 'T_correction_factor', 'pH_inhibit', 'Hill_inhibit', - 'rhos_adm1') + 'rhos_adm1_p_extension') -_path = ospath.join(data_path, 'process_data/_adm1.tsv') +_path = ospath.join(data_path, 'process_data/_adm1_p_extension.tsv') _load_components = settings.get_default_chemicals #%% # ============================================================================= -# ADM1-specific components +# ADM1 (with P extension) -specific components # ============================================================================= C_mw = get_mw({'C':1}) N_mw = get_mw({'N':1}) P_mw = get_mw({'P':1}) -def create_adm1_cmps(set_thermo=True): +def create_adm1_p_extension_cmps(set_thermo=True): cmps_all = Components.load_default() # varies @@ -182,16 +183,16 @@ def create_adm1_cmps(set_thermo=True): S_an = cmps_all.S_AN.copy('S_an') S_cat.i_mass = S_an.i_mass = 1 - cmps_adm1 = Components([S_su, S_aa, S_fa, S_va, S_bu, S_pro, S_ac, S_h2, + cmps_adm1_p_extension = Components([S_su, S_aa, S_fa, S_va, S_bu, S_pro, S_ac, S_h2, S_ch4, S_IC, S_IN, S_IP, S_I, X_ch, X_pr, X_li, X_su, X_aa, X_fa, X_c4, X_pro, X_ac, X_h2, X_I, X_PHA, X_PP, X_PAO, S_K, S_Mg, S_cat, S_an, cmps_all.H2O]) - cmps_adm1.default_compile() - if set_thermo: settings.set_thermo(cmps_adm1) - return cmps_adm1 + cmps_adm1_p_extension.default_compile() + if set_thermo: settings.set_thermo(cmps_adm1_p_extension) + return cmps_adm1_p_extension -# create_adm1_cmps() +# create_adm1_p_extension_cmps() #%% @@ -256,7 +257,7 @@ def Hill_inhibit(H_ion, ul, ll): rhos = np.zeros(28) # 28 kinetic processes (25 as defined in modified ADM1 + 3 for gases) Cs = np.empty(25) # 25 processes as defined in modified ADM1 -def rhos_adm1(state_arr, params): +def rhos_adm1_p_extension(state_arr, params): ks = params['rate_constants'] Ks = params['half_sat_coeffs'] cmps = params['components'] @@ -356,7 +357,7 @@ def rhos_adm1(state_arr, params): return rhos #%% # ============================================================================= -# ADM1 class +# ADM1_p_extension class # ============================================================================= class TempState: def __init__(self): @@ -366,7 +367,7 @@ def __init__(self): # self.data += [value] @chemicals_user -class ADM1(CompiledProcesses): +class ADM1_p_extension(CompiledProcesses): """ Anaerobic Digestion Model No.1. [1]_, [2]_, [3]_ @@ -572,8 +573,8 @@ class ADM1(CompiledProcesses): Examples -------- >>> from qsdsan import processes as pc - >>> cmps = pc.create_adm1_cmps() - >>> adm1 = pc.ADM1() + >>> cmps = pc.create_adm1_p_extension_cmps() + >>> adm1 = pc.ADM1_p_extension() >>> adm1.show() ADM1([hydrolysis_carbs, hydrolysis_proteins, hydrolysis_lipids, uptake_sugars, uptake_amino_acids, uptake_LCFA, uptake_valerate, uptake_butyrate, uptake_propionate, uptake_acetate, uptake_h2, decay_Xsu, decay_Xaa, decay_Xfa, decay_Xc4, decay_Xpro, decay_Xac, decay_Xh2, storage_Sva_in_XPHA, storage_Sbu_in_XPHA, storage_Spro_in_XPHA, storage_Sac_in_XPHA, lysis_XPAO, lysis_XPP, lysis_XPHA, h2_transfer, ch4_transfer, IC_transfer]) @@ -727,7 +728,7 @@ def __new__(cls, components=None, path=None, N_xc=2.686e-3, N_I=4.286e-3, N_aa=7 dct = self.__dict__ dct.update(kwargs) - self.set_rate_function(rhos_adm1) + self.set_rate_function(rhos_adm1_p_extension) dct['_parameters'] = dict(zip(cls._stoichio_params, stoichio_vals)) self.rate_function._params = dict(zip(cls._kinetic_params, [ks, Ks, pH_ULs, pH_LLs, KS_IN*N_mw, KS_IP*P_mw, From 97af128ce25d8cabc8b4544b7eb41e797fa1818d Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Thu, 30 Mar 2023 14:37:12 -0500 Subject: [PATCH 083/424] New Petersen Matrix (P extension ADM1) Adding new Petersen matrix for modified ADM1 (this alone doesn't contain all the added parameters --- .../data/process_data/_adm1_p_extension.tsv | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 qsdsan/data/process_data/_adm1_p_extension.tsv diff --git a/qsdsan/data/process_data/_adm1_p_extension.tsv b/qsdsan/data/process_data/_adm1_p_extension.tsv new file mode 100644 index 00000000..401ce9ca --- /dev/null +++ b/qsdsan/data/process_data/_adm1_p_extension.tsv @@ -0,0 +1,22 @@ + S_su S_aa S_fa S_va S_bu S_pro S_ac S_h2 S_ch4 S_IC S_IN S_IP S_I X_ch X_pr X_li X_su X_aa X_fa X_c4 X_pro X_ac X_h2 X_I X_PHA X_PP X_PAO S_K S_Mg +hydrolysis_carbs 1 ? ? ? -1 +hydrolysis_proteins 1 ? ? ? -1 +hydrolysis_lipids 1-f_fa_li f_fa_li ? ? ? -1 +uptake_sugars -1 (1-Y_su)*f_bu_su (1-Y_su)*f_pro_su (1-Y_su)*f_ac_su (1-Y_su)*f_h2_su ? ? ? Y_su +uptake_amino_acids -1 (1-Y_aa)*f_va_aa (1-Y_aa)*f_bu_aa (1-Y_aa)*f_pro_aa (1-Y_aa)*f_ac_aa (1-Y_aa)*f_h2_aa ? ? ? Y_aa +uptake_LCFA -1 (1-Y_fa)*f_ac_fa (1-Y_fa)*f_h2_fa ? ? ? Y_fa +uptake_valerate -1 (1-Y_c4)*f_pro_va (1-Y_c4)*f_ac_va (1-Y_c4)*f_h2_va ? ? ? Y_c4 +uptake_butyrate -1 (1-Y_c4)*f_ac_bu (1-Y_c4)*f_h2_bu ? ? ? Y_c4 +uptake_propionate -1 (1-Y_pro)*f_ac_pro (1-Y_pro)*f_h2_pro ? ? ? Y_pro +uptake_acetate -1 1-Y_ac ? ? ? Y_ac +uptake_h2 -1 1-Y_h2 ? ? ? Y_h2 +decay_Xsu ? ? ? f_sI_xb f_ch_xb f_pr_xb f_li_xb -1 f_xI_xb +decay_Xaa ? ? ? f_sI_xb f_ch_xb f_pr_xb f_li_xb -1 f_xI_xb +decay_Xfa ? ? ? f_sI_xb f_ch_xb f_pr_xb f_li_xb -1 f_xI_xb +decay_Xc4 ? ? ? f_sI_xb f_ch_xb f_pr_xb f_li_xb -1 f_xI_xb +decay_Xpro ? ? ? f_sI_xb f_ch_xb f_pr_xb f_li_xb -1 f_xI_xb +decay_Xac ? ? ? f_sI_xb f_ch_xb f_pr_xb f_li_xb -1 f_xI_xb +decay_Xh2 ? ? ? f_sI_xb f_ch_xb f_pr_xb f_li_xb -1 f_xI_xb +lysis_XPAO f_sI_xb f_ch_xb f_pr_xb f_li_xb f_xI_xb -1 +lysis_XPHA f_va_PHA f_bu_PHA f_pro_PHA f_ac_PHA -1 + From 90f983a15bb3d73f55c1c285ab05076e49fb1b84 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Thu, 30 Mar 2023 14:50:56 -0500 Subject: [PATCH 084/424] Create _adm1_p_extension Changing name of file --- qsdsan/processes/_adm1_p_extension.py | 820 ++++++++++++++++++++++++++ 1 file changed, 820 insertions(+) create mode 100644 qsdsan/processes/_adm1_p_extension.py diff --git a/qsdsan/processes/_adm1_p_extension.py b/qsdsan/processes/_adm1_p_extension.py new file mode 100644 index 00000000..a93dd1c1 --- /dev/null +++ b/qsdsan/processes/_adm1_p_extension.py @@ -0,0 +1,820 @@ +# -*- coding: utf-8 -*- +''' +QSDsan: Quantitative Sustainable Design for sanitation and resource recovery systems + +This module is developed by: + Joy Zhang + Saumitra Rai + + +Part of this module is based on the Thermosteam package: +https://github.com/BioSTEAMDevelopmentGroup/thermosteam + +This module is under the University of Illinois/NCSA Open Source License. +Please refer to https://github.com/QSD-Group/QSDsan/blob/main/LICENSE.txt +for license details. +''' + +from thermosteam.utils import chemicals_user +from thermosteam import settings +from chemicals.elements import molecular_weight as get_mw +from qsdsan import Component, Components, Process, Processes, CompiledProcesses +import numpy as np +from qsdsan.utils import ospath, data_path +from scipy.optimize import brenth +from warnings import warn + +__all__ = ('create_adm1_p_extension_cmps', 'ADM1_p_extension', + 'non_compet_inhibit', 'substr_inhibit', + 'T_correction_factor', + 'pH_inhibit', 'Hill_inhibit', + 'rhos_adm1_p_extension') + +_path = ospath.join(data_path, 'process_data/_adm1_p_extension.tsv') +_load_components = settings.get_default_chemicals + +#%% +# ============================================================================= +# ADM1 (with P extension) -specific components +# ============================================================================= + +C_mw = get_mw({'C':1}) +N_mw = get_mw({'N':1}) +P_mw = get_mw({'P':1}) + +def create_adm1_p_extension_cmps(set_thermo=True): + cmps_all = Components.load_default() + + # varies + # X_c = cmps_all.X_OHO.copy('X_c') + # X_c.description = 'Composite' + # X_c.i_C = 0.02786 * C_mw + # X_c.i_N = 0.0376 + + X_ch = Component.from_chemical('X_ch', chemical='glycogen', Tc=1011.4, # glucan + description='Carbohydrates', + measured_as='COD', + particle_size='Particulate', + degradability='Slowly', + organic=True) + # X_ch = cmps_all.X_B_Subst.copy('X_ch') + # X_ch.i_N = 0 + # X_ch.i_C = 0.0313 * C_mw + + # varies + X_pr = cmps_all.X_B_Subst.copy('X_pr') + X_pr.i_N = 0.007 * N_mw + X_pr.i_C = 0.03 * C_mw + + X_li = Component.from_chemical('X_li', chemical='tripalmitin', + description='Lipids', + measured_as='COD', + particle_size='Particulate', + degradability='Slowly', + organic=True) + + # both varies + X_I = cmps_all.X_U_Inf.copy('X_I') + S_I = cmps_all.S_U_Inf.copy('S_I') + X_I.i_C = S_I.i_C = 0.03 * C_mw + X_I.i_N = S_I.i_N = 0.06 + + S_su = Component.from_chemical('S_su', chemical='glucose', + description='Monosaccharides', + measured_as='COD', + particle_size='Soluble', + degradability='Readily', + organic=True) + # S_su = cmps_all.S_F.copy('S_su') + # S_su.i_N = 0 + # S_su.i_C = 0.0313 * 12 + + # varies + S_aa = cmps_all.S_F.copy('S_aa') + S_aa.i_N = 0.007 * N_mw + S_aa.i_P = 0 + S_aa.i_C = 0.03 * C_mw + + S_fa = Component.from_chemical('S_fa', chemical='palmitate', + description='Total long-chain fatty acids', + measured_as='COD', + particle_size='Soluble', + degradability='Readily', + organic=True) + + S_va = Component.from_chemical('S_va', chemical='valerate', + description='Total valerate', + measured_as='COD', + particle_size='Soluble', + degradability='Readily', + organic=True) + + S_bu = Component.from_chemical('S_bu', chemical='butyrate', + description='Total butyrate', + measured_as='COD', + particle_size='Soluble', + degradability='Readily', + organic=True) + + S_pro = cmps_all.S_Prop.copy('S_pro') + S_ac = cmps_all.S_Ac.copy('S_ac') + S_h2 = cmps_all.S_H2.copy('S_h2') + S_ch4 = cmps_all.S_CH4.copy('S_ch4') + + S_IC = Component.from_chemical('S_IC', chemical='CO2', + measured_as='C', + description='Inorganic carbon', + particle_size='Dissolved gas', + degradability='Undegradable', + organic=False) + S_IC.copy_models_from(S_ch4, ('Cn',)) + + S_IN = Component.from_chemical('S_IN', chemical='NH3', + measured_as='N', + description='Inorganic nitrogen', + particle_size='Soluble', + degradability='Undegradable', + organic=False) + + S_IP = cmps_all.S_PO4.copy('S_IP') + + X_su = cmps_all.X_FO.copy('X_su') + X_su.description = 'Biomass uptaking sugars' + + X_aa = cmps_all.X_FO.copy('X_aa') + X_aa.description = 'Biomass uptaking amino acids' + + X_fa = cmps_all.X_FO.copy('X_fa') + X_fa.description = 'Biomass uptaking long chain fatty acids' + + X_c4 = cmps_all.X_FO.copy('X_c4') + X_c4.description = 'Biomass uptaking c4 fatty acids' + + X_pro = cmps_all.X_PRO.copy('X_pro') + X_ac = cmps_all.X_ACO.copy('X_ac') + X_h2 = cmps_all.X_HMO.copy('X_h2') + + X_PHA = cmps_all.X_PAO_PHA.copy('X_PHA') + + X_PP = cmps_all.X_PAO_PP_Lo.copy('X_PP') + + X_PAO = cmps_all.X_PAO.copy('X_PAO') + + S_K = Component.from_chemical('S_K', chemical='K', + measured_as='K', + description='Potassium', + particle_size='Soluble', + degradability='Undegradable', + organic=False) + + S_Mg = Component.from_chemical('S_Mg', chemical='Mg', + measured_as='Mg', + description='Magnesium', + particle_size='Soluble', + degradability='Undegradable', + organic=False) + + for bio in (X_su, X_aa, X_fa, X_c4, X_pro, X_ac, X_h2): + # bio.formula = 'C5H7O2N' + bio.i_C = 0.0313 * C_mw + bio.i_N = 0.08 + + S_cat = cmps_all.S_CAT.copy('S_cat') + S_an = cmps_all.S_AN.copy('S_an') + S_cat.i_mass = S_an.i_mass = 1 + + cmps_adm1_p_extension = Components([S_su, S_aa, S_fa, S_va, S_bu, S_pro, S_ac, S_h2, + S_ch4, S_IC, S_IN, S_IP, S_I, X_ch, X_pr, X_li, + X_su, X_aa, X_fa, X_c4, X_pro, X_ac, X_h2, X_I, + X_PHA, X_PP, X_PAO, S_K, S_Mg, + S_cat, S_an, cmps_all.H2O]) + cmps_adm1_p_extension.default_compile() + if set_thermo: settings.set_thermo(cmps_adm1_p_extension) + return cmps_adm1_p_extension + +# create_adm1_p_extension_cmps() + + +#%% +# ============================================================================= +# kinetic rate functions +# ============================================================================= + +R = 8.3145e-2 # Universal gas constant, [bar/M/K] + +def non_compet_inhibit(Si, Ki): + return Ki/(Ki+Si) + +def substr_inhibit(Si, Ki): + return Si/(Ki+Si) + +def mass2mol_conversion(cmps): + '''conversion factor from kg[measured_as]/m3 to mol[component]/L''' + return cmps.i_mass / cmps.chem_MW + +# def T_correction_factor(T1, T2, theta): +# return np.exp(theta * (T2-T1)) + +def T_correction_factor(T1, T2, delta_H): + return np.exp(delta_H/(R*100) * (1/T1 - 1/T2)) # R converted to SI + +# def calc_Kas(pKas, T_base, T_op, theta): +# pKas = np.asarray(pKas) +# return 10**(-pKas) * T_correction_factor(T_base, T_op, theta) + +# What about this? Any changes needed? (Saumitra) +def acid_base_rxn(h_ion, weak_acids_tot, Kas): + # h, nh4, hco3, ac, pr, bu, va = mols + # S_cat, S_an, S_IN, S_IC, S_ac, S_pro, S_bu, S_va = weak_acids_tot # in M + S_cat, S_an, S_IN = weak_acids_tot[:3] + # Kw, Ka_nh, Ka_co2, Ka_ac, Ka_pr, Ka_bu, Ka_va = Kas + Kw = Kas[0] + oh_ion = Kw/h_ion + nh3, hco3, ac, pro, bu, va = Kas[1:] * weak_acids_tot[2:] / (Kas[1:] + h_ion) + return S_cat + h_ion + (S_IN - nh3) - S_an - oh_ion - hco3 - ac - pro - bu - va + +def fprime_abr(h_ion, weak_acids_tot, Kas): + S_cat, S_an, S_IN = weak_acids_tot[:3] + Kw = Kas[0] + doh_ion = - Kw / h_ion ** 2 + dnh3, dhco3, dac, dpro, dbu, dva = - Kas[1:] * weak_acids_tot[2:] / (Kas[1:] + h_ion)**2 + return 1 + (-dnh3) - doh_ion - dhco3 - dhco3 - dac - dpro - dbu - dva + +def pH_inhibit(pH, ul, ll, lower_only=True): + if lower_only: + # if pH >= ul: return 1 + # else: return exp(-3 * ((pH-ul)/(ul-ll))**2) + low_by = np.minimum(pH-ul, 0) + return np.exp(-3 * (low_by/(ul-ll))**2) + else: + return (1+2*10**(0.5*(ll-ul)))/(1+10**(pH-ul)+10**(ll-pH)) + +def Hill_inhibit(H_ion, ul, ll): + n = 3/(ul-ll) + K = 10**(-(ul+ll)/2) + return 1/(1+(H_ion/K) ** n) + +rhos = np.zeros(28) # 28 kinetic processes (25 as defined in modified ADM1 + 3 for gases) +Cs = np.empty(25) # 25 processes as defined in modified ADM1 + +def rhos_adm1_p_extension(state_arr, params): + ks = params['rate_constants'] + Ks = params['half_sat_coeffs'] + cmps = params['components'] + # n = len(cmps) + pH_ULs = params['pH_ULs'] + pH_LLs = params['pH_LLs'] + KS_IN = params['KS_IN'] + KS_IP = params['KS_IP'] + KI_nh3 = params['KI_nh3'] + KIs_h2 = params['KIs_h2'] + KHb = params['K_H_base'] + Kab = params['Ka_base'] + KH_dH = params['K_H_dH'] + Ka_dH = params['Ka_dH'] + kLa = params['kLa'] + T_base = params['T_base'] + root = params['root'] + + # state_arr_cmps stated just for readability of code + # state_arr_cmps = [S_su, S_aa, S_fa, S_va, S_bu, S_pro, S_ac, S_h2, S_ch4, S_IC, S_IN, + # S_IP, S_I, X_ch, X_pr, X_li, X_su, X_aa, X_fa, X_c4, X_pro, X_ac, + # X_h2, X_I, X_PHA, X_PP, X_PAO, S_K, S_Mg] + + # Cs_ids = cmps.indices(['X_ch', 'X_pr', 'X_li', 'X_su', 'X_aa', + # 'X_fa', 'X_c4', 'X_c4', 'X_pro', 'X_ac', 'X_h2', + # 'X_su', 'X_aa', 'X_fa', 'X_c4', 'X_pro', 'X_ac', 'X_h2', + # 'X_PAO', 'X_PAO', 'X_PAO', 'X_PAO', 'X_PAO', 'X_PP', 'X_PHA']) + # Cs = state_arr[Cs_ids] + Cs[:7] = state_arr[13:20] + Cs[7:11] = state_arr[19:23] + Cs[11:18] = state_arr[16:23] + Cs[18:23] = state_arr[26] + Cs[23] = state_arr[25] + Cs[24] = state_arr[24] + + # substrates_ids = cmps.indices(['S_su', 'S_aa', 'S_fa', 'S_va', + # 'S_bu', 'S_pro', 'S_ac', 'S_h2']) + # substrates = state_arr[substrates_ids] + substrates = state_arr[:8] + + # S_va, S_bu, S_h2, S_IN = state_arr[cmps.indices(['S_va', 'S_bu', 'S_h2', 'S_IN'])] + # S_va, S_bu, S_h2, S_ch4, S_IC, S_IN = state_arr[[3,4,7,8,9,10]] + S_va, S_bu, S_h2, S_IN, S_IP = state_arr[[3,4,7,10,11]] + unit_conversion = mass2mol_conversion(cmps) + cmps_in_M = state_arr[:27] * unit_conversion + weak_acids = cmps_in_M[[24, 25, 10, 9, 6, 5, 4, 3]] + + T_op = state_arr[-1] + biogas_S = state_arr[7:10].copy() + biogas_p = R * T_op * state_arr[27:30] + Kas = Kab * T_correction_factor(T_base, T_op, Ka_dH) + KH = KHb * T_correction_factor(T_base, T_op, KH_dH) / unit_conversion[7:10] + + rhos[:-3] = ks * Cs + rhos[3:11] *= substr_inhibit(substrates, Ks) + if S_va > 0: rhos[6] *= 1/(1+S_bu/S_va) + if S_bu > 0: rhos[7] *= 1/(1+S_va/S_bu) + + # substrates_ids = cmps.indices(['S_va', 'S_bu', 'S_pro', 'S_ac']) + # substrates_modified = state_arr[substrates_ids] + substrates_modified = state_arr[3:7] + # Ka = K_a + Ka = Ks[-2] + rhos[18:22] *= substr_inhibit(substrates_modified, Ka) + + # PP_PAO = X_PP/X_PAO + PP_PAO = state_arr[25]/state_arr[26] + # Kpp = K_pp + Kpp = Ks[-1] + rhos[18:22] *= substr_inhibit(PP_PAO, Kpp) + + # Multiplication by {Sva, Sbu, Spro, Sac}/(Sva + Sbu + Spro + Sac) + transformation_array = state_arr[3:7]/sum(state_arr[3:7]) + rhos[18:22] *= transformation_array + + h = brenth(acid_base_rxn, 1e-14, 1.0, + args=(weak_acids, Kas), + xtol=1e-12, maxiter=100) + + nh3 = Kas[1] * weak_acids[2] / (Kas[1] + h) + co2 = weak_acids[3] - Kas[2] * weak_acids[3] / (Kas[2] + h) + biogas_S[-1] = co2 / unit_conversion[9] + + Iph = Hill_inhibit(h, pH_ULs, pH_LLs) + Ih2 = non_compet_inhibit(S_h2, KIs_h2) + root.data = [-np.log10(h), Iph, Ih2] + rhos[3:11] *= Iph * substr_inhibit(S_IN, KS_IN) * substr_inhibit(S_IP, KS_IP) + rhos[5:9] *= Ih2 + # rhos[4:12] *= Hill_inhibit(h, pH_ULs, pH_LLs) * substr_inhibit(S_IN, KS_IN) + # rhos[6:10] *= non_compet_inhibit(S_h2, KIs_h2) + rhos[9] *= non_compet_inhibit(nh3, KI_nh3) + + + + rhos[-3:] = kLa * (biogas_S - KH * biogas_p) + # print(rhos) + return rhos +#%% +# ============================================================================= +# ADM1_p_extension class +# ============================================================================= +class TempState: + def __init__(self): + self.data = [] + + # def append(self, value): + # self.data += [value] + +@chemicals_user +class ADM1_p_extension(CompiledProcesses): + """ + Anaerobic Digestion Model No.1. [1]_, [2]_, [3]_ + + Parameters + ---------- + components : class:`CompiledComponents`, optional + Components corresponding to each entry in the stoichiometry array, + defaults to thermosteam.settings.chemicals. + path : str, optional + Alternative file path for the Petersen matrix. The default is None. + N_xc : float, optional + Nitrogen content of composite materials [kmol N/kg COD]. The default is 2.686e-3. + N_I : float, optional + Nitrogen content of inert organics [kmol N/kg COD]. The default is 4.286e-3. + N_aa : float, optional + Nitrogen content of amino acids [kmol N/kg COD]. The default is 7e-3. + f_sI_xb : float, optional + fraction of soluble inerts from biomass. The default is 0. + f_ch_xb : float, optional + fraction of carbohydrates from biomass. The default is 0.275. + f_pr_xb : float, optional + fraction of proteins from biomass. The default is 0.275. + f_li_xb : float, optional + fraction of lipids from biomass. The default is 0.35. + f_xI_xb : float, optional + fraction of particulate inerts from biomass. The default is 0.1. + f_fa_li : float, optional + Fraction of long chain fatty acids (LCFAs) from hydrolysis of lipids + [kg COD/kg COD]. The default is 0.95. + f_bu_su : float, optional + Fraction of butyrate from sugars [kg COD/kg COD]. The default is 0.13. + f_pro_su : float, optional + Fraction of propionate from sugars [kg COD/kg COD]. The default is 0.27. + f_ac_su : float, optional + Fraction of acetate from sugars [kg COD/kg COD]. The default is 0.41. + f_va_aa : float, optional + Fraction of valerate from amino acids [kg COD/kg COD]. The default is 0.23. + f_bu_aa : float, optional + Fraction of butyrate from amino acids [kg COD/kg COD]. The default is 0.26. + f_pro_aa : float, optional + Fraction of propionate from amino acids [kg COD/kg COD]. The default is 0.05. + f_ac_aa : float, optional + Fraction of acetate from amino acids [kg COD/kg COD]. The default is 0.4. + f_ac_fa : float, optional + Fraction of acetate from LCFAs [kg COD/kg COD]. The default is 0.7. + f_pro_va : float, optional + Fraction of propionate from LCFAs [kg COD/kg COD]. The default is 0.54. + f_ac_va : float, optional + Fraction of acetate from valerate [kg COD/kg COD]. The default is 0.31. + f_ac_bu : float, optional + Fraction of acetate from butyrate [kg COD/kg COD]. The default is 0.8. + f_ac_pro : float, optional + Fraction of acetate from propionate [kg COD/kg COD]. The default is 0.57. + f_ac_PHA : float, optional + Yield of acetate on PHA [kg COD/kg COD]. The default is 0.4. + f_bu_PHA : float, optional + Yield of butyrate on PHA [kg COD/kg COD]. The default is 0.1. + f_pro_PHA : float, optional + Yield of propionate on PHA [kg COD/kg COD]. The default is 0.4. + f_va_PHA : float, optional + Yield of valerate on PHA [kg COD/kg COD]. The default is 0.1. + Y_su : float, optional + Biomass yield of sugar uptake [kg COD/kg COD]. The default is 0.1. + Y_aa : float, optional + Biomass yield of amino acid uptake [kg COD/kg COD]. The default is 0.08. + Y_fa : float, optional + Biomass yield of LCFA uptake [kg COD/kg COD]. The default is 0.06. + Y_c4 : float, optional + Biomass yield of butyrate or valerate uptake [kg COD/kg COD]. + The default is 0.06. + Y_pro : float, optional + Biomass yield of propionate uptake [kg COD/kg COD]. The default is 0.04. + Y_ac : float, optional + Biomass yield of acetate uptake [kg COD/kg COD]. The default is 0.05. + Y_h2 : float, optional + Biomass yield of H2 uptake [kg COD/kg COD]. The default is 0.06. + Y_po4 : float, optional + Yield of biomass on phosphate [kmol P/kg COD]. The default is 0.013. + q_dis : float, optional + Composites disintegration rate constant [d^(-1)]. The default is 0.5. + q_ch_hyd : float, optional + Carbohydrate hydrolysis rate constant [d^(-1)]. The default is 10. + q_pr_hyd : float, optional + Protein hydrolysis rate constant [d^(-1)]. The default is 10. + q_li_hyd : float, optional + Lipid hydrolysis rate constant [d^(-1)]. The default is 10. + k_su : float, optional + Sugar uptake rate constant [d^(-1)]. The default is 30. + k_aa : float, optional + Amino acid uptake rate constant [d^(-1)]. The default is 50. + k_fa : float, optional + LCFA uptake rate constant [d^(-1)]. The default is 6. + k_c4 : float, optional + Butyrate or valerate uptake rate constant [d^(-1)]. The default is 20. + k_pro : float, optional + Propionate uptake rate constant [d^(-1)]. The default is 13. + k_ac : float, optional + Acetate uptake rate constant [d^(-1)]. The default is 8. + k_h2 : float, optional + H2 uptake rate constant [d^(-1)]. The default is 35. + K_su : float, optional + Half saturation coefficient of sugar uptake [kg COD/m3]. + The default is 0.5. + K_aa : float, optional + Half saturation coefficient of amino acid uptake [kg COD/m3]. + The default is 0.3. + K_fa : float, optional + Half saturation coefficient of LCFA uptake [kg COD/m3]. + The default is 0.4. + K_c4 : float, optional + Half saturation coefficient of butyrate or valerate uptake [kg COD/m3]. + The default is 0.2. + K_pro : float, optional + Half saturation coefficient of propionate uptake [kg COD/m3]. + The default is 0.1. + K_ac : float, optional + Half saturation coefficient of acetate uptake [kg COD/m3]. + The default is 0.15. + K_h2 : float, optional + Half saturation coefficient of H2 uptake [kg COD/m3]. + The default is 7e-6. + K_a : float, optional + Saturation coefficient for acetate [kg COD/m3]. + The default is 0.004. + K_pp : float, optional + Saturation coefficient for polyphosphate [kg COD/m3]. + The default is 0.00032. + b_su : float, optional + Decay rate constant of sugar-uptaking biomass [d^(-1)]. + The default is 0.02. + b_aa : float, optional + Decay rate constant of amino-acid-uptaking biomass [d^(-1)]. + The default is 0.02. + b_fa : float, optional + Decay rate constant of LCFA-uptaking biomass [d^(-1)]. + The default is 0.02. + b_c4 : float, optional + Decay rate constant of valerate- or butyrate-uptaking biomass [d^(-1)]. + The default is 0.02. + b_pro : float, optional + Decay rate constant of propionate-uptaking biomass [d^(-1)]. + The default is 0.02. + b_ac : float, optional + Decay rate constant of acetate-uptaking biomass [d^(-1)]. + The default is 0.02. + b_h2 : float, optional + Decay rate constant of H2-uptaking biomass [d^(-1)]. The default is 0.02. + q_PHA : float, optional + Rate constant for storage of PHA [d^(-1)]. The default is 3. + b_PAO : float, optional + Lysis rate of PAOs [d^(-1)]. The default is 0.2. + b_PP : float, optional + Lysis rate of polyphosphates [d^(-1)]. The default is 0.2. + b_PHA : float, optional + Lysis rate of PHAs [d^(-1)]. The default is 0.2. + KI_h2_fa : float, optional + H2 inhibition coefficient for LCFA uptake [kg COD/m3]. The default is 5e-6. + KI_h2_c4 : float, optional + H2 inhibition coefficient for butyrate or valerate uptake [kg COD/m3]. + The default is 1e-5. + KI_h2_pro : float, optional + H2 inhibition coefficient for propionate uptake [kg COD/m3]. + The default is 3.5e-6. + KI_nh3 : float, optional + Free ammonia inhibition coefficient for acetate uptake [M]. + The default is 1.8e-3. + KS_IN : float, optional + Inorganic nitrogen (nutrient) inhibition coefficient for soluble + substrate uptake [M]. The default is 1e-4. + KS_IP : float, optional + P limitation for inorganic phosphorous [kmol P/m3]. + The default is 2e-5. + pH_limits_aa : 2-tuple, optional + Lower and upper limits of pH inhibition for acidogens and acetogens, + unitless. The default is (4,5.5). + pH_limits_ac : 2-tuple, optional + Lower and upper limits of pH inhibition for aceticlastic methanogens, + unitless. The default is (6,7). + pH_limits_h2 : 2-tuple, optional + Lower and upper limits of pH inhibition for H2-utilizing methanogens, + unitless. The default is (5,6). + T_base : float, optional + Base temperature for kinetic parameters [K]. The default is 298.15. + pKa_base : iterable[float], optional + pKa (equilibrium coefficient) values of acid-base pairs at the base + temperature, unitless, following the order of `ADM1._acid_base_pairs`. + The default is [14, 9.25, 6.35, 4.76, 4.88, 4.82, 4.86]. + Ka_dH : iterable[float], optional + Heat of reaction of each acid-base pair at base temperature [J/mol], + following the order of `ADM1._acid_base_pairs`. The default is + [55900, 51965, 7646, 0, 0, 0, 0]. + kLa : float, optional + Liquid-gas mass transfer coefficient [d^(-1)]. The default is 200. + K_H_base : iterable[float], optional + Henry's Law coefficients of biogas species at the base temperature + [M dissolved in liquid/bar]. Follows the order of `ADM1._biogas_IDs`. + The default is [7.8e-4, 1.4e-3, 3.5e-2]. + K_H_dH : iterable[float], optional + Heat of reaction of liquid-gas transfer of biogas species [J/mol]. + Follows the order of `ADM1._biogas_IDs`. The default is + [-4180, -14240, -19410]. + + Examples + -------- + >>> from qsdsan import processes as pc + >>> cmps = pc.create_adm1_p_extension_cmps() + >>> adm1 = pc.ADM1_p_extension() + >>> adm1.show() + ADM1([hydrolysis_carbs, hydrolysis_proteins, hydrolysis_lipids, uptake_sugars, uptake_amino_acids, uptake_LCFA, uptake_valerate, uptake_butyrate, uptake_propionate, uptake_acetate, uptake_h2, decay_Xsu, decay_Xaa, decay_Xfa, decay_Xc4, decay_Xpro, decay_Xac, decay_Xh2, storage_Sva_in_XPHA, storage_Sbu_in_XPHA, storage_Spro_in_XPHA, storage_Sac_in_XPHA, lysis_XPAO, lysis_XPP, lysis_XPHA, h2_transfer, ch4_transfer, IC_transfer]) + + References + ---------- + .. [1] Batstone, D. J.; Keller, J.; Angelidaki, I.; Kalyuzhnyi, S. V; + Pavlostathis, S. G.; Rozzi, A.; Sanders, W. T. M.; Siegrist, H.; + Vavilin, V. A. The IWA Anaerobic Digestion Model No 1 (ADM1). + Water Sci. Technol. 2002, 45 (10), 65–73. + .. [2] Rosen, C.; Jeppsson, U. Aspects on ADM1 Implementation within + the BSM2 Framework; Lund, 2006. + .. [3] Flores-Alsina, X.; Solon, K.; Kazadi Mbamba, C.; Tait, S.; + Gernaey, K. V.; Jeppsson, U.; Batstone, D. J. + Modelling phosphorus (P), sulfur (S) and iron (FE) interactions for + dynamic simulations of anaerobic digestion processes. Water Research. 2016, + 95, 370–382. + """ + + _stoichio_params = ('f_sI_xb', 'f_ch_xb', 'f_pr_xb', 'f_li_xb', 'f_xI_xb', + 'f_fa_li', 'f_bu_su', 'f_pro_su', 'f_ac_su', 'f_h2_su', + 'f_va_aa', 'f_bu_aa', 'f_pro_aa', 'f_ac_aa', 'f_h2_aa', + 'f_ac_fa', 'f_h2_fa', 'f_pro_va', 'f_ac_va', 'f_h2_va', + 'f_ac_bu', 'f_h2_bu', 'f_ac_pro', 'f_h2_pro', + 'f_ac_PHA', 'f_bu_PHA', 'f_pro_PHA', 'f_va_PHA', + 'Y_su', 'Y_aa', 'Y_fa', 'Y_c4', 'Y_pro', 'Y_ac', 'Y_h2', 'Y_po4') + _kinetic_params = ('rate_constants', 'half_sat_coeffs', 'pH_ULs', 'pH_LLs', + 'KS_IN', 'KS_IP', 'KI_nh3', 'KIs_h2', + 'Ka_base', 'Ka_dH', 'K_H_base', 'K_H_dH', 'kLa', + 'T_base', 'components', 'root') + _acid_base_pairs = (('H+', 'OH-'), ('NH4+', 'NH3'), ('CO2', 'HCO3-'), + ('HAc', 'Ac-'), ('HPr', 'Pr-'), + ('HBu', 'Bu-'), ('HVa', 'Va-')) + + _biogas_IDs = ('S_h2', 'S_ch4', 'S_IC') + + def __new__(cls, components=None, path=None, N_xc=2.686e-3, N_I=4.286e-3, N_aa=7e-3, + f_sI_xb=0, f_ch_xb=0.275, f_pr_xb=0.275, f_li_xb=0.350, + f_fa_li=0.95, f_bu_su=0.13, f_pro_su=0.27, f_ac_su=0.41, + f_va_aa=0.23, f_bu_aa=0.26, f_pro_aa=0.05, f_ac_aa=0.4, + f_ac_fa=0.7, f_pro_va=0.54, f_ac_va=0.31, f_ac_bu=0.8, f_ac_pro=0.57, + f_ac_PHA=0.4, f_bu_PHA=0.1, f_pro_PHA=0.4, + Y_su=0.1, Y_aa=0.08, Y_fa=0.06, Y_c4=0.06, Y_pro=0.04, Y_ac=0.05, Y_h2=0.06, Y_po4=0.013, + q_dis=0.5, q_ch_hyd=10, q_pr_hyd=10, q_li_hyd=10, + k_su=30, k_aa=50, k_fa=6, k_c4=20, k_pro=13, k_ac=8, k_h2=35, + K_su=0.5, K_aa=0.3, K_fa=0.4, K_c4=0.2, K_pro=0.1, K_ac=0.15, K_h2=7e-6, K_a=4e-3, K_pp=32e-5, + b_su=0.02, b_aa=0.02, b_fa=0.02, b_c4=0.02, b_pro=0.02, b_ac=0.02, b_h2=0.02, + q_PHA=3, b_PAO=0.2, b_PP=0.2, b_PHA=0.2, + KI_h2_fa=5e-6, KI_h2_c4=1e-5, KI_h2_pro=3.5e-6, KI_nh3=1.8e-3, KS_IN=1e-4, KS_IP=2e-5, + pH_limits_aa=(4,5.5), pH_limits_ac=(6,7), pH_limits_h2=(5,6), + T_base=298.15, pKa_base=[14, 9.25, 6.35, 4.76, 4.88, 4.82, 4.86], + Ka_dH=[55900, 51965, 7646, 0, 0, 0, 0], + kLa=200, K_H_base=[7.8e-4, 1.4e-3, 3.5e-2], + K_H_dH=[-4180, -14240, -19410], + **kwargs): + + cmps = _load_components(components) + # Sure that some things are missing here! (Saumitra) + cmps.X_c.i_N = N_xc * N_mw + cmps.X_I.i_N = cmps.S_I.i_N = N_I * N_mw + cmps.S_aa.i_N = cmps.X_pr.i_N = N_aa * N_mw + + if not path: path = _path + self = Processes.load_from_file(path, + components=cmps, + conserved_for=('C', 'N', 'P'), + parameters=cls._stoichio_params, + compile=False) + + # Need to add 5 processes separately for stoichiometry. See ASM2d for reference. + if path == _path: + _p19 = Process('storage_Sva_in_XPHA', + 'S_va + [Y_po4]X_PP -> X_PHA + [?]S_K + [?]S_Mg', + components=cmps, + ref_component='X_PHA', + rate_equation='q_PHA * S_va/(K_a+S_va) * (X_PP/X_PAO)/(K_PP+(X_PP/X_PAO)) * X_PAO * S_va/(S_va+S_bu+S_pro+S_ac)', + parameters=('Y_po4', 'q_PHA', 'K_a', 'K_PP'), + conserved_for=('K', 'Mg')) + + _p20 = Process('storage_Sbu_in_XPHA', + 'S_bu + [Y_po4]X_PP -> X_PHA + [?]S_K + [?]S_Mg', + components=cmps, + ref_component='X_PHA', + rate_equation='q_PHA * S_bu/(K_a+S_bu) * (X_PP/X_PAO)/(K_PP+(X_PP/X_PAO)) * X_PAO * S_bu/(S_va+S_bu+S_pro+S_ac)', + parameters=('Y_po4', 'q_PHA', 'K_a', 'K_PP'), + conserved_for=('K', 'Mg')) + + _p21 = Process('storage_Spro_in_XPHA', + 'S_pro + [Y_po4]X_PP -> X_PHA + [?]S_K + [?]S_Mg', + components=cmps, + ref_component='X_PHA', + rate_equation='q_PHA * S_pro/(K_a+S_pro) * (X_PP/X_PAO)/(K_PP+(X_PP/X_PAO)) * X_PAO * S_pro/(S_va+S_bu+S_pro+S_ac)', + parameters=('Y_po4', 'q_PHA', 'K_a', 'K_PP'), + conserved_for=('K', 'Mg')) + + _p22 = Process('storage_Sac_in_XPHA', + 'S_ac + [Y_po4]X_PP -> X_PHA + [?]S_K + [?]S_Mg', + components=cmps, + ref_component='X_PHA', + rate_equation='q_PHA * S_ac/(K_a+S_ac) * (X_PP/X_PAO)/(K_PP+(X_PP/X_PAO)) * X_PAO * S_ac/(S_va+S_bu+S_pro+S_ac)', + parameters=('Y_po4', 'q_PHA', 'K_a', 'K_PP'), + conserved_for=('K', 'Mg')) + + _p24 = Process('lysis_XPP', + 'X_pp -> [?]S_K + [?]S_Mg', + components=cmps, + ref_component='X_PP', + rate_equation='b_PP*X_PP', + parameters=('b_PP'), + conserved_for=('K', 'Mg')) + + self.insert(18, _p19) + self.insert(19, _p20) + self.insert(20, _p21) + self.insert(21, _p22) + self.insert(23, _p24) + + gas_transfer = [] + for i in cls._biogas_IDs: + new_p = Process('%s_transfer' % i.lstrip('S_'), + reaction={i:-1}, + ref_component=i, + conserved_for=(), + parameters=()) + gas_transfer.append(new_p) + self.extend(gas_transfer) + self.compile(to_class=cls) + + stoichio_vals = (f_sI_xb, f_ch_xb, f_pr_xb, f_li_xb, 1-f_sI_xb-f_ch_xb-f_pr_xb-f_li_xb, + f_fa_li, f_bu_su, f_pro_su, f_ac_su, 1-f_bu_su-f_pro_su-f_ac_su, + f_va_aa, f_bu_aa, f_pro_aa, f_ac_aa, 1-f_va_aa-f_bu_aa-f_pro_aa-f_ac_aa, + f_ac_fa, 1-f_ac_fa, f_pro_va, f_ac_va, 1-f_pro_va-f_ac_va, + f_ac_bu, 1-f_ac_bu, f_ac_pro, 1-f_ac_pro, + f_ac_PHA, f_bu_PHA, f_pro_PHA, 1-f_ac_PHA-f_bu_PHA-f_pro_PHA, + Y_su, Y_aa, Y_fa, Y_c4, Y_pro, Y_ac, Y_h2, Y_po4) + pH_LLs = np.array([pH_limits_aa[0]]*6 + [pH_limits_ac[0], pH_limits_h2[0]]) + pH_ULs = np.array([pH_limits_aa[1]]*6 + [pH_limits_ac[1], pH_limits_h2[1]]) + + ks = np.array((q_ch_hyd, q_pr_hyd, q_li_hyd, + k_su, k_aa, k_fa, k_c4, k_c4, k_pro, k_ac, k_h2, + b_su, b_aa, b_fa, b_c4, b_pro, b_ac, b_h2, + q_PHA, q_PHA, q_PHA, q_PHA, b_PAO, b_PP, b_PHA)) + + Ks = np.array((K_su, K_aa, K_fa, K_c4, K_c4, K_pro, K_ac, K_h2, K_a, K_pp)) + KIs_h2 = np.array((KI_h2_fa, KI_h2_c4, KI_h2_c4, KI_h2_pro)) + K_H_base = np.array(K_H_base) + K_H_dH = np.array(K_H_dH) + Ka_base = np.array([10**(-pKa) for pKa in pKa_base]) + Ka_dH = np.array(Ka_dH) + root = TempState() + # root.data = 10**(-7.4655) + dct = self.__dict__ + dct.update(kwargs) + + self.set_rate_function(rhos_adm1_p_extension) + dct['_parameters'] = dict(zip(cls._stoichio_params, stoichio_vals)) + self.rate_function._params = dict(zip(cls._kinetic_params, + [ks, Ks, pH_ULs, pH_LLs, KS_IN*N_mw, KS_IP*P_mw, + KI_nh3, KIs_h2, Ka_base, Ka_dH, + K_H_base, K_H_dH, kLa, + T_base, self._components, root])) + + return self + + def set_pKas(self, pKas): + '''Set the pKa values of the acid-base reactions at the base temperature.''' + if len(pKas) != 7: + raise ValueError(f'pKas must be an array of 7 elements, one for each ' + f'acid-base pair, not {len(pKas)} elements.') + dct = self.rate_function._params + dct['Ka_base'] = np.array([10**(-pKa) for pKa in pKas]) + + def _find_index(self, process): + isa = isinstance + if isa(process, int): return process + elif isa(process, str): return self.index(process) + elif isa(process, Process): return self.index(process.ID) + else: raise TypeError(f'must input an int or str or :class:`Process`, ' + f'not {type(process)}') + + def set_rate_constant(self, k, process): + '''Set the reaction rate constant [d^(-1)] for a process given its ID.''' + i = self._find_index(process) + self.rate_function._params['rate_constants'][i] = k + + def set_half_sat_K(self, K, process): + '''Set the substrate half saturation coefficient [kg/m3] for a process given its ID.''' + i = self._find_index(process) + self.rate_function._params['half_sat_coeffs'][i-4] = K + + def set_pH_inhibit_bounds(self, process, lower=None, upper=None): + '''Set the upper and/or lower limit(s) of pH inhibition [unitless] for a process given its ID.''' + i = self._find_index(process) + dct = self.rate_function._params + if lower is None: lower = dct['pH_LLs'][i-4] + else: dct['pH_LLs'][i-4] = lower + if upper is None: upper = dct['pH_ULs'][i-4] + else: dct['pH_ULs'][i-4] = upper + if lower >= upper: + raise ValueError(f'lower limit for pH inhibition of {process} must ' + f'be lower than the upper limit, not {[lower, upper]}') + + def set_h2_inhibit_K(self, KI, process): + '''Set the H2 inhibition coefficient [kg/m3] for a process given its ID.''' + i = self._find_index(process) + self.rate_function._params['KIs_h2'][i-6] = KI + + def set_KS_IN(self, K): + '''Set inhibition coefficient for inorganic nitrogen as a secondary + substrate [M nitrogen].''' + self.rate_function._params['KS_IN'] = K * N_mw + + def set_KS_IP(self, K): + '''Set inhibition coefficient for inorganic phosphorous as a secondary + substrate [M phosphorous].''' + self.rate_function._params['KS_IP'] = K * P_mw + + def set_KI_nh3(self, K): + '''Set inhibition coefficient for free ammonia [M].''' + self.rate_function._params['KI_nh3'] = K + + def set_parameters(self, **parameters): + '''Set values to stoichiometric parameters in `ADM1._stoichio_params`.''' + non_stoichio = {} + for k,v in parameters.items(): + if k in self._stoichio_params: + if v >= 0 : self._parameters[k] = v + else: raise ValueError(f'{k} must >= 0, not {v}') + else: non_stoichio[k] = v + if non_stoichio: + warn(f'ignored value setting for non-stoichiometric parameters {non_stoichio}') + self.check_stoichiometric_parameters() + if self._stoichio_lambdified is not None: + self.__dict__['_stoichio_lambdified'] = None + + def check_stoichiometric_parameters(self): + '''Check whether product COD fractions sum up to 1 for each process.''' + stoichio = self.parameters + subst = ('xb', 'su', 'aa', 'fa', 'va', 'bu', 'pro', 'PHA') + for s in subst: + f_tot = sum([stoichio[k] for k in self._stoichio_params[:-7] \ + if k.endswith(s)]) + if f_tot != 1: + raise ValueError(f"the sum of 'f_()_{s}' values must equal 1") From 2c3e5924a93705f6ec6fd35287ba30edcc753bc5 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Thu, 30 Mar 2023 14:51:24 -0500 Subject: [PATCH 085/424] Delete _adm1_modified Changing name of file --- qsdsan/processes/_adm1_modified.py | 820 ----------------------------- 1 file changed, 820 deletions(-) delete mode 100644 qsdsan/processes/_adm1_modified.py diff --git a/qsdsan/processes/_adm1_modified.py b/qsdsan/processes/_adm1_modified.py deleted file mode 100644 index a93dd1c1..00000000 --- a/qsdsan/processes/_adm1_modified.py +++ /dev/null @@ -1,820 +0,0 @@ -# -*- coding: utf-8 -*- -''' -QSDsan: Quantitative Sustainable Design for sanitation and resource recovery systems - -This module is developed by: - Joy Zhang - Saumitra Rai - - -Part of this module is based on the Thermosteam package: -https://github.com/BioSTEAMDevelopmentGroup/thermosteam - -This module is under the University of Illinois/NCSA Open Source License. -Please refer to https://github.com/QSD-Group/QSDsan/blob/main/LICENSE.txt -for license details. -''' - -from thermosteam.utils import chemicals_user -from thermosteam import settings -from chemicals.elements import molecular_weight as get_mw -from qsdsan import Component, Components, Process, Processes, CompiledProcesses -import numpy as np -from qsdsan.utils import ospath, data_path -from scipy.optimize import brenth -from warnings import warn - -__all__ = ('create_adm1_p_extension_cmps', 'ADM1_p_extension', - 'non_compet_inhibit', 'substr_inhibit', - 'T_correction_factor', - 'pH_inhibit', 'Hill_inhibit', - 'rhos_adm1_p_extension') - -_path = ospath.join(data_path, 'process_data/_adm1_p_extension.tsv') -_load_components = settings.get_default_chemicals - -#%% -# ============================================================================= -# ADM1 (with P extension) -specific components -# ============================================================================= - -C_mw = get_mw({'C':1}) -N_mw = get_mw({'N':1}) -P_mw = get_mw({'P':1}) - -def create_adm1_p_extension_cmps(set_thermo=True): - cmps_all = Components.load_default() - - # varies - # X_c = cmps_all.X_OHO.copy('X_c') - # X_c.description = 'Composite' - # X_c.i_C = 0.02786 * C_mw - # X_c.i_N = 0.0376 - - X_ch = Component.from_chemical('X_ch', chemical='glycogen', Tc=1011.4, # glucan - description='Carbohydrates', - measured_as='COD', - particle_size='Particulate', - degradability='Slowly', - organic=True) - # X_ch = cmps_all.X_B_Subst.copy('X_ch') - # X_ch.i_N = 0 - # X_ch.i_C = 0.0313 * C_mw - - # varies - X_pr = cmps_all.X_B_Subst.copy('X_pr') - X_pr.i_N = 0.007 * N_mw - X_pr.i_C = 0.03 * C_mw - - X_li = Component.from_chemical('X_li', chemical='tripalmitin', - description='Lipids', - measured_as='COD', - particle_size='Particulate', - degradability='Slowly', - organic=True) - - # both varies - X_I = cmps_all.X_U_Inf.copy('X_I') - S_I = cmps_all.S_U_Inf.copy('S_I') - X_I.i_C = S_I.i_C = 0.03 * C_mw - X_I.i_N = S_I.i_N = 0.06 - - S_su = Component.from_chemical('S_su', chemical='glucose', - description='Monosaccharides', - measured_as='COD', - particle_size='Soluble', - degradability='Readily', - organic=True) - # S_su = cmps_all.S_F.copy('S_su') - # S_su.i_N = 0 - # S_su.i_C = 0.0313 * 12 - - # varies - S_aa = cmps_all.S_F.copy('S_aa') - S_aa.i_N = 0.007 * N_mw - S_aa.i_P = 0 - S_aa.i_C = 0.03 * C_mw - - S_fa = Component.from_chemical('S_fa', chemical='palmitate', - description='Total long-chain fatty acids', - measured_as='COD', - particle_size='Soluble', - degradability='Readily', - organic=True) - - S_va = Component.from_chemical('S_va', chemical='valerate', - description='Total valerate', - measured_as='COD', - particle_size='Soluble', - degradability='Readily', - organic=True) - - S_bu = Component.from_chemical('S_bu', chemical='butyrate', - description='Total butyrate', - measured_as='COD', - particle_size='Soluble', - degradability='Readily', - organic=True) - - S_pro = cmps_all.S_Prop.copy('S_pro') - S_ac = cmps_all.S_Ac.copy('S_ac') - S_h2 = cmps_all.S_H2.copy('S_h2') - S_ch4 = cmps_all.S_CH4.copy('S_ch4') - - S_IC = Component.from_chemical('S_IC', chemical='CO2', - measured_as='C', - description='Inorganic carbon', - particle_size='Dissolved gas', - degradability='Undegradable', - organic=False) - S_IC.copy_models_from(S_ch4, ('Cn',)) - - S_IN = Component.from_chemical('S_IN', chemical='NH3', - measured_as='N', - description='Inorganic nitrogen', - particle_size='Soluble', - degradability='Undegradable', - organic=False) - - S_IP = cmps_all.S_PO4.copy('S_IP') - - X_su = cmps_all.X_FO.copy('X_su') - X_su.description = 'Biomass uptaking sugars' - - X_aa = cmps_all.X_FO.copy('X_aa') - X_aa.description = 'Biomass uptaking amino acids' - - X_fa = cmps_all.X_FO.copy('X_fa') - X_fa.description = 'Biomass uptaking long chain fatty acids' - - X_c4 = cmps_all.X_FO.copy('X_c4') - X_c4.description = 'Biomass uptaking c4 fatty acids' - - X_pro = cmps_all.X_PRO.copy('X_pro') - X_ac = cmps_all.X_ACO.copy('X_ac') - X_h2 = cmps_all.X_HMO.copy('X_h2') - - X_PHA = cmps_all.X_PAO_PHA.copy('X_PHA') - - X_PP = cmps_all.X_PAO_PP_Lo.copy('X_PP') - - X_PAO = cmps_all.X_PAO.copy('X_PAO') - - S_K = Component.from_chemical('S_K', chemical='K', - measured_as='K', - description='Potassium', - particle_size='Soluble', - degradability='Undegradable', - organic=False) - - S_Mg = Component.from_chemical('S_Mg', chemical='Mg', - measured_as='Mg', - description='Magnesium', - particle_size='Soluble', - degradability='Undegradable', - organic=False) - - for bio in (X_su, X_aa, X_fa, X_c4, X_pro, X_ac, X_h2): - # bio.formula = 'C5H7O2N' - bio.i_C = 0.0313 * C_mw - bio.i_N = 0.08 - - S_cat = cmps_all.S_CAT.copy('S_cat') - S_an = cmps_all.S_AN.copy('S_an') - S_cat.i_mass = S_an.i_mass = 1 - - cmps_adm1_p_extension = Components([S_su, S_aa, S_fa, S_va, S_bu, S_pro, S_ac, S_h2, - S_ch4, S_IC, S_IN, S_IP, S_I, X_ch, X_pr, X_li, - X_su, X_aa, X_fa, X_c4, X_pro, X_ac, X_h2, X_I, - X_PHA, X_PP, X_PAO, S_K, S_Mg, - S_cat, S_an, cmps_all.H2O]) - cmps_adm1_p_extension.default_compile() - if set_thermo: settings.set_thermo(cmps_adm1_p_extension) - return cmps_adm1_p_extension - -# create_adm1_p_extension_cmps() - - -#%% -# ============================================================================= -# kinetic rate functions -# ============================================================================= - -R = 8.3145e-2 # Universal gas constant, [bar/M/K] - -def non_compet_inhibit(Si, Ki): - return Ki/(Ki+Si) - -def substr_inhibit(Si, Ki): - return Si/(Ki+Si) - -def mass2mol_conversion(cmps): - '''conversion factor from kg[measured_as]/m3 to mol[component]/L''' - return cmps.i_mass / cmps.chem_MW - -# def T_correction_factor(T1, T2, theta): -# return np.exp(theta * (T2-T1)) - -def T_correction_factor(T1, T2, delta_H): - return np.exp(delta_H/(R*100) * (1/T1 - 1/T2)) # R converted to SI - -# def calc_Kas(pKas, T_base, T_op, theta): -# pKas = np.asarray(pKas) -# return 10**(-pKas) * T_correction_factor(T_base, T_op, theta) - -# What about this? Any changes needed? (Saumitra) -def acid_base_rxn(h_ion, weak_acids_tot, Kas): - # h, nh4, hco3, ac, pr, bu, va = mols - # S_cat, S_an, S_IN, S_IC, S_ac, S_pro, S_bu, S_va = weak_acids_tot # in M - S_cat, S_an, S_IN = weak_acids_tot[:3] - # Kw, Ka_nh, Ka_co2, Ka_ac, Ka_pr, Ka_bu, Ka_va = Kas - Kw = Kas[0] - oh_ion = Kw/h_ion - nh3, hco3, ac, pro, bu, va = Kas[1:] * weak_acids_tot[2:] / (Kas[1:] + h_ion) - return S_cat + h_ion + (S_IN - nh3) - S_an - oh_ion - hco3 - ac - pro - bu - va - -def fprime_abr(h_ion, weak_acids_tot, Kas): - S_cat, S_an, S_IN = weak_acids_tot[:3] - Kw = Kas[0] - doh_ion = - Kw / h_ion ** 2 - dnh3, dhco3, dac, dpro, dbu, dva = - Kas[1:] * weak_acids_tot[2:] / (Kas[1:] + h_ion)**2 - return 1 + (-dnh3) - doh_ion - dhco3 - dhco3 - dac - dpro - dbu - dva - -def pH_inhibit(pH, ul, ll, lower_only=True): - if lower_only: - # if pH >= ul: return 1 - # else: return exp(-3 * ((pH-ul)/(ul-ll))**2) - low_by = np.minimum(pH-ul, 0) - return np.exp(-3 * (low_by/(ul-ll))**2) - else: - return (1+2*10**(0.5*(ll-ul)))/(1+10**(pH-ul)+10**(ll-pH)) - -def Hill_inhibit(H_ion, ul, ll): - n = 3/(ul-ll) - K = 10**(-(ul+ll)/2) - return 1/(1+(H_ion/K) ** n) - -rhos = np.zeros(28) # 28 kinetic processes (25 as defined in modified ADM1 + 3 for gases) -Cs = np.empty(25) # 25 processes as defined in modified ADM1 - -def rhos_adm1_p_extension(state_arr, params): - ks = params['rate_constants'] - Ks = params['half_sat_coeffs'] - cmps = params['components'] - # n = len(cmps) - pH_ULs = params['pH_ULs'] - pH_LLs = params['pH_LLs'] - KS_IN = params['KS_IN'] - KS_IP = params['KS_IP'] - KI_nh3 = params['KI_nh3'] - KIs_h2 = params['KIs_h2'] - KHb = params['K_H_base'] - Kab = params['Ka_base'] - KH_dH = params['K_H_dH'] - Ka_dH = params['Ka_dH'] - kLa = params['kLa'] - T_base = params['T_base'] - root = params['root'] - - # state_arr_cmps stated just for readability of code - # state_arr_cmps = [S_su, S_aa, S_fa, S_va, S_bu, S_pro, S_ac, S_h2, S_ch4, S_IC, S_IN, - # S_IP, S_I, X_ch, X_pr, X_li, X_su, X_aa, X_fa, X_c4, X_pro, X_ac, - # X_h2, X_I, X_PHA, X_PP, X_PAO, S_K, S_Mg] - - # Cs_ids = cmps.indices(['X_ch', 'X_pr', 'X_li', 'X_su', 'X_aa', - # 'X_fa', 'X_c4', 'X_c4', 'X_pro', 'X_ac', 'X_h2', - # 'X_su', 'X_aa', 'X_fa', 'X_c4', 'X_pro', 'X_ac', 'X_h2', - # 'X_PAO', 'X_PAO', 'X_PAO', 'X_PAO', 'X_PAO', 'X_PP', 'X_PHA']) - # Cs = state_arr[Cs_ids] - Cs[:7] = state_arr[13:20] - Cs[7:11] = state_arr[19:23] - Cs[11:18] = state_arr[16:23] - Cs[18:23] = state_arr[26] - Cs[23] = state_arr[25] - Cs[24] = state_arr[24] - - # substrates_ids = cmps.indices(['S_su', 'S_aa', 'S_fa', 'S_va', - # 'S_bu', 'S_pro', 'S_ac', 'S_h2']) - # substrates = state_arr[substrates_ids] - substrates = state_arr[:8] - - # S_va, S_bu, S_h2, S_IN = state_arr[cmps.indices(['S_va', 'S_bu', 'S_h2', 'S_IN'])] - # S_va, S_bu, S_h2, S_ch4, S_IC, S_IN = state_arr[[3,4,7,8,9,10]] - S_va, S_bu, S_h2, S_IN, S_IP = state_arr[[3,4,7,10,11]] - unit_conversion = mass2mol_conversion(cmps) - cmps_in_M = state_arr[:27] * unit_conversion - weak_acids = cmps_in_M[[24, 25, 10, 9, 6, 5, 4, 3]] - - T_op = state_arr[-1] - biogas_S = state_arr[7:10].copy() - biogas_p = R * T_op * state_arr[27:30] - Kas = Kab * T_correction_factor(T_base, T_op, Ka_dH) - KH = KHb * T_correction_factor(T_base, T_op, KH_dH) / unit_conversion[7:10] - - rhos[:-3] = ks * Cs - rhos[3:11] *= substr_inhibit(substrates, Ks) - if S_va > 0: rhos[6] *= 1/(1+S_bu/S_va) - if S_bu > 0: rhos[7] *= 1/(1+S_va/S_bu) - - # substrates_ids = cmps.indices(['S_va', 'S_bu', 'S_pro', 'S_ac']) - # substrates_modified = state_arr[substrates_ids] - substrates_modified = state_arr[3:7] - # Ka = K_a - Ka = Ks[-2] - rhos[18:22] *= substr_inhibit(substrates_modified, Ka) - - # PP_PAO = X_PP/X_PAO - PP_PAO = state_arr[25]/state_arr[26] - # Kpp = K_pp - Kpp = Ks[-1] - rhos[18:22] *= substr_inhibit(PP_PAO, Kpp) - - # Multiplication by {Sva, Sbu, Spro, Sac}/(Sva + Sbu + Spro + Sac) - transformation_array = state_arr[3:7]/sum(state_arr[3:7]) - rhos[18:22] *= transformation_array - - h = brenth(acid_base_rxn, 1e-14, 1.0, - args=(weak_acids, Kas), - xtol=1e-12, maxiter=100) - - nh3 = Kas[1] * weak_acids[2] / (Kas[1] + h) - co2 = weak_acids[3] - Kas[2] * weak_acids[3] / (Kas[2] + h) - biogas_S[-1] = co2 / unit_conversion[9] - - Iph = Hill_inhibit(h, pH_ULs, pH_LLs) - Ih2 = non_compet_inhibit(S_h2, KIs_h2) - root.data = [-np.log10(h), Iph, Ih2] - rhos[3:11] *= Iph * substr_inhibit(S_IN, KS_IN) * substr_inhibit(S_IP, KS_IP) - rhos[5:9] *= Ih2 - # rhos[4:12] *= Hill_inhibit(h, pH_ULs, pH_LLs) * substr_inhibit(S_IN, KS_IN) - # rhos[6:10] *= non_compet_inhibit(S_h2, KIs_h2) - rhos[9] *= non_compet_inhibit(nh3, KI_nh3) - - - - rhos[-3:] = kLa * (biogas_S - KH * biogas_p) - # print(rhos) - return rhos -#%% -# ============================================================================= -# ADM1_p_extension class -# ============================================================================= -class TempState: - def __init__(self): - self.data = [] - - # def append(self, value): - # self.data += [value] - -@chemicals_user -class ADM1_p_extension(CompiledProcesses): - """ - Anaerobic Digestion Model No.1. [1]_, [2]_, [3]_ - - Parameters - ---------- - components : class:`CompiledComponents`, optional - Components corresponding to each entry in the stoichiometry array, - defaults to thermosteam.settings.chemicals. - path : str, optional - Alternative file path for the Petersen matrix. The default is None. - N_xc : float, optional - Nitrogen content of composite materials [kmol N/kg COD]. The default is 2.686e-3. - N_I : float, optional - Nitrogen content of inert organics [kmol N/kg COD]. The default is 4.286e-3. - N_aa : float, optional - Nitrogen content of amino acids [kmol N/kg COD]. The default is 7e-3. - f_sI_xb : float, optional - fraction of soluble inerts from biomass. The default is 0. - f_ch_xb : float, optional - fraction of carbohydrates from biomass. The default is 0.275. - f_pr_xb : float, optional - fraction of proteins from biomass. The default is 0.275. - f_li_xb : float, optional - fraction of lipids from biomass. The default is 0.35. - f_xI_xb : float, optional - fraction of particulate inerts from biomass. The default is 0.1. - f_fa_li : float, optional - Fraction of long chain fatty acids (LCFAs) from hydrolysis of lipids - [kg COD/kg COD]. The default is 0.95. - f_bu_su : float, optional - Fraction of butyrate from sugars [kg COD/kg COD]. The default is 0.13. - f_pro_su : float, optional - Fraction of propionate from sugars [kg COD/kg COD]. The default is 0.27. - f_ac_su : float, optional - Fraction of acetate from sugars [kg COD/kg COD]. The default is 0.41. - f_va_aa : float, optional - Fraction of valerate from amino acids [kg COD/kg COD]. The default is 0.23. - f_bu_aa : float, optional - Fraction of butyrate from amino acids [kg COD/kg COD]. The default is 0.26. - f_pro_aa : float, optional - Fraction of propionate from amino acids [kg COD/kg COD]. The default is 0.05. - f_ac_aa : float, optional - Fraction of acetate from amino acids [kg COD/kg COD]. The default is 0.4. - f_ac_fa : float, optional - Fraction of acetate from LCFAs [kg COD/kg COD]. The default is 0.7. - f_pro_va : float, optional - Fraction of propionate from LCFAs [kg COD/kg COD]. The default is 0.54. - f_ac_va : float, optional - Fraction of acetate from valerate [kg COD/kg COD]. The default is 0.31. - f_ac_bu : float, optional - Fraction of acetate from butyrate [kg COD/kg COD]. The default is 0.8. - f_ac_pro : float, optional - Fraction of acetate from propionate [kg COD/kg COD]. The default is 0.57. - f_ac_PHA : float, optional - Yield of acetate on PHA [kg COD/kg COD]. The default is 0.4. - f_bu_PHA : float, optional - Yield of butyrate on PHA [kg COD/kg COD]. The default is 0.1. - f_pro_PHA : float, optional - Yield of propionate on PHA [kg COD/kg COD]. The default is 0.4. - f_va_PHA : float, optional - Yield of valerate on PHA [kg COD/kg COD]. The default is 0.1. - Y_su : float, optional - Biomass yield of sugar uptake [kg COD/kg COD]. The default is 0.1. - Y_aa : float, optional - Biomass yield of amino acid uptake [kg COD/kg COD]. The default is 0.08. - Y_fa : float, optional - Biomass yield of LCFA uptake [kg COD/kg COD]. The default is 0.06. - Y_c4 : float, optional - Biomass yield of butyrate or valerate uptake [kg COD/kg COD]. - The default is 0.06. - Y_pro : float, optional - Biomass yield of propionate uptake [kg COD/kg COD]. The default is 0.04. - Y_ac : float, optional - Biomass yield of acetate uptake [kg COD/kg COD]. The default is 0.05. - Y_h2 : float, optional - Biomass yield of H2 uptake [kg COD/kg COD]. The default is 0.06. - Y_po4 : float, optional - Yield of biomass on phosphate [kmol P/kg COD]. The default is 0.013. - q_dis : float, optional - Composites disintegration rate constant [d^(-1)]. The default is 0.5. - q_ch_hyd : float, optional - Carbohydrate hydrolysis rate constant [d^(-1)]. The default is 10. - q_pr_hyd : float, optional - Protein hydrolysis rate constant [d^(-1)]. The default is 10. - q_li_hyd : float, optional - Lipid hydrolysis rate constant [d^(-1)]. The default is 10. - k_su : float, optional - Sugar uptake rate constant [d^(-1)]. The default is 30. - k_aa : float, optional - Amino acid uptake rate constant [d^(-1)]. The default is 50. - k_fa : float, optional - LCFA uptake rate constant [d^(-1)]. The default is 6. - k_c4 : float, optional - Butyrate or valerate uptake rate constant [d^(-1)]. The default is 20. - k_pro : float, optional - Propionate uptake rate constant [d^(-1)]. The default is 13. - k_ac : float, optional - Acetate uptake rate constant [d^(-1)]. The default is 8. - k_h2 : float, optional - H2 uptake rate constant [d^(-1)]. The default is 35. - K_su : float, optional - Half saturation coefficient of sugar uptake [kg COD/m3]. - The default is 0.5. - K_aa : float, optional - Half saturation coefficient of amino acid uptake [kg COD/m3]. - The default is 0.3. - K_fa : float, optional - Half saturation coefficient of LCFA uptake [kg COD/m3]. - The default is 0.4. - K_c4 : float, optional - Half saturation coefficient of butyrate or valerate uptake [kg COD/m3]. - The default is 0.2. - K_pro : float, optional - Half saturation coefficient of propionate uptake [kg COD/m3]. - The default is 0.1. - K_ac : float, optional - Half saturation coefficient of acetate uptake [kg COD/m3]. - The default is 0.15. - K_h2 : float, optional - Half saturation coefficient of H2 uptake [kg COD/m3]. - The default is 7e-6. - K_a : float, optional - Saturation coefficient for acetate [kg COD/m3]. - The default is 0.004. - K_pp : float, optional - Saturation coefficient for polyphosphate [kg COD/m3]. - The default is 0.00032. - b_su : float, optional - Decay rate constant of sugar-uptaking biomass [d^(-1)]. - The default is 0.02. - b_aa : float, optional - Decay rate constant of amino-acid-uptaking biomass [d^(-1)]. - The default is 0.02. - b_fa : float, optional - Decay rate constant of LCFA-uptaking biomass [d^(-1)]. - The default is 0.02. - b_c4 : float, optional - Decay rate constant of valerate- or butyrate-uptaking biomass [d^(-1)]. - The default is 0.02. - b_pro : float, optional - Decay rate constant of propionate-uptaking biomass [d^(-1)]. - The default is 0.02. - b_ac : float, optional - Decay rate constant of acetate-uptaking biomass [d^(-1)]. - The default is 0.02. - b_h2 : float, optional - Decay rate constant of H2-uptaking biomass [d^(-1)]. The default is 0.02. - q_PHA : float, optional - Rate constant for storage of PHA [d^(-1)]. The default is 3. - b_PAO : float, optional - Lysis rate of PAOs [d^(-1)]. The default is 0.2. - b_PP : float, optional - Lysis rate of polyphosphates [d^(-1)]. The default is 0.2. - b_PHA : float, optional - Lysis rate of PHAs [d^(-1)]. The default is 0.2. - KI_h2_fa : float, optional - H2 inhibition coefficient for LCFA uptake [kg COD/m3]. The default is 5e-6. - KI_h2_c4 : float, optional - H2 inhibition coefficient for butyrate or valerate uptake [kg COD/m3]. - The default is 1e-5. - KI_h2_pro : float, optional - H2 inhibition coefficient for propionate uptake [kg COD/m3]. - The default is 3.5e-6. - KI_nh3 : float, optional - Free ammonia inhibition coefficient for acetate uptake [M]. - The default is 1.8e-3. - KS_IN : float, optional - Inorganic nitrogen (nutrient) inhibition coefficient for soluble - substrate uptake [M]. The default is 1e-4. - KS_IP : float, optional - P limitation for inorganic phosphorous [kmol P/m3]. - The default is 2e-5. - pH_limits_aa : 2-tuple, optional - Lower and upper limits of pH inhibition for acidogens and acetogens, - unitless. The default is (4,5.5). - pH_limits_ac : 2-tuple, optional - Lower and upper limits of pH inhibition for aceticlastic methanogens, - unitless. The default is (6,7). - pH_limits_h2 : 2-tuple, optional - Lower and upper limits of pH inhibition for H2-utilizing methanogens, - unitless. The default is (5,6). - T_base : float, optional - Base temperature for kinetic parameters [K]. The default is 298.15. - pKa_base : iterable[float], optional - pKa (equilibrium coefficient) values of acid-base pairs at the base - temperature, unitless, following the order of `ADM1._acid_base_pairs`. - The default is [14, 9.25, 6.35, 4.76, 4.88, 4.82, 4.86]. - Ka_dH : iterable[float], optional - Heat of reaction of each acid-base pair at base temperature [J/mol], - following the order of `ADM1._acid_base_pairs`. The default is - [55900, 51965, 7646, 0, 0, 0, 0]. - kLa : float, optional - Liquid-gas mass transfer coefficient [d^(-1)]. The default is 200. - K_H_base : iterable[float], optional - Henry's Law coefficients of biogas species at the base temperature - [M dissolved in liquid/bar]. Follows the order of `ADM1._biogas_IDs`. - The default is [7.8e-4, 1.4e-3, 3.5e-2]. - K_H_dH : iterable[float], optional - Heat of reaction of liquid-gas transfer of biogas species [J/mol]. - Follows the order of `ADM1._biogas_IDs`. The default is - [-4180, -14240, -19410]. - - Examples - -------- - >>> from qsdsan import processes as pc - >>> cmps = pc.create_adm1_p_extension_cmps() - >>> adm1 = pc.ADM1_p_extension() - >>> adm1.show() - ADM1([hydrolysis_carbs, hydrolysis_proteins, hydrolysis_lipids, uptake_sugars, uptake_amino_acids, uptake_LCFA, uptake_valerate, uptake_butyrate, uptake_propionate, uptake_acetate, uptake_h2, decay_Xsu, decay_Xaa, decay_Xfa, decay_Xc4, decay_Xpro, decay_Xac, decay_Xh2, storage_Sva_in_XPHA, storage_Sbu_in_XPHA, storage_Spro_in_XPHA, storage_Sac_in_XPHA, lysis_XPAO, lysis_XPP, lysis_XPHA, h2_transfer, ch4_transfer, IC_transfer]) - - References - ---------- - .. [1] Batstone, D. J.; Keller, J.; Angelidaki, I.; Kalyuzhnyi, S. V; - Pavlostathis, S. G.; Rozzi, A.; Sanders, W. T. M.; Siegrist, H.; - Vavilin, V. A. The IWA Anaerobic Digestion Model No 1 (ADM1). - Water Sci. Technol. 2002, 45 (10), 65–73. - .. [2] Rosen, C.; Jeppsson, U. Aspects on ADM1 Implementation within - the BSM2 Framework; Lund, 2006. - .. [3] Flores-Alsina, X.; Solon, K.; Kazadi Mbamba, C.; Tait, S.; - Gernaey, K. V.; Jeppsson, U.; Batstone, D. J. - Modelling phosphorus (P), sulfur (S) and iron (FE) interactions for - dynamic simulations of anaerobic digestion processes. Water Research. 2016, - 95, 370–382. - """ - - _stoichio_params = ('f_sI_xb', 'f_ch_xb', 'f_pr_xb', 'f_li_xb', 'f_xI_xb', - 'f_fa_li', 'f_bu_su', 'f_pro_su', 'f_ac_su', 'f_h2_su', - 'f_va_aa', 'f_bu_aa', 'f_pro_aa', 'f_ac_aa', 'f_h2_aa', - 'f_ac_fa', 'f_h2_fa', 'f_pro_va', 'f_ac_va', 'f_h2_va', - 'f_ac_bu', 'f_h2_bu', 'f_ac_pro', 'f_h2_pro', - 'f_ac_PHA', 'f_bu_PHA', 'f_pro_PHA', 'f_va_PHA', - 'Y_su', 'Y_aa', 'Y_fa', 'Y_c4', 'Y_pro', 'Y_ac', 'Y_h2', 'Y_po4') - _kinetic_params = ('rate_constants', 'half_sat_coeffs', 'pH_ULs', 'pH_LLs', - 'KS_IN', 'KS_IP', 'KI_nh3', 'KIs_h2', - 'Ka_base', 'Ka_dH', 'K_H_base', 'K_H_dH', 'kLa', - 'T_base', 'components', 'root') - _acid_base_pairs = (('H+', 'OH-'), ('NH4+', 'NH3'), ('CO2', 'HCO3-'), - ('HAc', 'Ac-'), ('HPr', 'Pr-'), - ('HBu', 'Bu-'), ('HVa', 'Va-')) - - _biogas_IDs = ('S_h2', 'S_ch4', 'S_IC') - - def __new__(cls, components=None, path=None, N_xc=2.686e-3, N_I=4.286e-3, N_aa=7e-3, - f_sI_xb=0, f_ch_xb=0.275, f_pr_xb=0.275, f_li_xb=0.350, - f_fa_li=0.95, f_bu_su=0.13, f_pro_su=0.27, f_ac_su=0.41, - f_va_aa=0.23, f_bu_aa=0.26, f_pro_aa=0.05, f_ac_aa=0.4, - f_ac_fa=0.7, f_pro_va=0.54, f_ac_va=0.31, f_ac_bu=0.8, f_ac_pro=0.57, - f_ac_PHA=0.4, f_bu_PHA=0.1, f_pro_PHA=0.4, - Y_su=0.1, Y_aa=0.08, Y_fa=0.06, Y_c4=0.06, Y_pro=0.04, Y_ac=0.05, Y_h2=0.06, Y_po4=0.013, - q_dis=0.5, q_ch_hyd=10, q_pr_hyd=10, q_li_hyd=10, - k_su=30, k_aa=50, k_fa=6, k_c4=20, k_pro=13, k_ac=8, k_h2=35, - K_su=0.5, K_aa=0.3, K_fa=0.4, K_c4=0.2, K_pro=0.1, K_ac=0.15, K_h2=7e-6, K_a=4e-3, K_pp=32e-5, - b_su=0.02, b_aa=0.02, b_fa=0.02, b_c4=0.02, b_pro=0.02, b_ac=0.02, b_h2=0.02, - q_PHA=3, b_PAO=0.2, b_PP=0.2, b_PHA=0.2, - KI_h2_fa=5e-6, KI_h2_c4=1e-5, KI_h2_pro=3.5e-6, KI_nh3=1.8e-3, KS_IN=1e-4, KS_IP=2e-5, - pH_limits_aa=(4,5.5), pH_limits_ac=(6,7), pH_limits_h2=(5,6), - T_base=298.15, pKa_base=[14, 9.25, 6.35, 4.76, 4.88, 4.82, 4.86], - Ka_dH=[55900, 51965, 7646, 0, 0, 0, 0], - kLa=200, K_H_base=[7.8e-4, 1.4e-3, 3.5e-2], - K_H_dH=[-4180, -14240, -19410], - **kwargs): - - cmps = _load_components(components) - # Sure that some things are missing here! (Saumitra) - cmps.X_c.i_N = N_xc * N_mw - cmps.X_I.i_N = cmps.S_I.i_N = N_I * N_mw - cmps.S_aa.i_N = cmps.X_pr.i_N = N_aa * N_mw - - if not path: path = _path - self = Processes.load_from_file(path, - components=cmps, - conserved_for=('C', 'N', 'P'), - parameters=cls._stoichio_params, - compile=False) - - # Need to add 5 processes separately for stoichiometry. See ASM2d for reference. - if path == _path: - _p19 = Process('storage_Sva_in_XPHA', - 'S_va + [Y_po4]X_PP -> X_PHA + [?]S_K + [?]S_Mg', - components=cmps, - ref_component='X_PHA', - rate_equation='q_PHA * S_va/(K_a+S_va) * (X_PP/X_PAO)/(K_PP+(X_PP/X_PAO)) * X_PAO * S_va/(S_va+S_bu+S_pro+S_ac)', - parameters=('Y_po4', 'q_PHA', 'K_a', 'K_PP'), - conserved_for=('K', 'Mg')) - - _p20 = Process('storage_Sbu_in_XPHA', - 'S_bu + [Y_po4]X_PP -> X_PHA + [?]S_K + [?]S_Mg', - components=cmps, - ref_component='X_PHA', - rate_equation='q_PHA * S_bu/(K_a+S_bu) * (X_PP/X_PAO)/(K_PP+(X_PP/X_PAO)) * X_PAO * S_bu/(S_va+S_bu+S_pro+S_ac)', - parameters=('Y_po4', 'q_PHA', 'K_a', 'K_PP'), - conserved_for=('K', 'Mg')) - - _p21 = Process('storage_Spro_in_XPHA', - 'S_pro + [Y_po4]X_PP -> X_PHA + [?]S_K + [?]S_Mg', - components=cmps, - ref_component='X_PHA', - rate_equation='q_PHA * S_pro/(K_a+S_pro) * (X_PP/X_PAO)/(K_PP+(X_PP/X_PAO)) * X_PAO * S_pro/(S_va+S_bu+S_pro+S_ac)', - parameters=('Y_po4', 'q_PHA', 'K_a', 'K_PP'), - conserved_for=('K', 'Mg')) - - _p22 = Process('storage_Sac_in_XPHA', - 'S_ac + [Y_po4]X_PP -> X_PHA + [?]S_K + [?]S_Mg', - components=cmps, - ref_component='X_PHA', - rate_equation='q_PHA * S_ac/(K_a+S_ac) * (X_PP/X_PAO)/(K_PP+(X_PP/X_PAO)) * X_PAO * S_ac/(S_va+S_bu+S_pro+S_ac)', - parameters=('Y_po4', 'q_PHA', 'K_a', 'K_PP'), - conserved_for=('K', 'Mg')) - - _p24 = Process('lysis_XPP', - 'X_pp -> [?]S_K + [?]S_Mg', - components=cmps, - ref_component='X_PP', - rate_equation='b_PP*X_PP', - parameters=('b_PP'), - conserved_for=('K', 'Mg')) - - self.insert(18, _p19) - self.insert(19, _p20) - self.insert(20, _p21) - self.insert(21, _p22) - self.insert(23, _p24) - - gas_transfer = [] - for i in cls._biogas_IDs: - new_p = Process('%s_transfer' % i.lstrip('S_'), - reaction={i:-1}, - ref_component=i, - conserved_for=(), - parameters=()) - gas_transfer.append(new_p) - self.extend(gas_transfer) - self.compile(to_class=cls) - - stoichio_vals = (f_sI_xb, f_ch_xb, f_pr_xb, f_li_xb, 1-f_sI_xb-f_ch_xb-f_pr_xb-f_li_xb, - f_fa_li, f_bu_su, f_pro_su, f_ac_su, 1-f_bu_su-f_pro_su-f_ac_su, - f_va_aa, f_bu_aa, f_pro_aa, f_ac_aa, 1-f_va_aa-f_bu_aa-f_pro_aa-f_ac_aa, - f_ac_fa, 1-f_ac_fa, f_pro_va, f_ac_va, 1-f_pro_va-f_ac_va, - f_ac_bu, 1-f_ac_bu, f_ac_pro, 1-f_ac_pro, - f_ac_PHA, f_bu_PHA, f_pro_PHA, 1-f_ac_PHA-f_bu_PHA-f_pro_PHA, - Y_su, Y_aa, Y_fa, Y_c4, Y_pro, Y_ac, Y_h2, Y_po4) - pH_LLs = np.array([pH_limits_aa[0]]*6 + [pH_limits_ac[0], pH_limits_h2[0]]) - pH_ULs = np.array([pH_limits_aa[1]]*6 + [pH_limits_ac[1], pH_limits_h2[1]]) - - ks = np.array((q_ch_hyd, q_pr_hyd, q_li_hyd, - k_su, k_aa, k_fa, k_c4, k_c4, k_pro, k_ac, k_h2, - b_su, b_aa, b_fa, b_c4, b_pro, b_ac, b_h2, - q_PHA, q_PHA, q_PHA, q_PHA, b_PAO, b_PP, b_PHA)) - - Ks = np.array((K_su, K_aa, K_fa, K_c4, K_c4, K_pro, K_ac, K_h2, K_a, K_pp)) - KIs_h2 = np.array((KI_h2_fa, KI_h2_c4, KI_h2_c4, KI_h2_pro)) - K_H_base = np.array(K_H_base) - K_H_dH = np.array(K_H_dH) - Ka_base = np.array([10**(-pKa) for pKa in pKa_base]) - Ka_dH = np.array(Ka_dH) - root = TempState() - # root.data = 10**(-7.4655) - dct = self.__dict__ - dct.update(kwargs) - - self.set_rate_function(rhos_adm1_p_extension) - dct['_parameters'] = dict(zip(cls._stoichio_params, stoichio_vals)) - self.rate_function._params = dict(zip(cls._kinetic_params, - [ks, Ks, pH_ULs, pH_LLs, KS_IN*N_mw, KS_IP*P_mw, - KI_nh3, KIs_h2, Ka_base, Ka_dH, - K_H_base, K_H_dH, kLa, - T_base, self._components, root])) - - return self - - def set_pKas(self, pKas): - '''Set the pKa values of the acid-base reactions at the base temperature.''' - if len(pKas) != 7: - raise ValueError(f'pKas must be an array of 7 elements, one for each ' - f'acid-base pair, not {len(pKas)} elements.') - dct = self.rate_function._params - dct['Ka_base'] = np.array([10**(-pKa) for pKa in pKas]) - - def _find_index(self, process): - isa = isinstance - if isa(process, int): return process - elif isa(process, str): return self.index(process) - elif isa(process, Process): return self.index(process.ID) - else: raise TypeError(f'must input an int or str or :class:`Process`, ' - f'not {type(process)}') - - def set_rate_constant(self, k, process): - '''Set the reaction rate constant [d^(-1)] for a process given its ID.''' - i = self._find_index(process) - self.rate_function._params['rate_constants'][i] = k - - def set_half_sat_K(self, K, process): - '''Set the substrate half saturation coefficient [kg/m3] for a process given its ID.''' - i = self._find_index(process) - self.rate_function._params['half_sat_coeffs'][i-4] = K - - def set_pH_inhibit_bounds(self, process, lower=None, upper=None): - '''Set the upper and/or lower limit(s) of pH inhibition [unitless] for a process given its ID.''' - i = self._find_index(process) - dct = self.rate_function._params - if lower is None: lower = dct['pH_LLs'][i-4] - else: dct['pH_LLs'][i-4] = lower - if upper is None: upper = dct['pH_ULs'][i-4] - else: dct['pH_ULs'][i-4] = upper - if lower >= upper: - raise ValueError(f'lower limit for pH inhibition of {process} must ' - f'be lower than the upper limit, not {[lower, upper]}') - - def set_h2_inhibit_K(self, KI, process): - '''Set the H2 inhibition coefficient [kg/m3] for a process given its ID.''' - i = self._find_index(process) - self.rate_function._params['KIs_h2'][i-6] = KI - - def set_KS_IN(self, K): - '''Set inhibition coefficient for inorganic nitrogen as a secondary - substrate [M nitrogen].''' - self.rate_function._params['KS_IN'] = K * N_mw - - def set_KS_IP(self, K): - '''Set inhibition coefficient for inorganic phosphorous as a secondary - substrate [M phosphorous].''' - self.rate_function._params['KS_IP'] = K * P_mw - - def set_KI_nh3(self, K): - '''Set inhibition coefficient for free ammonia [M].''' - self.rate_function._params['KI_nh3'] = K - - def set_parameters(self, **parameters): - '''Set values to stoichiometric parameters in `ADM1._stoichio_params`.''' - non_stoichio = {} - for k,v in parameters.items(): - if k in self._stoichio_params: - if v >= 0 : self._parameters[k] = v - else: raise ValueError(f'{k} must >= 0, not {v}') - else: non_stoichio[k] = v - if non_stoichio: - warn(f'ignored value setting for non-stoichiometric parameters {non_stoichio}') - self.check_stoichiometric_parameters() - if self._stoichio_lambdified is not None: - self.__dict__['_stoichio_lambdified'] = None - - def check_stoichiometric_parameters(self): - '''Check whether product COD fractions sum up to 1 for each process.''' - stoichio = self.parameters - subst = ('xb', 'su', 'aa', 'fa', 'va', 'bu', 'pro', 'PHA') - for s in subst: - f_tot = sum([stoichio[k] for k in self._stoichio_params[:-7] \ - if k.endswith(s)]) - if f_tot != 1: - raise ValueError(f"the sum of 'f_()_{s}' values must equal 1") From c9a03b1e2f3da8f887d1359a352b667f53b4754c Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Thu, 30 Mar 2023 15:04:55 -0500 Subject: [PATCH 086/424] Added adm1_p_extension to processes Added ADM1 with P extension to list of processes --- qsdsan/processes/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/qsdsan/processes/__init__.py b/qsdsan/processes/__init__.py index 9d284ec4..089edfd4 100644 --- a/qsdsan/processes/__init__.py +++ b/qsdsan/processes/__init__.py @@ -17,6 +17,7 @@ from ._asm1 import * from ._asm2d import * from ._adm1 import * +from ._adm1_p_extension import * from ._decay import * from ._kinetic_reaction import * @@ -25,6 +26,7 @@ _asm1, _asm2d, _adm1, + _adm1_p_extension, _decay, _kinetic_reaction, ) @@ -34,6 +36,7 @@ *_asm1.__all__, *_asm2d.__all__, *_adm1.__all__, + *_adm1_p_extension.__all__, *_decay.__all__, *_kinetic_reaction.__all__, ) \ No newline at end of file From 19fa87e6e1123a73baf151fc5914df5523a7d25e Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Thu, 30 Mar 2023 15:39:26 -0500 Subject: [PATCH 087/424] Corrected typo Replaced 'X_pp' with 'X_PP' --- qsdsan/processes/_adm1_p_extension.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/qsdsan/processes/_adm1_p_extension.py b/qsdsan/processes/_adm1_p_extension.py index a93dd1c1..2dba7f81 100644 --- a/qsdsan/processes/_adm1_p_extension.py +++ b/qsdsan/processes/_adm1_p_extension.py @@ -378,8 +378,6 @@ class ADM1_p_extension(CompiledProcesses): defaults to thermosteam.settings.chemicals. path : str, optional Alternative file path for the Petersen matrix. The default is None. - N_xc : float, optional - Nitrogen content of composite materials [kmol N/kg COD]. The default is 2.686e-3. N_I : float, optional Nitrogen content of inert organics [kmol N/kg COD]. The default is 4.286e-3. N_aa : float, optional @@ -632,7 +630,7 @@ def __new__(cls, components=None, path=None, N_xc=2.686e-3, N_I=4.286e-3, N_aa=7 cmps = _load_components(components) # Sure that some things are missing here! (Saumitra) - cmps.X_c.i_N = N_xc * N_mw + # cmps.X_c.i_N = N_xc * N_mw cmps.X_I.i_N = cmps.S_I.i_N = N_I * N_mw cmps.S_aa.i_N = cmps.X_pr.i_N = N_aa * N_mw @@ -678,7 +676,7 @@ def __new__(cls, components=None, path=None, N_xc=2.686e-3, N_I=4.286e-3, N_aa=7 conserved_for=('K', 'Mg')) _p24 = Process('lysis_XPP', - 'X_pp -> [?]S_K + [?]S_Mg', + 'X_PP -> [?]S_K + [?]S_Mg', components=cmps, ref_component='X_PP', rate_equation='b_PP*X_PP', From 03e2711ad72fac90ab352d8e373149e1948affee Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Fri, 31 Mar 2023 09:47:33 -0500 Subject: [PATCH 088/424] Some changes Some changes --- qsdsan/sanunits/ipython.html | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 qsdsan/sanunits/ipython.html diff --git a/qsdsan/sanunits/ipython.html b/qsdsan/sanunits/ipython.html new file mode 100644 index 00000000..d7f4813d --- /dev/null +++ b/qsdsan/sanunits/ipython.html @@ -0,0 +1,16 @@ + + + + +

Python 3.9.12 (main, Apr 4 2022, 05:22:27) [MSC v.1916 64 bit (AMD64)]

+

Type "copyright", "credits" or "license" for more information.

+


+

IPython 8.2.0 -- An enhanced Interactive Python.

+


Restarting kernel...

+ + +
+


+

In [1]:

\ No newline at end of file From 880761711f09aba3b69f83dc3d7dd618157ee3bc Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Fri, 31 Mar 2023 14:33:40 -0500 Subject: [PATCH 089/424] Updated Gas extraction membrane Updated state and dstate functions in the gas extraction membrane --- qsdsan/sanunits/_membrane_gas_extraction.py | 25 +++++++++------------ 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/qsdsan/sanunits/_membrane_gas_extraction.py b/qsdsan/sanunits/_membrane_gas_extraction.py index eddfc9b3..af04353f 100644 --- a/qsdsan/sanunits/_membrane_gas_extraction.py +++ b/qsdsan/sanunits/_membrane_gas_extraction.py @@ -293,18 +293,17 @@ def _update_state(self): numGas = len(self.GasID) # Need to add effluent streams for liquid (this includes all cmps of influent ws) and gas. - # The multiplication of any of the first n-1 array element with last element should give out kg/hr values. + # The multiplication of any of the first n-1 array element with last element should give out g/day values. self._outs[0].state = np.zeros(len(cmps) + 1) # The of the effluent gas in extraction membrane is the difference # between lumen concentration in the last and first segment gas_state_in_unit = self._state[ -2*numGas: -numGas] - self._state[ :numGas] # in mol/m3 - gas_state_in_unit = (gas_state_in_unit*cmps.chem_MW[self.idx])/cmps.i_mass[self.idx] # (mol/m3)*(g/mol) = g/m3 = mg/l + Molar_flow_gases = self._ins_QC[-1]*gas_state_in_unit # (m3/day)*(mol/m3) = mol/day + Mass_flow_gases = Molar_flow_gases*cmps.chem_MW[self.idx] #(mol/day)*(g/mol) = (g/day) - self._outs[0].state[idx] = gas_state_in_unit - - # Q_gas = ??Ian?? - # self._outs[0].state[-1] = Q_gas + self._outs[0].state[idx] = Mass_flow_gases # (g/day) + self._outs[0].state[-1] = 1 #(So the mutiplication with Q would give out g/day values) self._outs[1].state = np.zeros(len(cmps) + 1) # The state of effluent Liquid stream is simply the concentration of @@ -324,18 +323,16 @@ def _update_dstate(self): numGas = len(self.GasID) idx = self.idx - # Need to add effluent streams for liquid (this includes all cmps of influent ws) and gas. - # The multiplication of any of the first n-1 array element with last element should give out kg/hr values. - + + self._outs[0].dstate = np.zeros(len(cmps) + 1) # The of the effluent gas in extraction membrane is the difference # between lumen concentration in the last and first segment gas_dstate_in_unit = self._dstate[ -2*numGas: -numGas] - self._dstate[ :numGas] # in mol/m3 - gas_dstate_in_unit = (gas_dstate_in_unit*cmps.chem_MW[self.idx])/cmps.i_mass[self.idx] # (mol/m3)*(g/mol) = g/m3 = mg/l + Molar_dflow_gases = self._ins_dQC[-1]*gas_dstate_in_unit # (m3/day)*(mol/m3) = mol/day + Mass_dflow_gases = Molar_dflow_gases*cmps.chem_MW[self.idx] #(mol/day)*(g/mol) = (g/day) - self._outs[0].dstate[idx] = gas_dstate_in_unit - - # Q_gas = ??Ian?? - # self._outs[0].state[-1] = Q_gas + self._outs[0].dstate[idx] = Mass_dflow_gases # (g/day) + self._outs[0].dstate[-1] = 0 # Just differentiating constant 1 to 0 self._outs[1].dstate = np.zeros(len(cmps) + 1) # The state of effluent Liquid stream is simply the concentration of From 8dd17797356458c6177d3fd319cc59c24bc2ef68 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Mon, 3 Apr 2023 15:35:47 -0500 Subject: [PATCH 090/424] Updated Thickener Updated update_state( ) and update_dstate( ) functions in Thickener to facilitate dynamic simulation Co-Authored-By: Joy Zhang <36645879+joyxyz1994@users.noreply.github.com> --- qsdsan/sanunits/_sludge_treatment.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py index 042b99c5..8a623bbe 100644 --- a/qsdsan/sanunits/_sludge_treatment.py +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -297,13 +297,16 @@ def _update_state(self): # For sludge, the particulate concentrations are multipled by thickener factor, and # flowrate is multiplied by Qu_factor. The soluble concentrations remains same. - self._outs[0].state[:-1] = self._state[:-1]*cmps.s*1 + self._state[:-1]*cmps.x*thickener_factor - self._outs[0].state[-1] = self._state[-1]*Qu_factor + uf, of = self.outs + if uf.state is None: uf.state = np.zeros(len(cmps)+1) + uf.state[:-1] = self._state[:-1]*cmps.s*1 + self._state[:-1]*cmps.x*thickener_factor + uf.state[-1] = self._state[-1]*Qu_factor # For effluent, the particulate concentrations are multipled by thinning factor, and # flowrate is multiplied by Qu_factor. The soluble concentrations remains same. - self._outs[1].state[:-1] = self._state[:-1]*cmps.s*1 + self._state[:-1]*cmps.x*thinning_factor - self._outs[1].state[-1] = self._state[-1]*(1 - Qu_factor) + if of.state is None: of.state = np.zeros(len(cmps)+1) + of.state[:-1] = self._state[:-1]*cmps.s*1 + self._state[:-1]*cmps.x*thinning_factor + of.state[-1] = self._state[-1]*(1 - Qu_factor) def _update_dstate(self): '''updates rates of change of output stream from rates of change of the Thickener''' @@ -324,13 +327,16 @@ def _update_dstate(self): # For sludge, the particulate concentrations are multipled by thickener factor, and # flowrate is multiplied by Qu_factor. The soluble concentrations remains same. - self._outs[0].dstate[:-1] = self._dstate[:-1]*cmps.s*1 + self._dstate[:-1]*cmps.x*thickener_factor - self._outs[0].dstate[-1] = self._dstate[-1]*Qu_factor + uf, of = self.outs + if uf.dstate is None: uf.dstate = np.zeros(len(cmps)+1) + uf.dstate[:-1] = self._dstate[:-1]*cmps.s*1 + self._dstate[:-1]*cmps.x*thickener_factor + uf.dstate[-1] = self._dstate[-1]*Qu_factor # For effluent, the particulate concentrations are multipled by thinning factor, and # flowrate is multiplied by Qu_factor. The soluble concentrations remains same. - self._outs[1].dstate[:-1] = self._dstate[:-1]*cmps.s*1 + self._dstate[:-1]*cmps.x*thinning_factor - self._outs[1].dstate[-1] = self._dstate[-1]*(1 - Qu_factor) + if of.dstate is None: of.dstate = np.zeros(len(cmps)+1) + of.dstate[:-1] = self._dstate[:-1]*cmps.s*1 + self._dstate[:-1]*cmps.x*thinning_factor + of.dstate[-1] = self._dstate[-1]*(1 - Qu_factor) @property def AE(self): From e79a940e2382aef9b818c6c718bfead92f433c14 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Tue, 4 Apr 2023 11:27:51 -0500 Subject: [PATCH 091/424] Updated Gas Extraction Membrane Updated Gas Extraction Membrane after input from Ian --- qsdsan/sanunits/_membrane_gas_extraction.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/qsdsan/sanunits/_membrane_gas_extraction.py b/qsdsan/sanunits/_membrane_gas_extraction.py index af04353f..377d2a9d 100644 --- a/qsdsan/sanunits/_membrane_gas_extraction.py +++ b/qsdsan/sanunits/_membrane_gas_extraction.py @@ -118,7 +118,7 @@ def __init__(self, ID='GEM', ins=None, outs=(), thermo=None, isdynamic=False, self.SurfArea = SurfArea # Surface Area [m^2] self.GasID = GasID # IDs of gas used in the process self.PVac = PVac # Operating Vacuum Pressure [-kPa] - self.segs = segs # Number of segments ??Ask Ian?? + self.segs = segs # Number of segments #self.Volume = VolBatchTank # Volume of the bioreactor (Don't think this is needed) dct_gas_perm = GasPerm or self._GasPerm @@ -173,7 +173,7 @@ def NumTubes(self, NumTubes): if NumTubes is not None: self._NumTubes = NumTubes else: - raise ValueError('Outer diameter of fiber expected from user') + raise ValueError('Number of tubes expected from user') @property def ShellDia(self): @@ -360,7 +360,7 @@ def _compile_ODE(self): # Synthesizes the ODEs to simulate a batch reactor with side-flow gas extraction. The code takes in an object of class Membrane (Mem) and an array of objects of class Gas (GasVec). It also takes in an array of experimental conditions ExpCond. # Extract Operating Parameters from ExpCond - Q = self.ins.F_vol*(1000/60) # Volumetric Flowrate [L/min] + Q = self.ins.F_vol # Volumetric Flowrate [m3/sec] T = self.ins[0].T # Temperature [K] P = self.PVac*1000 # Vacuum Pressure [Pa] #V = self.Volume # Volume of the Batch Tank [L] @@ -401,7 +401,7 @@ def _compile_ODE(self): #Diff = np.array([0.0265e-7, 0.0296e-7, 0.0253e-7, 0.3199e-7]) # Calculate dx and u dx = L/Segs # Length of Segments [m] - u = Q/((np.pi*D**2/4)*num_tubes*1000*60) # Linear Flow Velocity [m/s] + u = Q/((np.pi*D**2/4)*num_tubes) # Linear Flow Velocity [m/s] # Calculate the Kinematic Viscosity of Water Tb = T/300 # Reduced Temperature [] From 7f0237846ccd62814f849f02b1346440ec8c0b3e Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Tue, 4 Apr 2023 13:24:14 -0500 Subject: [PATCH 092/424] Update SanUnits Added name to list of contributors of SanUnits --- qsdsan/sanunits/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/qsdsan/sanunits/__init__.py b/qsdsan/sanunits/__init__.py index 2a460dc2..4be52588 100644 --- a/qsdsan/sanunits/__init__.py +++ b/qsdsan/sanunits/__init__.py @@ -23,6 +23,8 @@ Smiti Mittal Jianan Feng + + Saumitra Rai This module is under the University of Illinois/NCSA Open Source License. Please refer to https://github.com/QSD-Group/QSDsan/blob/main/LICENSE.txt From 19b86b752d47f9f968a9571f793ede8951b74971 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Tue, 4 Apr 2023 15:06:59 -0500 Subject: [PATCH 093/424] Incorporating pH (ADM1 P extension) Incorporating changes in pH due to addition of new components --- qsdsan/processes/_adm1_p_extension.py | 40 +++++++++++++-------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/qsdsan/processes/_adm1_p_extension.py b/qsdsan/processes/_adm1_p_extension.py index 2dba7f81..92e154e5 100644 --- a/qsdsan/processes/_adm1_p_extension.py +++ b/qsdsan/processes/_adm1_p_extension.py @@ -222,23 +222,22 @@ def T_correction_factor(T1, T2, delta_H): # pKas = np.asarray(pKas) # return 10**(-pKas) * T_correction_factor(T_base, T_op, theta) -# What about this? Any changes needed? (Saumitra) def acid_base_rxn(h_ion, weak_acids_tot, Kas): # h, nh4, hco3, ac, pr, bu, va = mols - # S_cat, S_an, S_IN, S_IC, S_ac, S_pro, S_bu, S_va = weak_acids_tot # in M - S_cat, S_an, S_IN = weak_acids_tot[:3] - # Kw, Ka_nh, Ka_co2, Ka_ac, Ka_pr, Ka_bu, Ka_va = Kas + # S_cat, S_K, S_Mg, S_an, S_IN, S_IP, S_IC, S_ac, S_pro, S_bu, S_va = weak_acids_tot # in M + S_cat, S_K, S_Mg, S_an, S_IN, S_IP = weak_acids_tot[:6] + # Kw, Ka_nh, Ka_h3po4, Ka_co2, Ka_ac, Ka_pr, Ka_bu, Ka_va = Kas Kw = Kas[0] oh_ion = Kw/h_ion - nh3, hco3, ac, pro, bu, va = Kas[1:] * weak_acids_tot[2:] / (Kas[1:] + h_ion) - return S_cat + h_ion + (S_IN - nh3) - S_an - oh_ion - hco3 - ac - pro - bu - va + nh3, h2po4, hco3, ac, pro, bu, va = Kas[1:] * weak_acids_tot[4:] / (Kas[1:] + h_ion) + return S_cat + S_K + S_Mg + h_ion + (S_IN - nh3) - S_an - oh_ion - hco3 - ac - pro - bu - va - (3*S_IP + h2po4) def fprime_abr(h_ion, weak_acids_tot, Kas): - S_cat, S_an, S_IN = weak_acids_tot[:3] + S_cat, S_K, S_Mg, S_an, S_IN, S_IP = weak_acids_tot[:6] Kw = Kas[0] doh_ion = - Kw / h_ion ** 2 - dnh3, dhco3, dac, dpro, dbu, dva = - Kas[1:] * weak_acids_tot[2:] / (Kas[1:] + h_ion)**2 - return 1 + (-dnh3) - doh_ion - dhco3 - dhco3 - dac - dpro - dbu - dva + dnh3, dh2po4, dhco3, dac, dpro, dbu, dva = - Kas[1:] * weak_acids_tot[4:] / (Kas[1:] + h_ion)**2 + return 1 + (-dnh3) - dh2po4 - doh_ion - dhco3 - dhco3 - dac - dpro - dbu - dva def pH_inhibit(pH, ul, ll, lower_only=True): if lower_only: @@ -302,12 +301,14 @@ def rhos_adm1_p_extension(state_arr, params): # S_va, S_bu, S_h2, S_ch4, S_IC, S_IN = state_arr[[3,4,7,8,9,10]] S_va, S_bu, S_h2, S_IN, S_IP = state_arr[[3,4,7,10,11]] unit_conversion = mass2mol_conversion(cmps) - cmps_in_M = state_arr[:27] * unit_conversion - weak_acids = cmps_in_M[[24, 25, 10, 9, 6, 5, 4, 3]] + cmps_in_M = state_arr[:32] * unit_conversion + # weak acids (ADM1) = [S_ca, S_an, S_IN, S_IC, S_ac, S_pro, S_bu, S_va] + # weak acids (modified_ADM1) = [S_ca, S_an, S_IN, S_IP, S_IC, S_ac, S_pro, S_bu, S_va] + weak_acids = cmps_in_M[[29, 30, 10, 11, 9, 6, 5, 4, 3]] T_op = state_arr[-1] biogas_S = state_arr[7:10].copy() - biogas_p = R * T_op * state_arr[27:30] + biogas_p = R * T_op * state_arr[32:35] Kas = Kab * T_correction_factor(T_base, T_op, Ka_dH) KH = KHb * T_correction_factor(T_base, T_op, KH_dH) / unit_conversion[7:10] @@ -349,9 +350,6 @@ def rhos_adm1_p_extension(state_arr, params): # rhos[4:12] *= Hill_inhibit(h, pH_ULs, pH_LLs) * substr_inhibit(S_IN, KS_IN) # rhos[6:10] *= non_compet_inhibit(S_h2, KIs_h2) rhos[9] *= non_compet_inhibit(nh3, KI_nh3) - - - rhos[-3:] = kLa * (biogas_S - KH * biogas_p) # print(rhos) return rhos @@ -552,7 +550,7 @@ class ADM1_p_extension(CompiledProcesses): pKa_base : iterable[float], optional pKa (equilibrium coefficient) values of acid-base pairs at the base temperature, unitless, following the order of `ADM1._acid_base_pairs`. - The default is [14, 9.25, 6.35, 4.76, 4.88, 4.82, 4.86]. + The default is [14, 9.25, 2.12, 6.35, 4.76, 4.88, 4.82, 4.86]. Ka_dH : iterable[float], optional Heat of reaction of each acid-base pair at base temperature [J/mol], following the order of `ADM1._acid_base_pairs`. The default is @@ -602,8 +600,8 @@ class ADM1_p_extension(CompiledProcesses): 'KS_IN', 'KS_IP', 'KI_nh3', 'KIs_h2', 'Ka_base', 'Ka_dH', 'K_H_base', 'K_H_dH', 'kLa', 'T_base', 'components', 'root') - _acid_base_pairs = (('H+', 'OH-'), ('NH4+', 'NH3'), ('CO2', 'HCO3-'), - ('HAc', 'Ac-'), ('HPr', 'Pr-'), + _acid_base_pairs = (('H+', 'OH-'), ('NH4+', 'NH3'), ('H3PO4', 'H2PO4-'), + ('CO2', 'HCO3-'), ('HAc', 'Ac-'), ('HPr', 'Pr-'), ('HBu', 'Bu-'), ('HVa', 'Va-')) _biogas_IDs = ('S_h2', 'S_ch4', 'S_IC') @@ -622,7 +620,7 @@ def __new__(cls, components=None, path=None, N_xc=2.686e-3, N_I=4.286e-3, N_aa=7 q_PHA=3, b_PAO=0.2, b_PP=0.2, b_PHA=0.2, KI_h2_fa=5e-6, KI_h2_c4=1e-5, KI_h2_pro=3.5e-6, KI_nh3=1.8e-3, KS_IN=1e-4, KS_IP=2e-5, pH_limits_aa=(4,5.5), pH_limits_ac=(6,7), pH_limits_h2=(5,6), - T_base=298.15, pKa_base=[14, 9.25, 6.35, 4.76, 4.88, 4.82, 4.86], + T_base=298.15, pKa_base=[14, 9.25, 2.12, 6.35, 4.76, 4.88, 4.82, 4.86], Ka_dH=[55900, 51965, 7646, 0, 0, 0, 0], kLa=200, K_H_base=[7.8e-4, 1.4e-3, 3.5e-2], K_H_dH=[-4180, -14240, -19410], @@ -738,8 +736,8 @@ def __new__(cls, components=None, path=None, N_xc=2.686e-3, N_I=4.286e-3, N_aa=7 def set_pKas(self, pKas): '''Set the pKa values of the acid-base reactions at the base temperature.''' - if len(pKas) != 7: - raise ValueError(f'pKas must be an array of 7 elements, one for each ' + if len(pKas) != 8: + raise ValueError(f'pKas must be an array of 8 elements, one for each ' f'acid-base pair, not {len(pKas)} elements.') dct = self.rate_function._params dct['Ka_base'] = np.array([10**(-pKa) for pKa in pKas]) From e60dc75a2ac893024d09c4145923fb6d7f3c1d2d Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Tue, 4 Apr 2023 15:16:06 -0500 Subject: [PATCH 094/424] Created _junction copy Making a copy of the junction file to track changes while working on ASM2d-ADM1 interface --- qsdsan/sanunits/_junction_copy.py | 868 ++++++++++++++++++++++++++++++ 1 file changed, 868 insertions(+) create mode 100644 qsdsan/sanunits/_junction_copy.py diff --git a/qsdsan/sanunits/_junction_copy.py b/qsdsan/sanunits/_junction_copy.py new file mode 100644 index 00000000..e13d456b --- /dev/null +++ b/qsdsan/sanunits/_junction_copy.py @@ -0,0 +1,868 @@ +# -*- coding: utf-8 -*- +''' +QSDsan: Quantitative Sustainable Design for sanitation and resource recovery systems + +This module is developed by: + + Joy Zhang + + Yalin Li + +This module is under the University of Illinois/NCSA Open Source License. +Please refer to https://github.com/QSD-Group/QSDsan/blob/main/LICENSE.txt +for license details. +''' + +import numpy as np +from warnings import warn +from math import isclose +from biosteam.units import Junction as BSTjunction +from .. import SanUnit, processes as pc + +__all__ = ( + 'Junction', + 'ADMjunction', 'ADMtoASM', 'ASMtoADM', + ) + +#%% +class Junction(SanUnit): + ''' + A non-reactive class that serves to convert a stream with one set of components + and into another. + + Thermal conditions of the downstream (T, P) will be copied from those of the upstream. + + Parameters + ---------- + upstream : stream or str + Influent stream. + downstream : stream or str + Effluent stream. + reactions : iterable(dict) | callable + Iterable of dict that has the conversion of upstream components to + downstream components, + or a function that will return the concentration of the effluent + with influent concentration as the input. + + If given as an iterable of dict, keys of each of the dict should be + the ID or alias of components, + values should be the conversion/yield, + which should be negative for reactants and positive for products. + ''' + _graphics = BSTjunction._graphics + + def __init__(self, ID='', upstream=None, downstream=(), thermo=None, + init_with='WasteStream', F_BM_default=None, isdynamic=False, + reactions=None, **kwargs): + thermo = downstream.thermo if downstream else thermo + SanUnit.__init__(self, ID, ins=upstream, outs=downstream, thermo=thermo, + init_with=init_with, + F_BM_default=F_BM_default, isdynamic=isdynamic, + skip_property_package_check=True) + if reactions: self.reactions = reactions + else: self.reactions = None + + for key, val in kwargs.items(): + setattr(self, key, val) + + def _no_parse_reactions(self, rxns): + if rxns is None: return + raise RuntimeError('Reactions are automatically compiled.') + + + def _parse_reactions(self, rxns): + cmps_in = self.ins[0].components + cmps_outs = self.outs[0].components + + num_rxns = len(rxns) + num_upcmps = len(cmps_in) + num_downcmps = len(cmps_outs) + RX = np.zeros(shape=(num_rxns, num_upcmps)) # each row is a reaction + RY = np.zeros(shape=(num_rxns, num_downcmps)) + + for n, dct in enumerate(rxns): + RXn, RYn = RX[n], RY[n] + for cmp, val in dct.items(): + if val < 0: RXn[cmps_in.index(cmp)] = val + elif val > 0: RYn[cmps_outs.index(cmp)] = val + + # Transfer overlapped components + overlapped = set(cmps_in.IDs).intersection(set(cmps_outs.IDs)) + RXsum = RX.sum(axis=0) + RXnew = [] + RYnew = [] + for ID in overlapped: + idx_up = cmps_in.index(ID) + idx_down= cmps_outs.index(ID) + if RXsum[idx_up] != -1: + newRXn = np.zeros(num_upcmps) + newRYn = np.zeros(num_downcmps) + newRXn[idx_up] = -1 - RXsum[idx_up] + newRYn[idx_down] = -newRXn[idx_up] + RXnew.append(newRXn) + RYnew.append(newRYn) + RX = np.concatenate((RX, np.array(RXnew))) + RY = np.concatenate((RY, np.array(RYnew))) + + # Check if all upstream components are converted + RXsum = RX.sum(axis=0) + if np.where(RXsum!=-1)[0].any(): + index = np.where(RXsum!=-1)[0][0] + raise ValueError(f'Conversion for Component "{cmps_in.IDs[index]}" ' + f'is {abs(RXsum[index])}, not 100%.') + self._RX = RX + self._RY = RY + + + def _compile_reactions(self): + def reactions(X): + X.reshape(1, len(X)) + Yarr = -(self._RX*X).T @ self._RY # _RX: (num_rxns, num_upcmps); _RY: (num_rxns, num_downcmps) + Y = Yarr.sum(axis=0) # Yarr: (num_upcmps, num_downcmps) + return Y.reshape(Y.shape[1],) + self._reactions = reactions + + + def _run(self): + ins = self.ins[0] + rxns = self.reactions + X = ins.conc.value + Y = rxns(X) + outs = self.outs[0] + outs.thermal_condition.copy_like(ins.thermal_condition) + concs = dict(zip(outs.components.IDs, Y)) + concs.pop('H2O', None) + outs.set_flow_by_concentration( + flow_tot=ins.F_vol, + concentrations=concs, + units=('m3/hr', 'mg/L')) + + + # Below are dynamic simulation-related properties + @property + def state(self): + '''The state of the Junction, including component concentrations [mg/L] and flow rate [m^3/d].''' + if self._state is None: return None + else: + return dict(zip(list(self.components.IDs) + ['Q'], self._state)) + + def _init_dynamic(self): + super()._init_dynamic() + # Need to use ins' components, otherwise _ins_QC will follow the shape of + # the unit's (i.e., downstream) components + self._ins_QC = np.zeros((len(self._ins), len(self.ins[0].components)+1)) + self._ins_dQC = self._ins_QC.copy() + + def _init_state(self): + ''' + Initialize state by specifying or calculating component concentrations + based on influents. Total flow rate is always initialized as the sum of + influent wastestream flows. + ''' + self._state = np.append(self.outs[0].conc, self.outs[0].F_vol*24) + self._dstate = self._state * 0. + self._cached_state = self._state.copy() + self._cached_t = 0 + + def _update_state(self): + ''' + Updates conditions of output stream based on conditions of the Junction. + ''' + self._outs[0].state = self._state + + def _update_dstate(self): + ''' + Updates rates of change of output stream from rates of change of the Junction. + ''' + self._outs[0].dstate = self._dstate + + # The unit's state should be the same as the effluent state + # react the state arr and dstate arr + def _compile_AE(self): + _state = self._state + _dstate = self._dstate + _update_state = self._update_state + _update_dstate = self._update_dstate + rxns = self.reactions + def yt(t, QC_ins, dQC_ins): + for i, j in zip((QC_ins, dQC_ins), (_state, _dstate)): + X = i[0][:-1] # shape = (1, num_upcmps) + Y = rxns(X) + j[:-1] = Y + j[-1] = i[0][-1] # volumetric flow of outs should equal that of ins + _update_state() + _update_dstate() + self._AE = yt + + + @property + def upstream(self): + '''[qsdsan.WasteStream] Influent.''' + return self.ins[0] + @upstream.setter + def upstream(self, upstream): + self.ins[0] = upstream + + @property + def downstream(self): + '''[qsdsan.WasteStream] Effluent.''' + return self.outs[0] + @downstream.setter + def downstream(self, downstream): + self.outs[0] = downstream + + @property + def AE(self): + if self._AE is None: + self._compile_AE() + return self._AE + + @property + def reactions(self): + ''' + [callable] Function that takes the concentration array of the influent + and convert to the concentration array of the effluent. + ''' + if not self._reactions: self._compile_reactions() + return self._reactions + @reactions.setter + def reactions(self, i): + if callable(i): self._reactions = i + else: + self._parse_reactions(i) + self._compile_reactions() + + +# %% + +#TODO: add a `rtol` kwargs for error checking +class ADMjunction(Junction): + ''' + An abstract superclass holding common properties of ADM interface classes. + Users should use its subclasses (e.g., ``ASMtoADM``, ``ADMtoASM``) instead. + + See Also + -------- + :class:`qsdsan.sanunits.Junction` + + :class:`qsdsan.sanunits.ADMtoASM` + + :class:`qsdsan.sanunits.ASMtoADM` + ''' + _parse_reactions = Junction._no_parse_reactions + rtol = 1e-2 + atol = 1e-6 + + def __init__(self, ID='', upstream=None, downstream=(), thermo=None, + init_with='WasteStream', F_BM_default=None, isdynamic=False, + adm1_model=None): + self.adm1_model = adm1_model # otherwise there won't be adm1_model when `_compile_reactions` is called + if thermo is None: + warn('No `thermo` object is provided and is prone to raise error. ' + 'If you are not sure how to get the `thermo` object, ' + 'use `thermo = qsdsan.set_thermo` after setting thermo with the `Components` object.') + super().__init__(ID=ID, upstream=upstream, downstream=downstream, + thermo=thermo, init_with=init_with, + F_BM_default=F_BM_default, isdynamic=isdynamic) + + + @property + def T(self): + '''[float] Temperature of the upstream/downstream [K].''' + return self.ins[0].T + @T.setter + def T(self, T): + self.ins[0].T = self.outs[0].T = T + + @property + def pH(self): + '''[float] pH of the upstream/downstream.''' + return self.ins[0].pH + + @property + def adm1_model(self): + '''[qsdsan.Process] ADM process model.''' + return self._adm1_model + @adm1_model.setter + def adm1_model(self, model): + if not isinstance(model, pc.ADM1): + raise ValueError('`adm1_model` must be an `AMD1` object, ' + f'the given object is {type(model).__name__}.') + self._adm1_model = model + + @property + def T_base(self): + '''[float] Base temperature in the ADM1 model.''' + return self.adm1_model.rate_function.params['T_base'] + + @property + def pKa_base(self): + '''[float] pKa of the acid-base pairs at the base temperature in the ADM1 model.''' + Ka_base = self.adm1_model.rate_function.params['Ka_base'] + return -np.log10(Ka_base) + + @property + def Ka_dH(self): + '''[float] Heat of reaction for Ka.''' + return self.adm1_model.rate_function.params['Ka_dH'] + + @property + def pKa(self): + ''' + [numpy.array] pKa array of the following acid-base pairs: + ('H+', 'OH-'), ('NH4+', 'NH3'), ('CO2', 'HCO3-'), + ('HAc', 'Ac-'), ('HPr', 'Pr-'), ('HBu', 'Bu-'), ('HVa', 'Va-') + ''' + return self.pKa_base-np.log10(np.exp(pc.T_correction_factor(self.T_base, self.T, self.Ka_dH))) + + @property + def alpha_IC(self): + '''[float] Charge per g of C.''' + pH = self.pH + pKa_IC = self.pKa[2] + return -1/(1+10**(pKa_IC-pH))/12 + + @property + def alpha_IN(self): + '''[float] Charge per g of N.''' + pH = self.pH + pKa_IN = self.pKa[1] + return 10**(pKa_IN-pH)/(1+10**(pKa_IN-pH))/14 + + + def _compile_AE(self): + _state = self._state + _dstate = self._dstate + _cached_state = self._cached_state + _update_state = self._update_state + _update_dstate = self._update_dstate + rxn = self.reactions + + def yt(t, QC_ins, dQC_ins): + before_vals = QC_ins[0,:-1] + _state[:-1] = rxn(before_vals) + _state[-1] = QC_ins[0,-1] + if t > self._cached_t: + _dstate[:] = (_state - _cached_state)/(t-self._cached_t) + _cached_state[:] = _state + self._cached_t = t + _update_state() + _update_dstate() + + self._AE = yt + +# %% + +class ADMtoASM(ADMjunction): + ''' + Interface unit to convert anaerobic digestion model (ADM) components + to activated sludge model (ASM) components. + + Parameters + ---------- + upstream : stream or str + Influent stream with ADM components. + downstream : stream or str + Effluent stream with ASM components. + adm1_model : obj + The anaerobic digestion process model (:class:`qsdsan.processes.ADM1`). + bio_to_xs : float + Split of the total biomass COD to slowly biodegradable substrate (X_S), + the rest is assumed to be mapped into X_P. + rtol : float + Relative tolerance for COD and TKN balance. + atol : float + Absolute tolerance for COD and TKN balance. + + References + ---------- + [1] Nopens, I.; Batstone, D. J.; Copp, J. B.; Jeppsson, U.; Volcke, E.; + Alex, J.; Vanrolleghem, P. A. An ASM/ADM Model Interface for Dynamic + Plant-Wide Simulation. Water Res. 2009, 43, 1913–1923. + + See Also + -------- + :class:`qsdsan.sanunits.ADMjunction` + + :class:`qsdsan.sanunits.ASMtoADM` + + `math.isclose ` + ''' + # User defined values + bio_to_xs = 0.7 + + # Should be constants + cod_vfa = np.array([64, 112, 160, 208]) + + def isbalanced(self, lhs, rhs_vals, rhs_i): + rhs = sum(rhs_vals*rhs_i) + error = rhs - lhs + tol = max(self.rtol*lhs, self.rtol*rhs, self.atol) + return abs(error) <= tol, error, tol, rhs + + def balance_cod_tkn(self, adm_vals, asm_vals): + cmps_adm = self.ins[0].components + cmps_asm = self.outs[0].components + asm_i_COD = cmps_asm.i_COD + adm_i_COD = cmps_adm.i_COD + gas_idx = cmps_adm.indices(('S_h2', 'S_ch4')) + asm_i_N = cmps_asm.i_N + adm_i_N = cmps_adm.i_N + adm_cod = sum(adm_vals*adm_i_COD) - sum(adm_vals[gas_idx]) + adm_tkn = sum(adm_vals*adm_i_N) + cod_bl, cod_err, cod_tol, asm_cod = self.isbalanced(adm_cod, asm_vals, asm_i_COD) + tkn_bl, tkn_err, tkn_tol, asm_tkn = self.isbalanced(adm_tkn, asm_vals, asm_i_N) + if cod_bl: + if tkn_bl: return asm_vals + else: + if tkn_err > 0: dtkn = -(tkn_err - tkn_tol)/asm_tkn + else: dtkn = -(tkn_err + tkn_tol)/asm_tkn + _asm_vals = asm_vals * (1 + (asm_i_N>0)*dtkn) + _cod_bl, _cod_err, _cod_tol, _asm_cod = self.isbalanced(adm_cod, _asm_vals, asm_i_COD) + if _cod_bl: return _asm_vals + else: + warn('cannot balance COD and TKN at the same ' + f'time with rtol={self.rtol} and atol={self.atol}. ' + f'influent (ADM) COD is {adm_cod}, ' + f'effluent (ASM) COD is {asm_cod} or {_asm_cod}. ' + f'influent TKN is {adm_tkn}, ' + f'effluent TKN is {asm_tkn} or {asm_tkn*(1+dtkn)}. ') + return asm_vals + else: + if cod_err > 0: dcod = -(cod_err - cod_tol)/asm_cod + else: dcod = -(cod_err + cod_tol)/asm_cod + _asm_vals = asm_vals * (1 + (asm_i_COD>0)*dcod) + _tkn_bl, _tkn_err, _tkn_tol, _asm_tkn = self.isbalanced(adm_tkn, _asm_vals, asm_i_N) + if _tkn_bl: return _asm_vals + else: + warn('cannot balance COD and TKN at the same ' + f'time with rtol={self.rtol} and atol={self.atol}. ' + f'influent (ADM) COD is {adm_cod}, ' + f'effluent (ASM) COD is {asm_cod} or {asm_cod*(1+dcod)}. ' + f'influent TKN is {adm_tkn}, ' + f'effluent TKN is {asm_tkn} or {_asm_tkn}. ') + return asm_vals + + def _compile_reactions(self): + # Retrieve constants + ins = self.ins[0] + outs = self.outs[0] + rtol = self.rtol + atol = self.atol + + cmps_adm = ins.components + X_c_i_N = cmps_adm.X_c.i_N + X_pr_i_N = cmps_adm.X_pr.i_N + S_aa_i_N = cmps_adm.S_aa.i_N + adm_X_I_i_N = cmps_adm.X_I.i_N + adm_S_I_i_N = cmps_adm.S_I.i_N + adm_i_N = cmps_adm.i_N + adm_bio_N_indices = cmps_adm.indices(('X_su', 'X_aa', 'X_fa', + 'X_c4', 'X_pro', 'X_ac', 'X_h2')) + + cmps_asm = outs.components + X_P_i_N = cmps_asm.X_P.i_N + X_S_i_N = cmps_asm.X_S.i_N + S_S_i_N = cmps_asm.S_S.i_N + asm_X_I_i_N = cmps_asm.X_I.i_N + asm_S_I_i_N = cmps_asm.S_I.i_N + asm_X_P_i_N = cmps_asm.X_P.i_N + asm_ions_idx = cmps_asm.indices(('S_NH', 'S_ALK')) + + alpha_IN = self.alpha_IN + alpha_IC = self.alpha_IC + alpha_vfa = self.alpha_vfa + f_corr = self.balance_cod_tkn + + def adm2asm(adm_vals): + S_su, S_aa, S_fa, S_va, S_bu, S_pro, S_ac, S_h2, S_ch4, S_IC, S_IN, S_I, \ + X_c, X_ch, X_pr, X_li, X_su, X_aa, X_fa, X_c4, X_pro, X_ac, X_h2, X_I, \ + S_cat, S_an, H2O = adm_vals + + # Step 0: snapshot of charged components + _ions = np.array([S_IN, S_IC, S_ac, S_pro, S_bu, S_va]) + + # Step 1a: convert biomass into X_S+X_ND and X_P + bio_cod = X_su + X_aa + X_fa + X_c4 + X_pro + X_ac + X_h2 + bio_n = sum((adm_vals*adm_i_N)[adm_bio_N_indices]) + xp_cod = bio_cod * (1-self.bio_to_xs) + xp_ndm = xp_cod*X_P_i_N + if xp_ndm > bio_n: + warn('Not enough biomass N to map the specified proportion of ' + 'biomass COD into X_P. Mapped as much COD as possible, the rest ' + 'goes to X_S.') + X_P = bio_n/asm_X_P_i_N + bio_n = 0 + else: + X_P = xp_cod + bio_n -= xp_ndm + X_S = bio_cod - X_P + xs_ndm = X_S*X_S_i_N + if xs_ndm <= bio_n: + X_ND = bio_n - xs_ndm + bio_n = 0 + elif xs_ndm <= bio_n + S_IN: + X_ND = 0 + S_IN -= (xs_ndm - bio_n) + bio_n = 0 + else: + if isclose(xs_ndm, bio_n + S_IN, rel_tol=rtol, abs_tol=atol): + X_ND = S_IN = bio_n = 0 + else: + raise RuntimeError('Not enough nitrogen (S_IN + biomass) to map ' + 'all biomass COD into X_P and X_S') + + # Step 1b: convert particulate substrates into X_S + X_ND + xsub_cod = X_c + X_ch + X_pr + X_li + xsub_n = X_c*X_c_i_N + X_pr*X_pr_i_N + X_S += xsub_cod + X_ND += xsub_n - xsub_cod*X_S_i_N # X_S.i_N should technically be zero + if X_ND < 0: + if isclose(X_ND, 0, rel_tol=rtol, abs_tol=atol): X_ND = 0 + else: + raise RuntimeError('Not enough nitrogen (substrate + excess X_ND) ' + 'to map all particulate substrate COD into X_S') + + # Step 2: map all X_I from ADM to ASM + excess_XIn = X_I * (adm_X_I_i_N - asm_X_I_i_N) + S_IN += excess_XIn + if S_IN < 0: + if isclose(S_IN, 0, rel_tol=rtol, abs_tol=atol): S_IN = 0 + raise RuntimeError('Not enough nitrogen (X_I + S_IN) to map ' + 'all ADM X_I into ASM X_I') + + # Step 3: map ADM S_I into ASM S_I and S_NH + excess_SIn = S_I * (adm_S_I_i_N - asm_S_I_i_N) + if excess_SIn > 0: + S_NH = excess_SIn + else: + S_NH = 0 + S_IN += excess_SIn + if S_IN < 0: + if isclose(S_IN, 0, rel_tol=rtol, abs_tol=atol): S_IN = 0 + raise RuntimeError('Not enough nitrogen (S_I + S_IN) to map ' + 'all ADM S_I into ASM S_I') + S_NH += S_IN + + # Step 4: map all soluble substrates into S_S and S_ND + ssub_cod = S_su + S_aa + S_fa + S_va + S_bu + S_pro + S_ac + ssub_n = S_aa * S_aa_i_N + if ssub_cod*S_S_i_N <= ssub_n: + S_S = ssub_cod + S_ND = ssub_n + if S_S_i_N != 0: S_ND -= S_S/S_S_i_N # S_S.i_N should technically be zero + else: + if isclose(ssub_cod*S_S_i_N, ssub_n, rel_tol=rtol, abs_tol=atol): + S_S = ssub_cod + S_ND = 0 + else: + raise RuntimeError('Not enough nitrogen to map all soluble ' + 'substrates into ASM S_S') + + # Step 6: check COD and TKN balance + asm_vals = np.array(([ + S_I, S_S, X_I, X_S, + 0, 0, # X_BH, X_BA, + X_P, + 0, 0, # S_O, S_NO, + S_NH, S_ND, X_ND, + 0, 0, # temporary S_ALK, S_N2, + H2O])) + + if S_h2 > 0 or S_ch4 > 0: + warn('Ignored dissolved H2 or CH4.') + + asm_vals = f_corr(adm_vals, asm_vals) + + # Step 5: charge balance for alkalinity + S_NH = asm_vals[asm_ions_idx[0]] + S_ALK = (sum(_ions * np.append([alpha_IN, alpha_IC], alpha_vfa)) - S_NH/14)*(-12) + asm_vals[asm_ions_idx[1]] = S_ALK + + return asm_vals + + self._reactions = adm2asm + + @property + def alpha_vfa(self): + return 1.0/self.cod_vfa*(-1.0/(1.0 + 10**(self.pKa[3:]-self.pH))) + + +# %% + +class ASMtoADM(ADMjunction): + ''' + Interface unit to convert activated sludge model (ASM) components + to anaerobic digestion model (ADM) components. + + Parameters + ---------- + upstream : stream or str + Influent stream with ASM components. + downstream : stream or str + Effluent stream with ADM components. + adm1_model : obj + The anaerobic digestion process model (:class:`qsdsan.processes.ADM1`). + xs_to_li : float + Split of slowly biodegradable substrate COD to lipid, + after all N is mapped into protein. + bio_to_li : float + Split of biomass COD to lipid, after all biomass N is + mapped into protein. + frac_deg : float + Biodegradable fraction of biomass COD. + rtol : float + Relative tolerance for COD and TKN balance. + atol : float + Absolute tolerance for COD and TKN balance. + + References + ---------- + [1] Nopens, I.; Batstone, D. J.; Copp, J. B.; Jeppsson, U.; Volcke, E.; + Alex, J.; Vanrolleghem, P. A. An ASM/ADM Model Interface for Dynamic + Plant-Wide Simulation. Water Res. 2009, 43, 1913–1923. + + See Also + -------- + :class:`qsdsan.sanunits.ADMjunction` + + :class:`qsdsan.sanunits.ADMtoASM` + + `math.isclose ` + ''' + # User defined values + xs_to_li = 0.7 + bio_to_li = 0.4 + frac_deg = 0.68 + + + def isbalanced(self, lhs, rhs_vals, rhs_i): + rhs = sum(rhs_vals*rhs_i) + error = rhs - lhs + tol = max(self.rtol*lhs, self.rtol*rhs, self.atol) + return abs(error) <= tol, error, tol, rhs + + def balance_cod_tkn(self, asm_vals, adm_vals): + cmps_asm = self.ins[0].components + cmps_adm = self.outs[0].components + asm_i_COD = cmps_asm.i_COD + adm_i_COD = cmps_adm.i_COD + non_tkn_idx = cmps_asm.indices(('S_NO', 'S_N2')) + asm_i_N = cmps_asm.i_N + adm_i_N = cmps_adm.i_N + asm_cod = sum(asm_vals*asm_i_COD) + asm_tkn = sum(asm_vals*asm_i_N) - sum(asm_vals[non_tkn_idx]) + cod_bl, cod_err, cod_tol, adm_cod = self.isbalanced(asm_cod, adm_vals, adm_i_COD) + tkn_bl, tkn_err, tkn_tol, adm_tkn = self.isbalanced(asm_tkn, adm_vals, adm_i_N) + if cod_bl: + if tkn_bl: return adm_vals + else: + if tkn_err > 0: dtkn = -(tkn_err - tkn_tol)/adm_tkn + else: dtkn = -(tkn_err + tkn_tol)/adm_tkn + _adm_vals = adm_vals * (1 + (adm_i_N>0)*dtkn) + _cod_bl, _cod_err, _cod_tol, _adm_cod = self.isbalanced(asm_cod, _adm_vals, adm_i_COD) + if _cod_bl: return _adm_vals + else: + warn('cannot balance COD and TKN at the same ' + f'time with rtol={self.rtol} and atol={self.atol}.\n ' + f'influent (ASM) COD is {asm_cod}\n ' + f'effluent (ADM) COD is {adm_cod} or {_adm_cod}\n ' + f'influent TKN is {asm_tkn}\n ' + f'effluent TKN is {adm_tkn} or {adm_tkn*(1+dtkn)}. ') + return adm_vals + else: + if cod_err > 0: dcod = -(cod_err - cod_tol)/adm_cod + else: dcod = -(cod_err + cod_tol)/adm_cod + _adm_vals = adm_vals * (1 + (adm_i_COD>0)*dcod) + _tkn_bl, _tkn_err, _tkn_tol, _adm_tkn = self.isbalanced(asm_tkn, _adm_vals, adm_i_N) + if _tkn_bl: return _adm_vals + else: + warn('cannot balance COD and TKN at the same ' + f'time with rtol={self.rtol} and atol={self.atol}.\n ' + f'influent (ASM) COD is {asm_cod}\n ' + f'effluent (ADM) COD is {adm_cod} or {adm_cod*(1+dcod)}\n ' + f'influent TKN is {asm_tkn}\n ' + f'effluent TKN is {adm_tkn} or {_adm_tkn}. ') + return adm_vals + + def _compile_reactions(self): + # Retrieve constants + ins = self.ins[0] + outs = self.outs[0] + rtol = self.rtol + atol = self.atol + + cmps_asm = ins.components + S_NO_i_COD = cmps_asm.S_NO.i_COD + X_BH_i_N = cmps_asm.X_BH.i_N + X_BA_i_N = cmps_asm.X_BA.i_N + asm_X_I_i_N = cmps_asm.X_I.i_N + X_P_i_N = cmps_asm.X_P.i_N + if cmps_asm.X_S.i_N > 0: + warn(f'X_S in ASM has positive nitrogen content: {cmps_asm.X_S.i_N} gN/gCOD. ' + 'These nitrogen will be ignored by the interface model ' + 'and could lead to imbalance of TKN after conversion.') + if cmps_asm.S_S.i_N > 0: + warn(f'S_S in ASM has positive nitrogen content: {cmps_asm.S_S.i_N} gN/gCOD. ' + 'These nitrogen will be ignored by the interface model ' + 'and could lead to imbalance of TKN after conversion.') + + cmps_adm = outs.components + S_aa_i_N = cmps_adm.S_aa.i_N + X_pr_i_N = cmps_adm.X_pr.i_N + S_I_i_N = cmps_adm.S_I.i_N + adm_X_I_i_N = cmps_adm.X_I.i_N + adm_ions_idx = cmps_adm.indices(['S_IN', 'S_IC', 'S_cat', 'S_an']) + + frac_deg = self.frac_deg + alpha_IN = self.alpha_IN + alpha_IC = self.alpha_IC + proton_charge = 10**(-self.pKa[0]+self.pH) - 10**(-self.pH) # self.pKa[0] is pKw + f_corr = self.balance_cod_tkn + + def asm2adm(asm_vals): + S_I, S_S, X_I, X_S, X_BH, X_BA, X_P, S_O, S_NO, S_NH, S_ND, X_ND, S_ALK, S_N2, H2O = asm_vals + + # Step 0: charged component snapshot + _sno = S_NO + _snh = S_NH + _salk = S_ALK + + # Step 1: remove any remaining COD demand + O_coddm = S_O + NO_coddm = -S_NO*S_NO_i_COD + cod_spl = S_S + X_S + X_BH + X_BA + bioN = X_BH*X_BH_i_N + X_BA*X_BA_i_N + + if cod_spl <= O_coddm: + S_O = O_coddm - cod_spl + S_S = X_S = X_BH = X_BA = 0 + elif cod_spl <= O_coddm + NO_coddm: + S_O = 0 + S_NO = -(O_coddm + NO_coddm - cod_spl)/S_NO_i_COD + S_S = X_S = X_BH = X_BA = 0 + else: + S_S -= O_coddm + NO_coddm + if S_S < 0: + X_S += S_S + S_S = 0 + if X_S < 0: + X_BH += X_S + X_S = 0 + if X_BH < 0: + X_BA += X_BH + X_BH = 0 + S_O = S_NO = 0 + + # Step 2: convert any readily biodegradable + # COD and TKN into amino acids and sugars + # Assumed S_S, X_S has no nitrogen + req_scod = S_ND / S_aa_i_N + if S_S < req_scod: + S_aa = S_S + S_su = 0 + S_ND -= S_aa * S_aa_i_N + else: + S_aa = req_scod + S_su = S_S - S_aa + S_ND = 0 + + # Step 3: convert slowly biodegradable COD and TKN + # into proteins, lipids, and carbohydrates + req_xcod = X_ND / X_pr_i_N + if X_S < req_xcod: + X_pr = X_S + X_li = X_ch = 0 + X_ND -= X_pr * X_pr_i_N + else: + X_pr = req_xcod + X_li = self.xs_to_li * (X_S - X_pr) + X_ch = (X_S - X_pr) - X_li + X_ND = 0 + + # Step 4: convert active biomass into protein, lipids, + # carbohydrates and potentially particulate TKN + available_bioN = bioN - (X_BH+X_BA) * (1-frac_deg) * adm_X_I_i_N + if available_bioN < 0: + raise RuntimeError('Not enough N in X_BA and X_BH to fully convert ' + 'the non-biodegradable portion into X_I in ADM1.') + req_bioN = (X_BH+X_BA) * frac_deg * X_pr_i_N + if available_bioN + X_ND >= req_bioN: + X_pr += (X_BH+X_BA) * frac_deg + X_ND += available_bioN - req_bioN + else: + bio2pr = (available_bioN + X_ND)/X_pr_i_N + X_pr += bio2pr + bio_to_split = (X_BH+X_BA) * frac_deg - bio2pr + bio_split_to_li = bio_to_split * self.bio_to_li + X_li += bio_split_to_li + X_ch += (bio_to_split - bio_split_to_li) + X_ND = 0 + + # Step 5: map particulate inerts + xi_nsp = X_P_i_N * X_P + asm_X_I_i_N * X_I + xi_ndm = (X_P+X_I) * adm_X_I_i_N + if xi_nsp + X_ND >= xi_ndm: + deficit = xi_ndm - xi_nsp + X_I += X_P + (X_BH+X_BA) * (1-frac_deg) + X_ND -= deficit + elif isclose(xi_nsp+X_ND, xi_ndm, rel_tol=rtol, abs_tol=atol): + X_I += X_P + (X_BH+X_BA) * (1-frac_deg) + X_ND = 0 + else: + raise RuntimeError('Not enough N in X_I, X_P, X_ND to fully ' + 'convert X_I and X_P into X_I in ADM1.') + + req_sn = S_I * S_I_i_N + if req_sn <= S_ND: + S_ND -= req_sn + elif req_sn <= S_ND + X_ND: + X_ND -= (req_sn - S_ND) + S_ND = 0 + elif req_sn <= S_ND + X_ND + S_NH: + S_NH -= (req_sn - S_ND - X_ND) + S_ND = X_ND = 0 + else: + warn('Additional soluble inert COD is mapped to S_su.') + SI_cod = (S_ND + X_ND + S_NH)/S_I_i_N + S_su += S_I - SI_cod + S_I = SI_cod + S_ND = X_ND = S_NH = 0 + + # Step 6: map any remaining TKN + S_IN = S_ND + X_ND + S_NH + + # Step 8: check COD and TKN balance + # has TKN: S_aa, S_IN, S_I, X_pr, X_I + S_IC = S_cat = S_an = 0 + adm_vals = np.array([ + S_su, S_aa, + 0, 0, 0, 0, 0, # S_fa, S_va, S_bu, S_pro, S_ac, + 0, 0, # S_h2, S_ch4, + S_IC, S_IN, S_I, + 0, # X_c, + X_ch, X_pr, X_li, + 0, 0, 0, 0, 0, 0, 0, # X_su, X_aa, X_fa, X_c4, X_pro, X_ac, X_h2, + X_I, S_cat, S_an, H2O]) + + adm_vals = f_corr(asm_vals, adm_vals) + + # Step 7: charge balance + asm_charge_tot = _snh/14 - _sno/14 - _salk/12 + #!!! charge balance should technically include VFAs, + # but VFAs concentrations are assumed zero per previous steps?? + S_IN = adm_vals[adm_ions_idx[0]] + S_IC = (asm_charge_tot - S_IN*alpha_IN)/alpha_IC + net_Scat = asm_charge_tot + proton_charge + if net_Scat > 0: + S_cat = net_Scat + S_an = 0 + else: + S_cat = 0 + S_an = -net_Scat + + adm_vals[adm_ions_idx[1:]] = [S_IC, S_cat, S_an] + + return adm_vals + + self._reactions = asm2adm From e7cb509504c523769fcf67e4b22cf73d61a5dba7 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Tue, 4 Apr 2023 17:18:54 -0500 Subject: [PATCH 095/424] Acid-Base Chemistry (ADM1 P extension) Changed positions of weak_acid array components to account for new components in modified ADM1 --- qsdsan/processes/_adm1_p_extension.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/qsdsan/processes/_adm1_p_extension.py b/qsdsan/processes/_adm1_p_extension.py index 92e154e5..59b25b4e 100644 --- a/qsdsan/processes/_adm1_p_extension.py +++ b/qsdsan/processes/_adm1_p_extension.py @@ -230,8 +230,9 @@ def acid_base_rxn(h_ion, weak_acids_tot, Kas): Kw = Kas[0] oh_ion = Kw/h_ion nh3, h2po4, hco3, ac, pro, bu, va = Kas[1:] * weak_acids_tot[4:] / (Kas[1:] + h_ion) - return S_cat + S_K + S_Mg + h_ion + (S_IN - nh3) - S_an - oh_ion - hco3 - ac - pro - bu - va - (3*S_IP + h2po4) + return S_cat + S_K + 2*S_Mg + h_ion + (S_IN - nh3) - S_an - oh_ion - hco3 - ac - pro - bu - va - (3*S_IP + h2po4) +# The function 'fprime_abr' is not used in the code def fprime_abr(h_ion, weak_acids_tot, Kas): S_cat, S_K, S_Mg, S_an, S_IN, S_IP = weak_acids_tot[:6] Kw = Kas[0] @@ -303,8 +304,8 @@ def rhos_adm1_p_extension(state_arr, params): unit_conversion = mass2mol_conversion(cmps) cmps_in_M = state_arr[:32] * unit_conversion # weak acids (ADM1) = [S_ca, S_an, S_IN, S_IC, S_ac, S_pro, S_bu, S_va] - # weak acids (modified_ADM1) = [S_ca, S_an, S_IN, S_IP, S_IC, S_ac, S_pro, S_bu, S_va] - weak_acids = cmps_in_M[[29, 30, 10, 11, 9, 6, 5, 4, 3]] + # weak acids (modified_ADM1) = [S_ca, S_K, S_Mg, S_an, S_IN, S_IP, S_IC, S_ac, S_pro, S_bu, S_va] + weak_acids = cmps_in_M[[29, 27, 28, 30, 10, 11, 9, 6, 5, 4, 3]] T_op = state_arr[-1] biogas_S = state_arr[7:10].copy() @@ -338,8 +339,8 @@ def rhos_adm1_p_extension(state_arr, params): args=(weak_acids, Kas), xtol=1e-12, maxiter=100) - nh3 = Kas[1] * weak_acids[2] / (Kas[1] + h) - co2 = weak_acids[3] - Kas[2] * weak_acids[3] / (Kas[2] + h) + nh3 = Kas[1] * weak_acids[4] / (Kas[1] + h) + co2 = weak_acids[6] - Kas[2] * weak_acids[6] / (Kas[2] + h) biogas_S[-1] = co2 / unit_conversion[9] Iph = Hill_inhibit(h, pH_ULs, pH_LLs) From 0d5d7acbfda0e0e35e66560a1cebba040e3eea9c Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Tue, 4 Apr 2023 17:19:31 -0500 Subject: [PATCH 096/424] Edited typo in documentation Typo in documentation --- qsdsan/sanunits/_membrane_gas_extraction.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qsdsan/sanunits/_membrane_gas_extraction.py b/qsdsan/sanunits/_membrane_gas_extraction.py index 377d2a9d..ca45f3d7 100644 --- a/qsdsan/sanunits/_membrane_gas_extraction.py +++ b/qsdsan/sanunits/_membrane_gas_extraction.py @@ -23,7 +23,7 @@ class GasExtractionMembrane(SanUnit): """ - A Primary clarifier based on BSM2 Layout. [1] + Gas Extraction Membrane Parameters ---------- From ece3fd3bb4c965521aaee21a2ea566c6b3d13461 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Mon, 10 Apr 2023 16:47:28 -0500 Subject: [PATCH 097/424] Update gas extraction membrane Fixed minor errors for smooth functioning of gas extraction membrane Co-Authored-By: Joy Zhang <36645879+joyxyz1994@users.noreply.github.com> --- qsdsan/sanunits/_membrane_gas_extraction.py | 31 ++++++++++----------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/qsdsan/sanunits/_membrane_gas_extraction.py b/qsdsan/sanunits/_membrane_gas_extraction.py index ca45f3d7..b0fbfff0 100644 --- a/qsdsan/sanunits/_membrane_gas_extraction.py +++ b/qsdsan/sanunits/_membrane_gas_extraction.py @@ -101,7 +101,7 @@ class GasExtractionMembrane(SanUnit): } # Constructor: Initialize the instance variables - def __init__(self, ID='GEM', ins=None, outs=(), thermo=None, isdynamic=False, + def __init__(self, ID='GEM', ins=None, outs=(), thermo=None, isdynamic=True, init_with='WasteStream', F_BM_default=None, FiberID=190e-6, FiberOD=300e-6, NumTubes=1512, ShellDia=1.89e-2, SurfArea=0.1199, GasID = ['H2', 'CO2', 'CH4', 'O2', 'N2', 'H2O'], PVac = 97.325, @@ -271,11 +271,11 @@ def set_prop(self, attr_name, **kwargs): attr[idxr(k)] = v def _init_state(self): - inf = self.ins + inf, = self.ins cmps = inf.components - C = self._ins_QC[:-1]/cmps.chem_MW*cmps.i_mass + C = self._ins_QC[0,:-1]/cmps.chem_MW*cmps.i_mass Cs = C[self.idx] #idx selects only gases - Seg = self.segments + Seg = self.segs numGas = len(self.GasID) self._state = np.zeros(2*Seg*numGas) @@ -286,8 +286,7 @@ def _init_state(self): def _update_state(self): - - inf = self.ins + inf, = self.ins cmps = inf.components idx = self.idx numGas = len(self.GasID) @@ -299,7 +298,7 @@ def _update_state(self): # The of the effluent gas in extraction membrane is the difference # between lumen concentration in the last and first segment gas_state_in_unit = self._state[ -2*numGas: -numGas] - self._state[ :numGas] # in mol/m3 - Molar_flow_gases = self._ins_QC[-1]*gas_state_in_unit # (m3/day)*(mol/m3) = mol/day + Molar_flow_gases = self._ins_QC[0,-1]*gas_state_in_unit # (m3/day)*(mol/m3) = mol/day Mass_flow_gases = Molar_flow_gases*cmps.chem_MW[self.idx] #(mol/day)*(g/mol) = (g/day) self._outs[0].state[idx] = Mass_flow_gases # (g/day) @@ -311,14 +310,14 @@ def _update_state(self): liquid_state_in_unit = self._state[-2*numGas: -numGas] # in mol/m3 liquid_state_in_unit = (liquid_state_in_unit*cmps.chem_MW[self.idx])/cmps.i_mass[self.idx] # (mol/m3)*(g/mol) = g/m3 = mg/l - self._outs[1].state = self._ins_QC + self._outs[1].state = self._ins_QC[0] self._outs[1].state[idx] = liquid_state_in_unit # self._outs[1].state[-1] = self._ins_QC[-1] def _update_dstate(self): - inf = self.ins + inf, = self.ins cmps = inf.components numGas = len(self.GasID) idx = self.idx @@ -328,7 +327,7 @@ def _update_dstate(self): # The of the effluent gas in extraction membrane is the difference # between lumen concentration in the last and first segment gas_dstate_in_unit = self._dstate[ -2*numGas: -numGas] - self._dstate[ :numGas] # in mol/m3 - Molar_dflow_gases = self._ins_dQC[-1]*gas_dstate_in_unit # (m3/day)*(mol/m3) = mol/day + Molar_dflow_gases = self._ins_dQC[0,-1]*gas_dstate_in_unit # (m3/day)*(mol/m3) = mol/day Mass_dflow_gases = Molar_dflow_gases*cmps.chem_MW[self.idx] #(mol/day)*(g/mol) = (g/day) self._outs[0].dstate[idx] = Mass_dflow_gases # (g/day) @@ -340,14 +339,14 @@ def _update_dstate(self): liquid_dstate_in_unit = self._dstate[-2*numGas: -numGas] # in mol/m3 liquid_dstate_in_unit = (liquid_dstate_in_unit*cmps.chem_MW[self.idx])/cmps.i_mass[self.idx] # (mol/m3)*(g/mol) = g/m3 = mg/l - self._outs[1].dstate = self._ins_dQC - self._outs[0].dstate[idx] = liquid_dstate_in_unit + self._outs[1].dstate = self._ins_dQC[0] + self._outs[1].dstate[idx] = liquid_dstate_in_unit # self._outs[1].dstate[-1] = self._ins_dQC[-1] def _run(self): s_in, = self.ins gas, liq = self.outs - liq.copy(s_in) + liq.copy_like(s_in) @property def ODE(self): @@ -360,7 +359,7 @@ def _compile_ODE(self): # Synthesizes the ODEs to simulate a batch reactor with side-flow gas extraction. The code takes in an object of class Membrane (Mem) and an array of objects of class Gas (GasVec). It also takes in an array of experimental conditions ExpCond. # Extract Operating Parameters from ExpCond - Q = self.ins.F_vol # Volumetric Flowrate [m3/sec] + Q = self.ins[0].F_vol # Volumetric Flowrate [m3/sec] T = self.ins[0].T # Temperature [K] P = self.PVac*1000 # Vacuum Pressure [Pa] #V = self.Volume # Volume of the Batch Tank [L] @@ -377,7 +376,7 @@ def _compile_ODE(self): l = self.MemThick # Membrane Thickness [m] num_tubes = self.NumTubes # Number of Tubes in the Module [] L = self.Length # Membrane Length [m] - Segs = self.segments # Number of segments? Ask Ian + Segs = self.segs # Number of segments? Ask Ian vFrac = self.VolFrac # Lumen/Shell Volume Fraction [m^3/m^3] # Pre-allocate vectors for gas thermophysical properties @@ -433,7 +432,7 @@ def _compile_ODE(self): sumCp_init = P/(R*T) sumCp_fin = np.zeros(Segs) - C = self._ins_QC[:-1]/cmps.chem_MW*cmps.i_mass # conc. in mol/m^3 as defined by Ian + C = self._ins_QC[0,:-1]/cmps.chem_MW*cmps.i_mass # conc. in mol/m^3 as defined by Ian Cs = C[self.idx] #self.idx ensures its only for gases _dstate = self._dstate From 555a1d79ece413861a828a8c66d0e75587ed7b12 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Tue, 11 Apr 2023 16:01:31 -0500 Subject: [PATCH 098/424] ASM2d-ADM1 interface Implemented step 1 of ASM2d-ADM1 interface --- qsdsan/sanunits/_junction_copy.py | 98 +++++++++++++++++++++---------- 1 file changed, 68 insertions(+), 30 deletions(-) diff --git a/qsdsan/sanunits/_junction_copy.py b/qsdsan/sanunits/_junction_copy.py index e13d456b..05bcde23 100644 --- a/qsdsan/sanunits/_junction_copy.py +++ b/qsdsan/sanunits/_junction_copy.py @@ -310,7 +310,7 @@ def Ka_dH(self): def pKa(self): ''' [numpy.array] pKa array of the following acid-base pairs: - ('H+', 'OH-'), ('NH4+', 'NH3'), ('CO2', 'HCO3-'), + ('H+', 'OH-'), ('NH4+', 'NH3'), ('H3PO4', 'H2PO4-'), ('CO2', 'HCO3-'), ('HAc', 'Ac-'), ('HPr', 'Pr-'), ('HBu', 'Bu-'), ('HVa', 'Va-') ''' return self.pKa_base-np.log10(np.exp(pc.T_correction_factor(self.T_base, self.T, self.Ka_dH))) @@ -693,9 +693,10 @@ def _compile_reactions(self): atol = self.atol cmps_asm = ins.components - S_NO_i_COD = cmps_asm.S_NO.i_COD - X_BH_i_N = cmps_asm.X_BH.i_N - X_BA_i_N = cmps_asm.X_BA.i_N + S_NO3_i_COD = cmps_asm.S_NO3.i_COD + X_H_i_N = cmps_asm.X_H.i_N + X_AUT_i_N = cmps_asm.X_AUT.i_N + X_PAO_i_N = cmps_asm.X_PAO.i_N asm_X_I_i_N = cmps_asm.X_I.i_N X_P_i_N = cmps_asm.X_P.i_N if cmps_asm.X_S.i_N > 0: @@ -721,38 +722,75 @@ def _compile_reactions(self): f_corr = self.balance_cod_tkn def asm2adm(asm_vals): - S_I, S_S, X_I, X_S, X_BH, X_BA, X_P, S_O, S_NO, S_NH, S_ND, X_ND, S_ALK, S_N2, H2O = asm_vals + # S_I, S_S, X_I, X_S, X_BH, X_BA, X_P, S_O, S_NO, S_NH, S_ND, X_ND, S_ALK, S_N2, H2O = asm_vals + + S_O2, S_N2, S_NH4, S_NO3, S_PO4, S_F, S_A, S_I, S_ALK, X_I, X_S, X_H, \ + X_PAO, X_PP, X_PHA, X_AUT, X_MeOH, X_MeP, H2O = asm_vals # Step 0: charged component snapshot - _sno = S_NO - _snh = S_NH + _sno3 = S_NO3 + _snh4 = S_NH4 _salk = S_ALK - + _spo4 = S_PO4 + # Step 1: remove any remaining COD demand - O_coddm = S_O - NO_coddm = -S_NO*S_NO_i_COD - cod_spl = S_S + X_S + X_BH + X_BA - bioN = X_BH*X_BH_i_N + X_BA*X_BA_i_N + O2_coddm = S_O2 + NO3_coddm = -S_NO3*S_NO3_i_COD + + # cod_spl = S_S + X_S + X_BH + X_BA + # Replacing S_S with S_F + S_A + X_PHA (since all of them are measured as COD and are degradable) + # added X_PAO to X_H and X_AUT + + cod_spl = S_F + S_A + X_S + X_PHA + X_H + X_AUT + X_PAO - if cod_spl <= O_coddm: - S_O = O_coddm - cod_spl - S_S = X_S = X_BH = X_BA = 0 - elif cod_spl <= O_coddm + NO_coddm: - S_O = 0 - S_NO = -(O_coddm + NO_coddm - cod_spl)/S_NO_i_COD - S_S = X_S = X_BH = X_BA = 0 + #bioN = X_BH*X_BH_i_N + X_BA*X_BA_i_N + #Added PAO along with X_H and X_AUT to account for biological N removal + + bioN = X_H*X_H_i_N + X_AUT*X_AUT_i_N + X_PAO*X_PAO_i_N + + if cod_spl <= O2_coddm: + S_O2 = O2_coddm - cod_spl + # S_S = X_S = X_BH = X_BA = 0 + S_F = S_A = X_S = X_PHA = X_H = X_AUT = X_PAO = 0 + elif cod_spl <= O2_coddm + NO3_coddm: + S_O2 = 0 + S_NO3 = -(O2_coddm + NO3_coddm - cod_spl)/S_NO3_i_COD + #S_S = X_S = X_BH = X_BA = 0 + + # X_PHA should come after X_S, since X_S >> X_PHA (Joy) + S_F = S_A = X_S = X_PHA = X_H = X_AUT = X_PAO = 0 else: - S_S -= O_coddm + NO_coddm - if S_S < 0: - X_S += S_S - S_S = 0 - if X_S < 0: - X_BH += X_S - X_S = 0 - if X_BH < 0: - X_BA += X_BH - X_BH = 0 - S_O = S_NO = 0 + # S_S -= O_coddm + NO_coddm + # if S_S < 0: + # X_S += S_S + # S_S = 0 + # if X_S < 0: + # X_BH += X_S + # X_S = 0 + # if X_BH < 0: + # X_BA += X_BH + # X_BH = 0 + # S_O = S_NO = 0 + S_F -= O2_coddm + NO3_coddm + if S_F < 0: + S_A += S_F + S_F = 0 + if S_A < 0: + X_S += S_A + S_A = 0 + if X_S < 0: + X_PHA += X_S + X_S = 0 + if X_PHA < 0: + X_H += X_PHA + X_PHA = 0 + if X_H < 0: + X_AUT += X_H + X_H = 0 + if X_AUT < 0: + X_PAO += X_AUT + X_AUT = 0 + S_O2 = S_NO3 = 0 # Step 2: convert any readily biodegradable # COD and TKN into amino acids and sugars From 64734747972fc9ad4af3ae5e7ba61090755a75e0 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Mon, 17 Apr 2023 16:01:28 -0500 Subject: [PATCH 099/424] Corrected gas effluent calculation Corrected the update state and dstate of the Gas effluent. --- qsdsan/sanunits/_membrane_gas_extraction.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/qsdsan/sanunits/_membrane_gas_extraction.py b/qsdsan/sanunits/_membrane_gas_extraction.py index b0fbfff0..1a89aa49 100644 --- a/qsdsan/sanunits/_membrane_gas_extraction.py +++ b/qsdsan/sanunits/_membrane_gas_extraction.py @@ -297,7 +297,7 @@ def _update_state(self): self._outs[0].state = np.zeros(len(cmps) + 1) # The of the effluent gas in extraction membrane is the difference # between lumen concentration in the last and first segment - gas_state_in_unit = self._state[ -2*numGas: -numGas] - self._state[ :numGas] # in mol/m3 + gas_state_in_unit = self._state[ :numGas] - self._state[ -2*numGas: -numGas] # in mol/m3 Molar_flow_gases = self._ins_QC[0,-1]*gas_state_in_unit # (m3/day)*(mol/m3) = mol/day Mass_flow_gases = Molar_flow_gases*cmps.chem_MW[self.idx] #(mol/day)*(g/mol) = (g/day) @@ -326,7 +326,7 @@ def _update_dstate(self): self._outs[0].dstate = np.zeros(len(cmps) + 1) # The of the effluent gas in extraction membrane is the difference # between lumen concentration in the last and first segment - gas_dstate_in_unit = self._dstate[ -2*numGas: -numGas] - self._dstate[ :numGas] # in mol/m3 + gas_dstate_in_unit = self._dstate[ :numGas] - self._dstate[ -2*numGas: -numGas]# in mol/m3 Molar_dflow_gases = self._ins_dQC[0,-1]*gas_dstate_in_unit # (m3/day)*(mol/m3) = mol/day Mass_dflow_gases = Molar_dflow_gases*cmps.chem_MW[self.idx] #(mol/day)*(g/mol) = (g/day) @@ -346,7 +346,9 @@ def _update_dstate(self): def _run(self): s_in, = self.ins gas, liq = self.outs + gas.phase = 'g' liq.copy_like(s_in) + @property def ODE(self): From 49622070cc97be22a1956585f78dbd8e1d9ba4af Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Tue, 25 Apr 2023 13:07:21 -0500 Subject: [PATCH 100/424] ASM2d-ADM1: Step 1 Added charged species in ASM2d based on ASM2d IWA handbook Changed the order of substrates based on estimated magnitude (Joy's idea) Trivial changes: some comments are reframed --- qsdsan/sanunits/_junction_copy.py | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/qsdsan/sanunits/_junction_copy.py b/qsdsan/sanunits/_junction_copy.py index 05bcde23..740cd6d7 100644 --- a/qsdsan/sanunits/_junction_copy.py +++ b/qsdsan/sanunits/_junction_copy.py @@ -730,8 +730,9 @@ def asm2adm(asm_vals): # Step 0: charged component snapshot _sno3 = S_NO3 _snh4 = S_NH4 - _salk = S_ALK + _salk = S_ALK # HCO3- (pg. 82 IWA ASM models handbook) _spo4 = S_PO4 + _sa = S_A # S_A is basically acetate (pg. 82 IWA ASM models handbook) # Step 1: remove any remaining COD demand O2_coddm = S_O2 @@ -741,10 +742,10 @@ def asm2adm(asm_vals): # Replacing S_S with S_F + S_A + X_PHA (since all of them are measured as COD and are degradable) # added X_PAO to X_H and X_AUT - cod_spl = S_F + S_A + X_S + X_PHA + X_H + X_AUT + X_PAO + cod_spl = (S_A + S_F) + X_S + X_PHA + (X_H + X_AUT + X_PAO) #bioN = X_BH*X_BH_i_N + X_BA*X_BA_i_N - #Added PAO along with X_H and X_AUT to account for biological N removal + #Added X_PAO along with X_H and X_AUT bioN = X_H*X_H_i_N + X_AUT*X_AUT_i_N + X_PAO*X_PAO_i_N @@ -754,11 +755,12 @@ def asm2adm(asm_vals): S_F = S_A = X_S = X_PHA = X_H = X_AUT = X_PAO = 0 elif cod_spl <= O2_coddm + NO3_coddm: S_O2 = 0 + S_NO3 = -(O2_coddm + NO3_coddm - cod_spl)/S_NO3_i_COD #S_S = X_S = X_BH = X_BA = 0 # X_PHA should come after X_S, since X_S >> X_PHA (Joy) - S_F = S_A = X_S = X_PHA = X_H = X_AUT = X_PAO = 0 + S_A = S_F = X_S = X_PHA = X_H = X_AUT = X_PAO = 0 else: # S_S -= O_coddm + NO_coddm # if S_S < 0: @@ -771,13 +773,13 @@ def asm2adm(asm_vals): # X_BA += X_BH # X_BH = 0 # S_O = S_NO = 0 - S_F -= O2_coddm + NO3_coddm - if S_F < 0: - S_A += S_F - S_F = 0 - if S_A < 0: - X_S += S_A - S_A = 0 + S_A -= O2_coddm + NO3_coddm + if S_A < 0: + S_F += S_A + S_A = 0 + if S_F < 0: + X_S += S_F + S_F = 0 if X_S < 0: X_PHA += X_S X_S = 0 @@ -796,6 +798,8 @@ def asm2adm(asm_vals): # COD and TKN into amino acids and sugars # Assumed S_S, X_S has no nitrogen req_scod = S_ND / S_aa_i_N + + if S_S < req_scod: S_aa = S_S S_su = 0 From 1f46b3ef1b223f48ccec10146bfe6a773b753d3d Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Tue, 25 Apr 2023 14:03:53 -0500 Subject: [PATCH 101/424] ASM2d-ADM1: Step 2 and Step 3 convert any readily biodegradable COD and TKN into amino acids and sugars convert any slowly biodegradable COD and TKN into proteins, lipids, and carbohydrates --- qsdsan/sanunits/_junction_copy.py | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/qsdsan/sanunits/_junction_copy.py b/qsdsan/sanunits/_junction_copy.py index 740cd6d7..e032adce 100644 --- a/qsdsan/sanunits/_junction_copy.py +++ b/qsdsan/sanunits/_junction_copy.py @@ -797,30 +797,36 @@ def asm2adm(asm_vals): # Step 2: convert any readily biodegradable # COD and TKN into amino acids and sugars # Assumed S_S, X_S has no nitrogen - req_scod = S_ND / S_aa_i_N + S_S_asm1 = S_F + S_A # S_S (in asm1) equals to the sum of S_F and S_A (pg. 82 IWA ASM models handbook) + S_ND_asm1 = S_F*S_F.i_N #S_ND (in asm1) equals the N content in S_F (Joy) - if S_S < req_scod: - S_aa = S_S + req_scod = S_ND_asm1 / S_aa_i_N + + if S_S_asm1 < req_scod: + S_aa = S_S_asm1 S_su = 0 - S_ND -= S_aa * S_aa_i_N + S_ND_asm1 -= S_aa * S_aa_i_N else: S_aa = req_scod - S_su = S_S - S_aa - S_ND = 0 + S_su = S_S_asm1 - S_aa + S_ND_asm1 = 0 # Step 3: convert slowly biodegradable COD and TKN # into proteins, lipids, and carbohydrates - req_xcod = X_ND / X_pr_i_N + + X_ND_asm1 = X_S*X_S.i_N #X_ND (in asm1) equals the N content in X_S (Joy) + + req_xcod = X_ND_asm1 / X_pr_i_N if X_S < req_xcod: X_pr = X_S X_li = X_ch = 0 - X_ND -= X_pr * X_pr_i_N + X_ND_asm1 -= X_pr * X_pr_i_N else: X_pr = req_xcod X_li = self.xs_to_li * (X_S - X_pr) X_ch = (X_S - X_pr) - X_li - X_ND = 0 + X_ND_asm1 = 0 # Step 4: convert active biomass into protein, lipids, # carbohydrates and potentially particulate TKN From 45469c98d02d16fd0b5f603dbe24114873d5d3f2 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Tue, 25 Apr 2023 16:42:38 -0500 Subject: [PATCH 102/424] ASM2d-ADM1: Step 2 and Step 3 The available soluble and particulate organic N used in step 2 and step 3 should be calculated before step 1. Changes were made to ensure the same. --- qsdsan/sanunits/_junction_copy.py | 28 ++++++++++------------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/qsdsan/sanunits/_junction_copy.py b/qsdsan/sanunits/_junction_copy.py index e032adce..33a6cb31 100644 --- a/qsdsan/sanunits/_junction_copy.py +++ b/qsdsan/sanunits/_junction_copy.py @@ -697,6 +697,9 @@ def _compile_reactions(self): X_H_i_N = cmps_asm.X_H.i_N X_AUT_i_N = cmps_asm.X_AUT.i_N X_PAO_i_N = cmps_asm.X_PAO.i_N + S_F_i_N = cmps_asm.S_F.i_N + X_S_i_N = cmps_asm.X_S.i_N + asm_X_I_i_N = cmps_asm.X_I.i_N X_P_i_N = cmps_asm.X_P.i_N if cmps_asm.X_S.i_N > 0: @@ -727,12 +730,13 @@ def asm2adm(asm_vals): S_O2, S_N2, S_NH4, S_NO3, S_PO4, S_F, S_A, S_I, S_ALK, X_I, X_S, X_H, \ X_PAO, X_PP, X_PHA, X_AUT, X_MeOH, X_MeP, H2O = asm_vals - # Step 0: charged component snapshot + # Step 0: charged component snapshot (# pg. 84 of IWA ASM textbook) _sno3 = S_NO3 _snh4 = S_NH4 - _salk = S_ALK # HCO3- (pg. 82 IWA ASM models handbook) + _salk = S_ALK _spo4 = S_PO4 - _sa = S_A # S_A is basically acetate (pg. 82 IWA ASM models handbook) + _sa = S_A + _xpp = X_PP # Step 1: remove any remaining COD demand O2_coddm = S_O2 @@ -749,6 +753,9 @@ def asm2adm(asm_vals): bioN = X_H*X_H_i_N + X_AUT*X_AUT_i_N + X_PAO*X_PAO_i_N + S_ND_asm1 = S_F*S_F_i_N #S_ND (in asm1) equals the N content in S_F (Joy) + X_ND_asm1 = X_S*X_S_i_N #X_ND (in asm1) equals the N content in X_S (Joy) + if cod_spl <= O2_coddm: S_O2 = O2_coddm - cod_spl # S_S = X_S = X_BH = X_BA = 0 @@ -762,17 +769,6 @@ def asm2adm(asm_vals): # X_PHA should come after X_S, since X_S >> X_PHA (Joy) S_A = S_F = X_S = X_PHA = X_H = X_AUT = X_PAO = 0 else: - # S_S -= O_coddm + NO_coddm - # if S_S < 0: - # X_S += S_S - # S_S = 0 - # if X_S < 0: - # X_BH += X_S - # X_S = 0 - # if X_BH < 0: - # X_BA += X_BH - # X_BH = 0 - # S_O = S_NO = 0 S_A -= O2_coddm + NO3_coddm if S_A < 0: S_F += S_A @@ -796,10 +792,8 @@ def asm2adm(asm_vals): # Step 2: convert any readily biodegradable # COD and TKN into amino acids and sugars - # Assumed S_S, X_S has no nitrogen S_S_asm1 = S_F + S_A # S_S (in asm1) equals to the sum of S_F and S_A (pg. 82 IWA ASM models handbook) - S_ND_asm1 = S_F*S_F.i_N #S_ND (in asm1) equals the N content in S_F (Joy) req_scod = S_ND_asm1 / S_aa_i_N @@ -815,8 +809,6 @@ def asm2adm(asm_vals): # Step 3: convert slowly biodegradable COD and TKN # into proteins, lipids, and carbohydrates - X_ND_asm1 = X_S*X_S.i_N #X_ND (in asm1) equals the N content in X_S (Joy) - req_xcod = X_ND_asm1 / X_pr_i_N if X_S < req_xcod: X_pr = X_S From dc0097f1e954f8e2a79b1f3f5018dfe6d84fd912 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Tue, 25 Apr 2023 16:49:44 -0500 Subject: [PATCH 103/424] ASM2d-ADM1: Step 7 Implemented charge balance based on IWA ASM handbook (pg. 84) --- qsdsan/sanunits/_junction_copy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qsdsan/sanunits/_junction_copy.py b/qsdsan/sanunits/_junction_copy.py index 33a6cb31..abfa3b40 100644 --- a/qsdsan/sanunits/_junction_copy.py +++ b/qsdsan/sanunits/_junction_copy.py @@ -888,7 +888,7 @@ def asm2adm(asm_vals): adm_vals = f_corr(asm_vals, adm_vals) # Step 7: charge balance - asm_charge_tot = _snh/14 - _sno/14 - _salk/12 + asm_charge_tot = - _sa/64 + _snh4/14 - _sno3/14 - 1.5*_spo4/31 - _salk - _xpp/31 #Based on page 84 of IWA ASM handbook #!!! charge balance should technically include VFAs, # but VFAs concentrations are assumed zero per previous steps?? S_IN = adm_vals[adm_ions_idx[0]] From 2fe2e16e9a02bc0c0416c657adf07f52d51ef7f2 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Wed, 26 Apr 2023 16:13:05 -0500 Subject: [PATCH 104/424] ASM2d-ADM1: Step 4 Updated the composition of biomass from ASM1 to ASM2d. --- qsdsan/sanunits/_junction_copy.py | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/qsdsan/sanunits/_junction_copy.py b/qsdsan/sanunits/_junction_copy.py index abfa3b40..8a8c01b8 100644 --- a/qsdsan/sanunits/_junction_copy.py +++ b/qsdsan/sanunits/_junction_copy.py @@ -753,19 +753,17 @@ def asm2adm(asm_vals): bioN = X_H*X_H_i_N + X_AUT*X_AUT_i_N + X_PAO*X_PAO_i_N + # To be used in Step 2 S_ND_asm1 = S_F*S_F_i_N #S_ND (in asm1) equals the N content in S_F (Joy) + # To be used in Step 3 X_ND_asm1 = X_S*X_S_i_N #X_ND (in asm1) equals the N content in X_S (Joy) if cod_spl <= O2_coddm: S_O2 = O2_coddm - cod_spl - # S_S = X_S = X_BH = X_BA = 0 S_F = S_A = X_S = X_PHA = X_H = X_AUT = X_PAO = 0 elif cod_spl <= O2_coddm + NO3_coddm: S_O2 = 0 - S_NO3 = -(O2_coddm + NO3_coddm - cod_spl)/S_NO3_i_COD - #S_S = X_S = X_BH = X_BA = 0 - # X_PHA should come after X_S, since X_S >> X_PHA (Joy) S_A = S_F = X_S = X_PHA = X_H = X_AUT = X_PAO = 0 else: @@ -822,22 +820,22 @@ def asm2adm(asm_vals): # Step 4: convert active biomass into protein, lipids, # carbohydrates and potentially particulate TKN - available_bioN = bioN - (X_BH+X_BA) * (1-frac_deg) * adm_X_I_i_N + available_bioN = bioN - (X_H + X_AUT + X_PAO) * (1-frac_deg) * adm_X_I_i_N if available_bioN < 0: raise RuntimeError('Not enough N in X_BA and X_BH to fully convert ' 'the non-biodegradable portion into X_I in ADM1.') - req_bioN = (X_BH+X_BA) * frac_deg * X_pr_i_N - if available_bioN + X_ND >= req_bioN: - X_pr += (X_BH+X_BA) * frac_deg - X_ND += available_bioN - req_bioN + req_bioN = (X_H + X_AUT + X_PAO) * frac_deg * X_pr_i_N + if available_bioN + X_ND_asm1 >= req_bioN: + X_pr += (X_H + X_AUT + X_PAO) * frac_deg + X_ND_asm1 += available_bioN - req_bioN else: - bio2pr = (available_bioN + X_ND)/X_pr_i_N + bio2pr = (available_bioN + X_ND_asm1)/X_pr_i_N X_pr += bio2pr - bio_to_split = (X_BH+X_BA) * frac_deg - bio2pr + bio_to_split = (X_H + X_AUT + X_PAO) * frac_deg - bio2pr bio_split_to_li = bio_to_split * self.bio_to_li X_li += bio_split_to_li X_ch += (bio_to_split - bio_split_to_li) - X_ND = 0 + X_ND_asm1 = 0 # Step 5: map particulate inerts xi_nsp = X_P_i_N * X_P + asm_X_I_i_N * X_I From 2133d0e04efbc734ab91778c5a32932bd7576ec2 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Thu, 27 Apr 2023 09:35:10 -0500 Subject: [PATCH 105/424] Temporary change in gas extraction membrane Re-ordered gases based on implementation code. Should not be needed in the longer run. --- qsdsan/sanunits/_membrane_gas_extraction.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/qsdsan/sanunits/_membrane_gas_extraction.py b/qsdsan/sanunits/_membrane_gas_extraction.py index 1a89aa49..c27473a2 100644 --- a/qsdsan/sanunits/_membrane_gas_extraction.py +++ b/qsdsan/sanunits/_membrane_gas_extraction.py @@ -75,28 +75,28 @@ class GasExtractionMembrane(SanUnit): _HenryPreFac = { 'H2': 7.8e-6, - 'CO2' : 3.5e-4, - 'CH4': 1.3e-5, 'O2': 1.2e-5, 'N2': 6e-6, + 'CO2' : 3.5e-4, + 'CH4': 1.3e-5, 'H2O': 1 } _HenrySlope = { 'H2': 640, - 'CO2' : 2600, - 'CH4' : 1900, 'O2': 1800, 'N2': 1300, + 'CO2' : 2600, + 'CH4' : 1900, 'H2O': 1 } _WilkeChang = { 'H2': 9.84, - 'CO2': 2.6, - 'CH4': 2.2, 'O2': 1.90, 'N2': 1.77, + 'CO2': 2.6, + 'CH4': 2.2, 'H2O': 1 } @@ -104,7 +104,7 @@ class GasExtractionMembrane(SanUnit): def __init__(self, ID='GEM', ins=None, outs=(), thermo=None, isdynamic=True, init_with='WasteStream', F_BM_default=None, FiberID=190e-6, FiberOD=300e-6, NumTubes=1512, ShellDia=1.89e-2, SurfArea=0.1199, - GasID = ['H2', 'CO2', 'CH4', 'O2', 'N2', 'H2O'], PVac = 97.325, + GasID = ['H2', 'O2', 'N2', 'CO2', 'CH4', 'H2O'], PVac = 97.325, segs = 50, GasPerm = {}, HenryPreFac = {}, HenrySlope = {}, WilkeChang = {}): From 996b1e231b903ddd1aa180fa26ec2ad8cc70b3b8 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Mon, 1 May 2023 18:07:09 -0500 Subject: [PATCH 106/424] Make code more efficient --- qsdsan/sanunits/_membrane_gas_extraction.py | 306 ++++++++++++-------- 1 file changed, 189 insertions(+), 117 deletions(-) diff --git a/qsdsan/sanunits/_membrane_gas_extraction.py b/qsdsan/sanunits/_membrane_gas_extraction.py index c27473a2..334443d7 100644 --- a/qsdsan/sanunits/_membrane_gas_extraction.py +++ b/qsdsan/sanunits/_membrane_gas_extraction.py @@ -137,10 +137,13 @@ def __init__(self, ID='GEM', ins=None, outs=(), thermo=None, isdynamic=True, # and not all components in the influent self.idx = cmps.indices(self.GasID) # to save the index of water in the array of gases, to be used later - for i, ID in enumerate(GasID): - if ID == 'H2O': - self.h2o_j = i - break + # for i, ID in enumerate(GasID): + # if ID == 'H2O': + # self.h2o_j = i + # break + #!!! alternatively + self.h2o_j = GasID.index('H2O') + self.gas_mass2mol = (cmps.i_mass/cmps.chem_MW)[self.idx] @property def FiberOD(self): @@ -271,77 +274,98 @@ def set_prop(self, attr_name, **kwargs): attr[idxr(k)] = v def _init_state(self): - inf, = self.ins - cmps = inf.components - C = self._ins_QC[0,:-1]/cmps.chem_MW*cmps.i_mass - Cs = C[self.idx] #idx selects only gases - Seg = self.segs + # inf, = self.ins + # cmps = inf.components + # C = self._ins_QC[0,:-1]/cmps.chem_MW*cmps.i_mass + # Cs = C[self.idx] #idx selects only gases + # Seg = self.segs numGas = len(self.GasID) - self._state = np.zeros(2*Seg*numGas) - - for i in range(0, 2*Seg*numGas, 2*numGas): - for j in range(numGas): - self._state[j+i] = Cs[j] + # self._state = np.zeros(2*Seg*numGas) + # for i in range(0, 2*Seg*numGas, 2*numGas): + # for j in range(numGas): + # self._state[j+i] = Cs[j] + seg_i = np.zeros(2*numGas) + seg_i[:numGas] = self._ins_QC[0,self.idx]*self.gas_mass2mol + self._state = np.tile(seg_i, self.segs) self._dstate = self._state*0 - def _update_state(self): inf, = self.ins - cmps = inf.components + gas, liq = self.outs idx = self.idx numGas = len(self.GasID) - + mass2mol = self.gas_mass2mol + y = self._state # Need to add effluent streams for liquid (this includes all cmps of influent ws) and gas. # The multiplication of any of the first n-1 array element with last element should give out g/day values. - self._outs[0].state = np.zeros(len(cmps) + 1) + if gas.state is None: + cmps = inf.components + gas.state = np.zeros(len(cmps) + 1) + liq.state = np.zeros(len(cmps) + 1) + + liq.state[:] = inf.state + liq.state[idx] = y[-2*numGas: -numGas]/mass2mol + + gas.state[-1] = 1 + gas.state[idx] = (y[:numGas] - y[-2*numGas: -numGas])/mass2mol * liq.state[-1] + # The of the effluent gas in extraction membrane is the difference # between lumen concentration in the last and first segment - gas_state_in_unit = self._state[ :numGas] - self._state[ -2*numGas: -numGas] # in mol/m3 - Molar_flow_gases = self._ins_QC[0,-1]*gas_state_in_unit # (m3/day)*(mol/m3) = mol/day - Mass_flow_gases = Molar_flow_gases*cmps.chem_MW[self.idx] #(mol/day)*(g/mol) = (g/day) - - self._outs[0].state[idx] = Mass_flow_gases # (g/day) - self._outs[0].state[-1] = 1 #(So the mutiplication with Q would give out g/day values) + #!!! why? It seems this only holds when the unit is at steady state + # gas_state_in_unit = y[:numGas] - y[-2*numGas: -numGas] # in mol/m3 + # Molar_flow_gases = self._ins_QC[0,-1]*gas_state_in_unit # (m3/day)*(mol/m3) = mol/day + # Mass_flow_gases = Molar_flow_gases*cmps.chem_MW[idx] #(mol/day)*(g/mol) = (g/day) - self._outs[1].state = np.zeros(len(cmps) + 1) - # The state of effluent Liquid stream is simply the concentration of - # the last lumen segment in the extraction membrane - liquid_state_in_unit = self._state[-2*numGas: -numGas] # in mol/m3 - liquid_state_in_unit = (liquid_state_in_unit*cmps.chem_MW[self.idx])/cmps.i_mass[self.idx] # (mol/m3)*(g/mol) = g/m3 = mg/l + # self._outs[0].state[idx] = Mass_flow_gases # (g/day) + # self._outs[0].state[-1] = 1 #(So the mutiplication with Q would give out g/day values) - self._outs[1].state = self._ins_QC[0] - self._outs[1].state[idx] = liquid_state_in_unit + # # The state of effluent Liquid stream is simply the concentration of + # # the last lumen segment in the extraction membrane + # liquid_state_in_unit = y[-2*numGas: -numGas] # in mol/m3 + # liquid_state_in_unit = (liquid_state_in_unit*cmps.chem_MW[idx])/cmps.i_mass[idx] # (mol/m3)*(g/mol) = g/m3 = mg/l - # self._outs[1].state[-1] = self._ins_QC[-1] + # self._outs[1].state[:] = self._ins_QC[0] + # self._outs[1].state[idx] = liquid_state_in_unit - def _update_dstate(self): + def _update_dstate(self): inf, = self.ins - cmps = inf.components + gas, liq = self.outs numGas = len(self.GasID) + mass2mol = self.gas_mass2mol idx = self.idx + dy = self._dstate - - self._outs[0].dstate = np.zeros(len(cmps) + 1) - # The of the effluent gas in extraction membrane is the difference - # between lumen concentration in the last and first segment - gas_dstate_in_unit = self._dstate[ :numGas] - self._dstate[ -2*numGas: -numGas]# in mol/m3 - Molar_dflow_gases = self._ins_dQC[0,-1]*gas_dstate_in_unit # (m3/day)*(mol/m3) = mol/day - Mass_dflow_gases = Molar_dflow_gases*cmps.chem_MW[self.idx] #(mol/day)*(g/mol) = (g/day) - - self._outs[0].dstate[idx] = Mass_dflow_gases # (g/day) - self._outs[0].dstate[-1] = 0 # Just differentiating constant 1 to 0 - - self._outs[1].dstate = np.zeros(len(cmps) + 1) - # The state of effluent Liquid stream is simply the concentration of - # the last lumen segment in the extraction membrane - liquid_dstate_in_unit = self._dstate[-2*numGas: -numGas] # in mol/m3 - liquid_dstate_in_unit = (liquid_dstate_in_unit*cmps.chem_MW[self.idx])/cmps.i_mass[self.idx] # (mol/m3)*(g/mol) = g/m3 = mg/l - - self._outs[1].dstate = self._ins_dQC[0] - self._outs[1].dstate[idx] = liquid_dstate_in_unit - # self._outs[1].dstate[-1] = self._ins_dQC[-1] + if gas.dstate is None: + cmps = inf.components + gas.dstate = np.zeros(len(cmps) + 1) + liq.dstate = np.zeros(len(cmps) + 1) + + liq.dstate[:] = inf.dstate + liq.dstate[idx] = dy[-2*numGas: -numGas]/mass2mol + + #!!! this is probably wrong + gas.dstate[idx] = (dy[:numGas] - dy[-2*numGas: -numGas])/mass2mol * liq.dstate[-1] + + # self._outs[0].dstate = np.zeros(len(cmps) + 1) + # # The of the effluent gas in extraction membrane is the difference + # # between lumen concentration in the last and first segment + # gas_dstate_in_unit = self._dstate[ :numGas] - self._dstate[ -2*numGas: -numGas]# in mol/m3 + # Molar_dflow_gases = self._ins_dQC[0,-1]*gas_dstate_in_unit # (m3/day)*(mol/m3) = mol/day + # Mass_dflow_gases = Molar_dflow_gases*cmps.chem_MW[self.idx] #(mol/day)*(g/mol) = (g/day) + + # self._outs[0].dstate[idx] = Mass_dflow_gases # (g/day) + # self._outs[0].dstate[-1] = 0 # Just differentiating constant 1 to 0 + + # self._outs[1].dstate = np.zeros(len(cmps) + 1) + # # The state of effluent Liquid stream is simply the concentration of + # # the last lumen segment in the extraction membrane + # liquid_dstate_in_unit = self._dstate[-2*numGas: -numGas] # in mol/m3 + # liquid_dstate_in_unit = (liquid_dstate_in_unit*cmps.chem_MW[self.idx])/cmps.i_mass[self.idx] # (mol/m3)*(g/mol) = g/m3 = mg/l + + # self._outs[1].dstate = self._ins_dQC[0] + # self._outs[1].dstate[idx] = liquid_dstate_in_unit def _run(self): s_in, = self.ins @@ -356,16 +380,19 @@ def ODE(self): self._compile_ODE() return self._ODE - #def transientGasExtraction(t, C, ExpCond, GasVec, Mem, Segs, SS): def _compile_ODE(self): # Synthesizes the ODEs to simulate a batch reactor with side-flow gas extraction. The code takes in an object of class Membrane (Mem) and an array of objects of class Gas (GasVec). It also takes in an array of experimental conditions ExpCond. # Extract Operating Parameters from ExpCond - Q = self.ins[0].F_vol # Volumetric Flowrate [m3/sec] + # Q = self.ins[0].F_vol # Volumetric Flowrate [m3/sec] T = self.ins[0].T # Temperature [K] P = self.PVac*1000 # Vacuum Pressure [Pa] #V = self.Volume # Volume of the Batch Tank [L] + idx = self.idx + h2o_j = self.h2o_j + mass2mol = self.gas_mass2mol + # Calculate vapor pressure of water at operating temperature TCel = T-273.15 # Temperature [C] PVapH2O = np.exp(34.494-(4924.99/(TCel+237.1)))/((TCel+105)**1.57) # Saturated Vapor Pressure of Water [Pa] @@ -383,10 +410,10 @@ def _compile_ODE(self): # Pre-allocate vectors for gas thermophysical properties numGas = len(self.GasID) - numVec = 2*numGas + # numVec = 2*numGas inf, = self.ins - cmps = inf.components + # cmps = inf.components # Extract Gas Properties #for i in range(0,numGas): @@ -399,10 +426,11 @@ def _compile_ODE(self): #H[i] = GasVec[i].HeL() H = self.HeL - #Diff = np.array([0.0265e-7, 0.0296e-7, 0.0253e-7, 0.3199e-7]) + # Diff = np.array([0.0265e-7, 0.0296e-7, 0.0253e-7, 0.3199e-7]) # Calculate dx and u dx = L/Segs # Length of Segments [m] - u = Q/((np.pi*D**2/4)*num_tubes) # Linear Flow Velocity [m/s] + # u = Q/((np.pi*D**2/4)*num_tubes) # Linear Flow Velocity [m/s] + cross_section_A = ((np.pi*D**2/4)*num_tubes) # Calculate the Kinematic Viscosity of Water Tb = T/300 # Reduced Temperature [] @@ -410,88 +438,132 @@ def _compile_ODE(self): rho = (-13.851 + 0.64038*T - 1.9124e-3*T**2 + 1.8211e-6*T**3)*18 # Liquid Density of Water [kg/m^3] from DIPPR nu = mu/rho # Kinematic Viscosity of Water [m^2/s] - # Calculate the dimensionless numbers - # Reynolds - Re = u*D/nu + #!!! These variables should be calculated within ODE because it is + #!!! dependent on the influent Q, which could change during simulation + #!!! unless we can assume this change is negligible + + # # Calculate the dimensionless numbers + # # Reynolds + # Re = u*D/nu # Schmidt Sc = nu/Diff - # Sherwood - Sh = 1.615*(Re*Sc*D/L)**(1/3) + # # Sherwood + # Sh = 1.615*(Re*Sc*D/L)**(1/3) - # Calculate Mass Transfer Coefficients + # # Calculate Mass Transfer Coefficients KMem = Perm_SI/l - KLiq = Sh*Diff/D - KTot = 1/(1/KLiq + 1/(KMem*H)) - for j in range(0, numGas): - #if GasVec[j].Name == 'H2O': - if j == self.h2o_j: - KTot[j] = KMem[j] - + # KLiq = Sh*Diff/D + # KTot = 1/(1/KLiq + 1/(KMem*H)) + # # for j in range(0, numGas): + # # #if GasVec[j].Name == 'H2O': + # # if j == self.h2o_j: + # # KTot[j] = KMem[j] + # #!!! alternatively + # KTot[self.h2o_j] = KMem[self.h2o_j] + # Initialize - C = self._state - dC = self._dstate + # C = self._state + dC_lumen = np.zeros((Segs, numGas)) + dC_shell = dC_lumen.copy() sumCp_init = P/(R*T) - sumCp_fin = np.zeros(Segs) + # sumCp_fin = np.zeros(Segs) - C = self._ins_QC[0,:-1]/cmps.chem_MW*cmps.i_mass # conc. in mol/m^3 as defined by Ian - Cs = C[self.idx] #self.idx ensures its only for gases + # C = self._ins_QC[0,:-1]/cmps.chem_MW*cmps.i_mass # conc. in mol/m^3 as defined by Ian + # Cs = C[self.idx] #self.idx ensures its only for gases - _dstate = self._dstate - _update_dstate = self._update_dstate + dC = self._dstate + _update_dstate = self._update_dstate + def dy_dt(t, QC_ins, QC, dQC_ins): # QC is exactly 'the state' as we define in _init_ - C = QC + # C = QC + Q = QC_ins[0,-1] + C_in = QC_ins[0, idx] * mass2mol # mol/m^3 + u = Q/cross_section_A + + # Calculate the dimensionless numbers + # Reynolds + Re = u*D/nu + # Sherwood + Sh = 1.615*(Re*Sc*D/L)**(1/3) + + # Calculate Mass Transfer Coefficients + KLiq = Sh*Diff/D + KTot = 1/(1/KLiq + 1/(KMem*H)) + KTot[h2o_j] = KMem[h2o_j] + + QC = QC.reshape((Segs, numGas*2)) + C_lumen = QC[:,:numGas] + C_shell = QC[:,numGas:] + #!!! alternatively - # For the first segment: - for j in range(0, numGas): - #if GasVec[j].Name == 'H2O': - if j == self.h2o_j: - dC[j+numGas] = (KTot[j]/(D/4))*(PVapH2O- (C[j+numGas]/sumCp_init)*P)*vFrac - dC[j] = 0 - else: - dC[j] = (u/dx)*(Cs[j] - C[j]) - (KTot[j]/(D/4))*(C[j] - (C[j+numGas]/sumCp_init)*P/H[j]) - dC[j+numGas] = (KTot[j]/(D/4))*(C[j]-(C[j+numGas]/sumCp_init)*P/H[j])*vFrac + # # For the first segment: + # for j in range(0, numGas): + # #if GasVec[j].Name == 'H2O': + # if j == h2o_j: + # dC[j+numGas] = (KTot[j]/(D/4))*(PVapH2O- (C[j+numGas]/sumCp_init)*P)*vFrac + # dC[j] = 0 + # else: + # # dC[j] = (u/dx)*(Cs[j] - C[j]) - (KTot[j]/(D/4))*(C[j] - (C[j+numGas]/sumCp_init)*P/H[j]) + # #!!! It seems like Cs should be the dissolved gas concentration in influent + # dC[j] = (u/dx)*(C_in[j] - C[j]) - (KTot[j]/(D/4))*(C[j] - (C[j+numGas]/sumCp_init)*P/H[j]) + # dC[j+numGas] = (KTot[j]/(D/4))*(C[j]-(C[j+numGas]/sumCp_init)*P/H[j])*vFrac - # Calculate the total gas concentration in the shell after the change - sumCp_fin[0] += C[j+numGas] + dC[j+numGas] + # # Calculate the total gas concentration in the shell after the change + # sumCp_fin[0] += C[j+numGas] + dC[j+numGas] + + transmembrane = (KTot/(D/4))*(C_lumen - C_shell/sumCp_init*P/H) + dC_lumen[0] = (u/dx)*(C_in - C_lumen[0]) - transmembrane[0] + dC_lumen[1:] = (u/dx)*(C_lumen[:-1] - C_lumen[1:]) - transmembrane[1:] + dC_lumen[:,h2o_j] = 0 + dC_shell[:] = transmembrane*vFrac + dC_shell[:,h2o_j] = (KTot[h2o_j]/(D/4))*(PVapH2O - (C_shell[:,h2o_j]/sumCp_init)*P)*vFrac + + # sumCp_fin = np.sum(C_shell+dC_shell, axis=1) + - for i in range(1,Segs): - # For the remaining segments: - # Calculate the rate of change of the shell and lumen for all remaining segments. - for j in range(0, numGas): + # for i in range(1,Segs): + # # For the remaining segments: + # # Calculate the rate of change of the shell and lumen for all remaining segments. + # for j in range(0, numGas): - # Lumen - dC[numVec*(i)+j] = (u/dx)*(C[numVec*(i-1)+j] - C[numVec*(i)+j]) - (KTot[j]/(D/4))*(C[numVec*(i)+j] - (C[numVec*(i)+j+numGas]/sumCp_init)*(P/H[j])) + # # Lumen + # dC[numVec*(i)+j] = (u/dx)*(C[numVec*(i-1)+j] - C[numVec*(i)+j]) - (KTot[j]/(D/4))*(C[numVec*(i)+j] - (C[numVec*(i)+j+numGas]/sumCp_init)*(P/H[j])) - # Shell - dC[numVec*(i)+j+numGas] = (KTot[j]/(D/4))*(C[numVec*(i)+j] - (C[numVec*(i)+j+numGas]/sumCp_init)*(P/H[j]))*vFrac + # # Shell + # dC[numVec*(i)+j+numGas] = (KTot[j]/(D/4))*(C[numVec*(i)+j] - (C[numVec*(i)+j+numGas]/sumCp_init)*(P/H[j]))*vFrac - # If the gas is H2O, then it follows a different formula: - #if GasVec[j].Name == 'H2O': - if j == self.h2o_j: - dC[numVec*i+j+numGas] = (KTot[j]/(D/4))*(PVapH2O-(C[numVec*i+j+numGas]/sumCp_init)*P)*vFrac - dC[numVec*i+j] = 0 + # # If the gas is H2O, then it follows a different formula: + # #if GasVec[j].Name == 'H2O': + # if j == self.h2o_j: + # dC[numVec*i+j+numGas] = (KTot[j]/(D/4))*(PVapH2O-(C[numVec*i+j+numGas]/sumCp_init)*P)*vFrac + # dC[numVec*i+j] = 0 - # Calculate the total gas concentration in the shell after the change - sumCp_fin[i] += C[numVec*(i)+j+numGas] + dC[numVec*(i)+j+numGas] + # # Calculate the total gas concentration in the shell after the change + # sumCp_fin[i] += C[numVec*(i)+j+numGas] + dC[numVec*(i)+j+numGas] # Re-scale the shell concentration so that vacuum pressure stays constant during the entire process. Given the change of concentration that we have calculated above for the shell, we can re-scale the shell concentrations with the sumCp at each segment. # This FOR LOOP maintains consistent pressure in the shell - for i in range(0, Segs): - for j in range(0, numGas): - # Calculate the new concentration of gases in the shell - newCp = (C[numVec*(i)+j+numGas] + dC[numVec*(i)+j+numGas]) + # for i in range(0, Segs): + # for j in range(0, numGas): + # # Calculate the new concentration of gases in the shell + # newCp = (C[numVec*(i)+j+numGas] + dC[numVec*(i)+j+numGas]) - # Re-scale the concentration to vacuum pressure - newCp = (newCp/sumCp_fin[i])*P/(R*T) + # # Re-scale the concentration to vacuum pressure + # newCp = (newCp/sumCp_fin[i])*P/(R*T) - # Calculate the actual difference of concentration that the function will output - dC[numVec*(i)+j+numGas] = newCp- C[numVec*(i)+j+numGas] - # Return the difference in concentration + # # Calculate the actual difference of concentration that the function will output + # dC[numVec*(i)+j+numGas] = newCp- C[numVec*(i)+j+numGas] + # # Return the difference in concentration - #return dC + # #return dC + + new_C_shell = C_shell+dC_shell + sumCp_fin = np.sum(new_C_shell, axis=1) + dC_shell[:] = np.diag(sumCp_init/sumCp_fin) @ new_C_shell - C_shell + dC[:] = np.hstack((dC_lumen, dC_shell)).flatten() _update_dstate() self._ODE = dy_dt \ No newline at end of file From 4e7e39e96a97fde175d165d4201a84b11093635e Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Tue, 2 May 2023 16:16:38 -0500 Subject: [PATCH 107/424] fix order of gas properties --- qsdsan/sanunits/_membrane_gas_extraction.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/qsdsan/sanunits/_membrane_gas_extraction.py b/qsdsan/sanunits/_membrane_gas_extraction.py index 334443d7..73cdd4ca 100644 --- a/qsdsan/sanunits/_membrane_gas_extraction.py +++ b/qsdsan/sanunits/_membrane_gas_extraction.py @@ -130,9 +130,9 @@ def __init__(self, ID='GEM', ins=None, outs=(), thermo=None, isdynamic=True, dct_gas_wc = WilkeChang or self._WilkeChang self.set_WilkeChang(**dct_gas_wc) - inf, = self.ins - cmps = inf.components - self.indexer = cmps.index + cmps = self.thermo.chemicals + # self.indexer = cmps.index + self.indexer = GasID.index # self.idx ensures that the indexing in further code is only for gases # and not all components in the influent self.idx = cmps.indices(self.GasID) @@ -264,9 +264,6 @@ def set_HenrySlope(self, **kwargs): self.set_prop('_hs', **kwargs) def set_prop(self, attr_name, **kwargs): - inf, = self.ins - cmps = inf.components - self.indexer = cmps.index idxr = self.indexer try: attr = getattr(self, attr_name) except: attr = self.__dict__[attr_name] = np.zeros(len(self.chemicals)) From 3561f85ce2e4ea2b422562518a879374031009ac Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Tue, 2 May 2023 16:20:29 -0500 Subject: [PATCH 108/424] minor bug fix --- qsdsan/sanunits/_membrane_gas_extraction.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qsdsan/sanunits/_membrane_gas_extraction.py b/qsdsan/sanunits/_membrane_gas_extraction.py index 73cdd4ca..ebeab850 100644 --- a/qsdsan/sanunits/_membrane_gas_extraction.py +++ b/qsdsan/sanunits/_membrane_gas_extraction.py @@ -121,6 +121,7 @@ def __init__(self, ID='GEM', ins=None, outs=(), thermo=None, isdynamic=True, self.segs = segs # Number of segments #self.Volume = VolBatchTank # Volume of the bioreactor (Don't think this is needed) + self.indexer = GasID.index dct_gas_perm = GasPerm or self._GasPerm self.set_GasPerm(**dct_gas_perm) dct_gas_hpf = HenryPreFac or self._HenryPreFac @@ -132,7 +133,6 @@ def __init__(self, ID='GEM', ins=None, outs=(), thermo=None, isdynamic=True, cmps = self.thermo.chemicals # self.indexer = cmps.index - self.indexer = GasID.index # self.idx ensures that the indexing in further code is only for gases # and not all components in the influent self.idx = cmps.indices(self.GasID) From 561960c75cbcd7b1e887f130ef043059cb1093c1 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Tue, 2 May 2023 16:31:06 -0500 Subject: [PATCH 109/424] fix time unit --- qsdsan/sanunits/_membrane_gas_extraction.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qsdsan/sanunits/_membrane_gas_extraction.py b/qsdsan/sanunits/_membrane_gas_extraction.py index ebeab850..f28aec98 100644 --- a/qsdsan/sanunits/_membrane_gas_extraction.py +++ b/qsdsan/sanunits/_membrane_gas_extraction.py @@ -476,7 +476,7 @@ def _compile_ODE(self): def dy_dt(t, QC_ins, QC, dQC_ins): # QC is exactly 'the state' as we define in _init_ # C = QC - Q = QC_ins[0,-1] + Q = QC_ins[0,-1]/24/3600 C_in = QC_ins[0, idx] * mass2mol # mol/m^3 u = Q/cross_section_A @@ -558,9 +558,9 @@ def dy_dt(t, QC_ins, QC, dQC_ins): # #return dC - new_C_shell = C_shell+dC_shell + new_C_shell = C_shell + dC_shell sumCp_fin = np.sum(new_C_shell, axis=1) dC_shell[:] = np.diag(sumCp_init/sumCp_fin) @ new_C_shell - C_shell dC[:] = np.hstack((dC_lumen, dC_shell)).flatten() _update_dstate() - self._ODE = dy_dt \ No newline at end of file + self._ODE = dy_dt \ No newline at end of file From 689d3b65f18eb0ea47d6b3777184b3f2710e763d Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Wed, 3 May 2023 09:48:10 -0500 Subject: [PATCH 110/424] Modification in petersen matrix ADM1_P_extension Added X_MeOH and X_MeP to petersen matrix of modified ADM1. These components are not participating in any ADM1 process, and would be mapped directly from ASM2d to ADM1, and vice versa. --- .../data/process_data/_adm1_p_extension.tsv | 43 +++++++++---------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/qsdsan/data/process_data/_adm1_p_extension.tsv b/qsdsan/data/process_data/_adm1_p_extension.tsv index 401ce9ca..f704e080 100644 --- a/qsdsan/data/process_data/_adm1_p_extension.tsv +++ b/qsdsan/data/process_data/_adm1_p_extension.tsv @@ -1,22 +1,21 @@ - S_su S_aa S_fa S_va S_bu S_pro S_ac S_h2 S_ch4 S_IC S_IN S_IP S_I X_ch X_pr X_li X_su X_aa X_fa X_c4 X_pro X_ac X_h2 X_I X_PHA X_PP X_PAO S_K S_Mg -hydrolysis_carbs 1 ? ? ? -1 -hydrolysis_proteins 1 ? ? ? -1 -hydrolysis_lipids 1-f_fa_li f_fa_li ? ? ? -1 -uptake_sugars -1 (1-Y_su)*f_bu_su (1-Y_su)*f_pro_su (1-Y_su)*f_ac_su (1-Y_su)*f_h2_su ? ? ? Y_su -uptake_amino_acids -1 (1-Y_aa)*f_va_aa (1-Y_aa)*f_bu_aa (1-Y_aa)*f_pro_aa (1-Y_aa)*f_ac_aa (1-Y_aa)*f_h2_aa ? ? ? Y_aa -uptake_LCFA -1 (1-Y_fa)*f_ac_fa (1-Y_fa)*f_h2_fa ? ? ? Y_fa -uptake_valerate -1 (1-Y_c4)*f_pro_va (1-Y_c4)*f_ac_va (1-Y_c4)*f_h2_va ? ? ? Y_c4 -uptake_butyrate -1 (1-Y_c4)*f_ac_bu (1-Y_c4)*f_h2_bu ? ? ? Y_c4 -uptake_propionate -1 (1-Y_pro)*f_ac_pro (1-Y_pro)*f_h2_pro ? ? ? Y_pro -uptake_acetate -1 1-Y_ac ? ? ? Y_ac -uptake_h2 -1 1-Y_h2 ? ? ? Y_h2 -decay_Xsu ? ? ? f_sI_xb f_ch_xb f_pr_xb f_li_xb -1 f_xI_xb -decay_Xaa ? ? ? f_sI_xb f_ch_xb f_pr_xb f_li_xb -1 f_xI_xb -decay_Xfa ? ? ? f_sI_xb f_ch_xb f_pr_xb f_li_xb -1 f_xI_xb -decay_Xc4 ? ? ? f_sI_xb f_ch_xb f_pr_xb f_li_xb -1 f_xI_xb -decay_Xpro ? ? ? f_sI_xb f_ch_xb f_pr_xb f_li_xb -1 f_xI_xb -decay_Xac ? ? ? f_sI_xb f_ch_xb f_pr_xb f_li_xb -1 f_xI_xb -decay_Xh2 ? ? ? f_sI_xb f_ch_xb f_pr_xb f_li_xb -1 f_xI_xb -lysis_XPAO f_sI_xb f_ch_xb f_pr_xb f_li_xb f_xI_xb -1 -lysis_XPHA f_va_PHA f_bu_PHA f_pro_PHA f_ac_PHA -1 - + S_su S_aa S_fa S_va S_bu S_pro S_ac S_h2 S_ch4 S_IC S_IN S_IP S_I X_ch X_pr X_li X_su X_aa X_fa X_c4 X_pro X_ac X_h2 X_I X_PHA X_PP X_PAO S_K S_Mg X_MeOH X_MeP +hydrolysis_carbs 1 ? ? ? -1 +hydrolysis_proteins 1 ? ? ? -1 +hydrolysis_lipids 1-f_fa_li f_fa_li ? ? ? -1 +uptake_sugars -1 (1-Y_su)*f_bu_su (1-Y_su)*f_pro_su (1-Y_su)*f_ac_su (1-Y_su)*f_h2_su ? ? ? Y_su +uptake_amino_acids -1 (1-Y_aa)*f_va_aa (1-Y_aa)*f_bu_aa (1-Y_aa)*f_pro_aa (1-Y_aa)*f_ac_aa (1-Y_aa)*f_h2_aa ? ? ? Y_aa +uptake_LCFA -1 (1-Y_fa)*f_ac_fa (1-Y_fa)*f_h2_fa ? ? ? Y_fa +uptake_valerate -1 (1-Y_c4)*f_pro_va (1-Y_c4)*f_ac_va (1-Y_c4)*f_h2_va ? ? ? Y_c4 +uptake_butyrate -1 (1-Y_c4)*f_ac_bu (1-Y_c4)*f_h2_bu ? ? ? Y_c4 +uptake_propionate -1 (1-Y_pro)*f_ac_pro (1-Y_pro)*f_h2_pro ? ? ? Y_pro +uptake_acetate -1 1-Y_ac ? ? ? Y_ac +uptake_h2 -1 1-Y_h2 ? ? ? Y_h2 +decay_Xsu ? ? ? f_sI_xb f_ch_xb f_pr_xb f_li_xb -1 f_xI_xb +decay_Xaa ? ? ? f_sI_xb f_ch_xb f_pr_xb f_li_xb -1 f_xI_xb +decay_Xfa ? ? ? f_sI_xb f_ch_xb f_pr_xb f_li_xb -1 f_xI_xb +decay_Xc4 ? ? ? f_sI_xb f_ch_xb f_pr_xb f_li_xb -1 f_xI_xb +decay_Xpro ? ? ? f_sI_xb f_ch_xb f_pr_xb f_li_xb -1 f_xI_xb +decay_Xac ? ? ? f_sI_xb f_ch_xb f_pr_xb f_li_xb -1 f_xI_xb +decay_Xh2 ? ? ? f_sI_xb f_ch_xb f_pr_xb f_li_xb -1 f_xI_xb +lysis_XPAO f_sI_xb f_ch_xb f_pr_xb f_li_xb f_xI_xb -1 +lysis_XPHA f_va_PHA f_bu_PHA f_pro_PHA f_ac_PHA -1 From 91e61c7a9560d94a0d88fb31f31a6db9d51f25f4 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Wed, 3 May 2023 13:32:02 -0500 Subject: [PATCH 111/424] Added X_MeOH and X_MeP to ADM1 Changed the indices accordingly. X_MeOH, X_MeP would have index of 29 and 30 in the state array. So any index after 28 in the code has to be modified. Which I (hopefully) have done in this step. --- qsdsan/processes/_adm1_p_extension.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/qsdsan/processes/_adm1_p_extension.py b/qsdsan/processes/_adm1_p_extension.py index 59b25b4e..7d752f1b 100644 --- a/qsdsan/processes/_adm1_p_extension.py +++ b/qsdsan/processes/_adm1_p_extension.py @@ -173,6 +173,12 @@ def create_adm1_p_extension_cmps(set_thermo=True): particle_size='Soluble', degradability='Undegradable', organic=False) + + # X_MeOH and X_MeP are added at a later iteration of p_extension ADM1. + # They do not participate in any ADM1 process, and would be directly + # mapped to ASM2d components at the ASM-ADM-ASM interface. + X_MeOH = cmps_all.X_FeOH.copy('X_MeOH') + X_MeP = cmps_all.X_FePO4.copy('X_MeP') for bio in (X_su, X_aa, X_fa, X_c4, X_pro, X_ac, X_h2): # bio.formula = 'C5H7O2N' @@ -186,7 +192,7 @@ def create_adm1_p_extension_cmps(set_thermo=True): cmps_adm1_p_extension = Components([S_su, S_aa, S_fa, S_va, S_bu, S_pro, S_ac, S_h2, S_ch4, S_IC, S_IN, S_IP, S_I, X_ch, X_pr, X_li, X_su, X_aa, X_fa, X_c4, X_pro, X_ac, X_h2, X_I, - X_PHA, X_PP, X_PAO, S_K, S_Mg, + X_PHA, X_PP, X_PAO, S_K, S_Mg, X_MeOH, X_MeP, S_cat, S_an, cmps_all.H2O]) cmps_adm1_p_extension.default_compile() if set_thermo: settings.set_thermo(cmps_adm1_p_extension) @@ -279,7 +285,7 @@ def rhos_adm1_p_extension(state_arr, params): # state_arr_cmps stated just for readability of code # state_arr_cmps = [S_su, S_aa, S_fa, S_va, S_bu, S_pro, S_ac, S_h2, S_ch4, S_IC, S_IN, # S_IP, S_I, X_ch, X_pr, X_li, X_su, X_aa, X_fa, X_c4, X_pro, X_ac, - # X_h2, X_I, X_PHA, X_PP, X_PAO, S_K, S_Mg] + # X_h2, X_I, X_PHA, X_PP, X_PAO, S_K, S_Mg, X_MeOH, X_MeP] # Cs_ids = cmps.indices(['X_ch', 'X_pr', 'X_li', 'X_su', 'X_aa', # 'X_fa', 'X_c4', 'X_c4', 'X_pro', 'X_ac', 'X_h2', @@ -302,14 +308,15 @@ def rhos_adm1_p_extension(state_arr, params): # S_va, S_bu, S_h2, S_ch4, S_IC, S_IN = state_arr[[3,4,7,8,9,10]] S_va, S_bu, S_h2, S_IN, S_IP = state_arr[[3,4,7,10,11]] unit_conversion = mass2mol_conversion(cmps) - cmps_in_M = state_arr[:32] * unit_conversion + # Should I let X_MeOH, and X_MeP unit convert? (ask Joy) + cmps_in_M = state_arr[:34] * unit_conversion # weak acids (ADM1) = [S_ca, S_an, S_IN, S_IC, S_ac, S_pro, S_bu, S_va] # weak acids (modified_ADM1) = [S_ca, S_K, S_Mg, S_an, S_IN, S_IP, S_IC, S_ac, S_pro, S_bu, S_va] - weak_acids = cmps_in_M[[29, 27, 28, 30, 10, 11, 9, 6, 5, 4, 3]] + weak_acids = cmps_in_M[[31, 27, 28, 32, 10, 11, 9, 6, 5, 4, 3]] T_op = state_arr[-1] biogas_S = state_arr[7:10].copy() - biogas_p = R * T_op * state_arr[32:35] + biogas_p = R * T_op * state_arr[34:37] Kas = Kab * T_correction_factor(T_base, T_op, Ka_dH) KH = KHb * T_correction_factor(T_base, T_op, KH_dH) / unit_conversion[7:10] From 018c191696b1a2730cf2ccc232b04156dd276e62 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Thu, 4 May 2023 11:38:44 -0500 Subject: [PATCH 112/424] Directly mapping few phosphorus components: ASM2d-ADM1 X_PHA, X_PP, X_PAO would be directly mapped as they are common set across ASM2d and the modified ADM1. Therefore removing X_PHA disintegration from Step 1, and X_PAO from subsequent steps. --- qsdsan/sanunits/_junction_copy.py | 107 ++++++++++++++++-------------- 1 file changed, 58 insertions(+), 49 deletions(-) diff --git a/qsdsan/sanunits/_junction_copy.py b/qsdsan/sanunits/_junction_copy.py index 8a8c01b8..5f6945c6 100644 --- a/qsdsan/sanunits/_junction_copy.py +++ b/qsdsan/sanunits/_junction_copy.py @@ -7,6 +7,8 @@ Joy Zhang Yalin Li + + Saumitra Rai This module is under the University of Illinois/NCSA Open Source License. Please refer to https://github.com/QSD-Group/QSDsan/blob/main/LICENSE.txt @@ -699,9 +701,8 @@ def _compile_reactions(self): X_PAO_i_N = cmps_asm.X_PAO.i_N S_F_i_N = cmps_asm.S_F.i_N X_S_i_N = cmps_asm.X_S.i_N - asm_X_I_i_N = cmps_asm.X_I.i_N - X_P_i_N = cmps_asm.X_P.i_N + #X_P_i_N = cmps_asm.X_P.i_N (Doesn't exist in ASM2d) if cmps_asm.X_S.i_N > 0: warn(f'X_S in ASM has positive nitrogen content: {cmps_asm.X_S.i_N} gN/gCOD. ' 'These nitrogen will be ignored by the interface model ' @@ -743,15 +744,13 @@ def asm2adm(asm_vals): NO3_coddm = -S_NO3*S_NO3_i_COD # cod_spl = S_S + X_S + X_BH + X_BA - # Replacing S_S with S_F + S_A + X_PHA (since all of them are measured as COD and are degradable) - # added X_PAO to X_H and X_AUT + # Replacing S_S with S_F + S_A (IWA ASM textbook) - cod_spl = (S_A + S_F) + X_S + X_PHA + (X_H + X_AUT + X_PAO) + cod_spl = (S_A + S_F) + X_S + (X_H + X_AUT) - #bioN = X_BH*X_BH_i_N + X_BA*X_BA_i_N - #Added X_PAO along with X_H and X_AUT + # bioN = X_BH*X_BH_i_N + X_BA*X_BA_i_N - bioN = X_H*X_H_i_N + X_AUT*X_AUT_i_N + X_PAO*X_PAO_i_N + bioN = X_H*X_H_i_N + X_AUT*X_AUT_i_N # To be used in Step 2 S_ND_asm1 = S_F*S_F_i_N #S_ND (in asm1) equals the N content in S_F (Joy) @@ -760,12 +759,11 @@ def asm2adm(asm_vals): if cod_spl <= O2_coddm: S_O2 = O2_coddm - cod_spl - S_F = S_A = X_S = X_PHA = X_H = X_AUT = X_PAO = 0 + S_F = S_A = X_S = X_H = X_AUT = 0 elif cod_spl <= O2_coddm + NO3_coddm: S_O2 = 0 S_NO3 = -(O2_coddm + NO3_coddm - cod_spl)/S_NO3_i_COD - # X_PHA should come after X_S, since X_S >> X_PHA (Joy) - S_A = S_F = X_S = X_PHA = X_H = X_AUT = X_PAO = 0 + S_A = S_F = X_S = X_H = X_AUT = 0 else: S_A -= O2_coddm + NO3_coddm if S_A < 0: @@ -775,23 +773,18 @@ def asm2adm(asm_vals): X_S += S_F S_F = 0 if X_S < 0: - X_PHA += X_S + X_H += X_S X_S = 0 - if X_PHA < 0: - X_H += X_PHA - X_PHA = 0 - if X_H < 0: - X_AUT += X_H - X_H = 0 - if X_AUT < 0: - X_PAO += X_AUT - X_AUT = 0 + if X_H < 0: + X_AUT += X_H + X_H = 0 S_O2 = S_NO3 = 0 # Step 2: convert any readily biodegradable # COD and TKN into amino acids and sugars - S_S_asm1 = S_F + S_A # S_S (in asm1) equals to the sum of S_F and S_A (pg. 82 IWA ASM models handbook) + # S_S (in asm1) equals to the sum of S_F and S_A (pg. 82 IWA ASM models handbook) + S_S_asm1 = S_F + S_A req_scod = S_ND_asm1 / S_aa_i_N @@ -820,68 +813,84 @@ def asm2adm(asm_vals): # Step 4: convert active biomass into protein, lipids, # carbohydrates and potentially particulate TKN - available_bioN = bioN - (X_H + X_AUT + X_PAO) * (1-frac_deg) * adm_X_I_i_N + available_bioN = bioN - (X_H + X_AUT) * (1-frac_deg) * adm_X_I_i_N if available_bioN < 0: raise RuntimeError('Not enough N in X_BA and X_BH to fully convert ' 'the non-biodegradable portion into X_I in ADM1.') - req_bioN = (X_H + X_AUT + X_PAO) * frac_deg * X_pr_i_N + req_bioN = (X_H + X_AUT) * frac_deg * X_pr_i_N if available_bioN + X_ND_asm1 >= req_bioN: - X_pr += (X_H + X_AUT + X_PAO) * frac_deg + X_pr += (X_H + X_AUT) * frac_deg X_ND_asm1 += available_bioN - req_bioN else: bio2pr = (available_bioN + X_ND_asm1)/X_pr_i_N X_pr += bio2pr - bio_to_split = (X_H + X_AUT + X_PAO) * frac_deg - bio2pr + bio_to_split = (X_H + X_AUT) * frac_deg - bio2pr bio_split_to_li = bio_to_split * self.bio_to_li X_li += bio_split_to_li X_ch += (bio_to_split - bio_split_to_li) X_ND_asm1 = 0 # Step 5: map particulate inerts - xi_nsp = X_P_i_N * X_P + asm_X_I_i_N * X_I - xi_ndm = (X_P+X_I) * adm_X_I_i_N - if xi_nsp + X_ND >= xi_ndm: - deficit = xi_ndm - xi_nsp - X_I += X_P + (X_BH+X_BA) * (1-frac_deg) - X_ND -= deficit - elif isclose(xi_nsp+X_ND, xi_ndm, rel_tol=rtol, abs_tol=atol): - X_I += X_P + (X_BH+X_BA) * (1-frac_deg) - X_ND = 0 + # xi_nsp = X_P_i_N * X_P + asm_X_I_i_N * X_I + # Think about leftover N + xi_nsp_asm2d = X_I * asm_X_I_i_N + xi_ndm = X_I * adm_X_I_i_N + + if xi_nsp_asm2d + X_ND_asm1 >= xi_ndm: + deficit = xi_ndm - xi_nsp_asm2d + # X_I += X_P + (X_H+X_AUT) * (1-frac_deg) + X_I += (X_H+X_AUT) * (1-frac_deg) + X_ND_asm1 -= deficit + elif isclose(xi_nsp_asm2d+X_ND_asm1, xi_ndm, rel_tol=rtol, abs_tol=atol): + # X_I += X_P + (X_H+X_AUT) * (1-frac_deg) + X_I += (X_H+X_AUT) * (1-frac_deg) + X_ND_asm1 = 0 else: raise RuntimeError('Not enough N in X_I, X_P, X_ND to fully ' 'convert X_I and X_P into X_I in ADM1.') + # S_I_i_N is for ADM1 req_sn = S_I * S_I_i_N - if req_sn <= S_ND: - S_ND -= req_sn - elif req_sn <= S_ND + X_ND: - X_ND -= (req_sn - S_ND) - S_ND = 0 - elif req_sn <= S_ND + X_ND + S_NH: - S_NH -= (req_sn - S_ND - X_ND) - S_ND = X_ND = 0 + if req_sn <= S_ND_asm1: + S_ND_asm1 -= req_sn + elif req_sn <= S_ND_asm1 + X_ND_asm1: + X_ND_asm1 -= (req_sn - S_ND_asm1) + S_ND_asm1 = 0 + elif req_sn <= S_ND_asm1 + X_ND_asm1 + S_NH4: + S_NH4 -= (req_sn - S_ND_asm1 - X_ND_asm1) + S_ND_asm1 = X_ND_asm1 = 0 else: warn('Additional soluble inert COD is mapped to S_su.') - SI_cod = (S_ND + X_ND + S_NH)/S_I_i_N + SI_cod = (S_ND_asm1 + X_ND_asm1 + S_NH4)/S_I_i_N S_su += S_I - SI_cod S_I = SI_cod - S_ND = X_ND = S_NH = 0 + S_ND_asm1 = X_ND_asm1 = S_NH4 = 0 # Step 6: map any remaining TKN - S_IN = S_ND + X_ND + S_NH + S_IN = S_ND_asm1 + X_ND_asm1 + S_NH4 # Step 8: check COD and TKN balance # has TKN: S_aa, S_IN, S_I, X_pr, X_I S_IC = S_cat = S_an = 0 + # adm_vals = np.array([ + # S_su, S_aa, + # 0, 0, 0, 0, 0, # S_fa, S_va, S_bu, S_pro, S_ac, + # 0, 0, # S_h2, S_ch4, + # S_IC, S_IN, S_I, + # 0, # X_c, + # X_ch, X_pr, X_li, + # 0, 0, 0, 0, 0, 0, 0, # X_su, X_aa, X_fa, X_c4, X_pro, X_ac, X_h2, + # X_I, S_cat, S_an, H2O]) + + adm_vals = np.array([ S_su, S_aa, 0, 0, 0, 0, 0, # S_fa, S_va, S_bu, S_pro, S_ac, 0, 0, # S_h2, S_ch4, - S_IC, S_IN, S_I, - 0, # X_c, + S_IC, S_IN, S_IP, S_I, X_ch, X_pr, X_li, 0, 0, 0, 0, 0, 0, 0, # X_su, X_aa, X_fa, X_c4, X_pro, X_ac, X_h2, - X_I, S_cat, S_an, H2O]) + X_I, X_PHA, X_PP, X_PAO, S_K, S_Mg, S_cat, S_an, H2O]) adm_vals = f_corr(asm_vals, adm_vals) From 3f82f14a6b98c830e37e725b1552db25844d89bc Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Thu, 4 May 2023 11:44:38 -0500 Subject: [PATCH 113/424] Mapping common P state variables: ASM2d-ADM1 X_PHA, X_PP, X_PAO would be directly mapped as they are common set across ASM2d and the modified ADM1. --- qsdsan/sanunits/_junction_copy.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/qsdsan/sanunits/_junction_copy.py b/qsdsan/sanunits/_junction_copy.py index 5f6945c6..94cacb4f 100644 --- a/qsdsan/sanunits/_junction_copy.py +++ b/qsdsan/sanunits/_junction_copy.py @@ -872,6 +872,13 @@ def asm2adm(asm_vals): # Step 8: check COD and TKN balance # has TKN: S_aa, S_IN, S_I, X_pr, X_I S_IC = S_cat = S_an = 0 + + # Step 9: Mapping common state variables directly + # X_PAO (ADM1) = X_PAO (ASM2d) + # X_PP (ADM1) = X_PP (ASM2d) + # X_PHA (ADM1) = X_PHA (ASM2d) + S_IP = S_PO4 + # adm_vals = np.array([ # S_su, S_aa, # 0, 0, 0, 0, 0, # S_fa, S_va, S_bu, S_pro, S_ac, @@ -882,7 +889,6 @@ def asm2adm(asm_vals): # 0, 0, 0, 0, 0, 0, 0, # X_su, X_aa, X_fa, X_c4, X_pro, X_ac, X_h2, # X_I, S_cat, S_an, H2O]) - adm_vals = np.array([ S_su, S_aa, 0, 0, 0, 0, 0, # S_fa, S_va, S_bu, S_pro, S_ac, From b8839fac071f0ef9aee5e46e0d200513876dc483 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Thu, 4 May 2023 12:03:43 -0500 Subject: [PATCH 114/424] Mapping MeOH, MeP, K, and Mg in ASM2d-ADM1 X_MeOH and X_MeP are mapped directly from ASM2d to ADM1 S_K, S_Mg = 0 in the ASM2d-ADM1 interface (even though ASM2d cmps have non-zero i_K, and i_Mg values), since we are not aiming for K, Mg balance --- qsdsan/sanunits/_junction_copy.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/qsdsan/sanunits/_junction_copy.py b/qsdsan/sanunits/_junction_copy.py index 94cacb4f..cbe2d0d0 100644 --- a/qsdsan/sanunits/_junction_copy.py +++ b/qsdsan/sanunits/_junction_copy.py @@ -874,6 +874,8 @@ def asm2adm(asm_vals): S_IC = S_cat = S_an = 0 # Step 9: Mapping common state variables directly + # The next three commented lines are executed when outputting + # array of ADM1 components # X_PAO (ADM1) = X_PAO (ASM2d) # X_PP (ADM1) = X_PP (ASM2d) # X_PHA (ADM1) = X_PHA (ASM2d) @@ -896,7 +898,10 @@ def asm2adm(asm_vals): S_IC, S_IN, S_IP, S_I, X_ch, X_pr, X_li, 0, 0, 0, 0, 0, 0, 0, # X_su, X_aa, X_fa, X_c4, X_pro, X_ac, X_h2, - X_I, X_PHA, X_PP, X_PAO, S_K, S_Mg, S_cat, S_an, H2O]) + X_I, X_PHA, X_PP, X_PAO, + 0, 0, # S_K, S_Mg, + X_MeOH, X_MeP, + S_cat, S_an, H2O]) adm_vals = f_corr(asm_vals, adm_vals) From ee7c6bbfca6c9201959ed31ca4da0fdecca8bf7b Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Thu, 4 May 2023 14:33:55 -0500 Subject: [PATCH 115/424] Commented step 2 and 3 of ASM2d-ADM1 Commented Step 2 and Step 3 of the ASM2d-ADM1 interface for better understanding --- qsdsan/sanunits/_junction_copy.py | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/qsdsan/sanunits/_junction_copy.py b/qsdsan/sanunits/_junction_copy.py index cbe2d0d0..1d7adb9e 100644 --- a/qsdsan/sanunits/_junction_copy.py +++ b/qsdsan/sanunits/_junction_copy.py @@ -786,29 +786,53 @@ def asm2adm(asm_vals): # S_S (in asm1) equals to the sum of S_F and S_A (pg. 82 IWA ASM models handbook) S_S_asm1 = S_F + S_A + # First we calculate the amount of amino acid required in ADM1 + # if all available soluble organic N can be mapped to amino acid req_scod = S_ND_asm1 / S_aa_i_N - if S_S_asm1 < req_scod: + # if available S_S is not enough to fulfill that amino acid requirement + if S_S_asm1 < req_scod: + # then all available S_S is mapped to amino acids S_aa = S_S_asm1 + # and no S_S would be available for conversion to sugars S_su = 0 + # This needs to be followed by a corresponding loss in soluble organic N S_ND_asm1 -= S_aa * S_aa_i_N + # if available S_S is more than enough to fulfill that amino acid requirement else: + # All soluble organic N will be mapped to amino acid S_aa = req_scod + # The line above implies that a certain portion of S_S would also be consumed to form amino acid + # The S_S which is left would form sugar + # In simpler terms; S_S = S_S - S_aa; S_su = S_S S_su = S_S_asm1 - S_aa + # All soluble organic N would thus be consumed in amino acid formation S_ND_asm1 = 0 # Step 3: convert slowly biodegradable COD and TKN # into proteins, lipids, and carbohydrates + # First we calculate the amount of protein required in ADM1 + # if all available particulate organic N can be mapped to amino acid req_xcod = X_ND_asm1 / X_pr_i_N + + # if available X_S is not enough to fulfill that protein requirement if X_S < req_xcod: + # then all available X_S is mapped to amino acids X_pr = X_S + # and no X_S would be available for conversion to lipid or carbohydrates X_li = X_ch = 0 + # This needs to be followed by a corresponding loss in particulate organic N X_ND_asm1 -= X_pr * X_pr_i_N + # if available X_S is more than enough to fulfill that protein requirement else: + # All particulate organic N will be mapped to amino acid X_pr = req_xcod + # The line above implies that a certain portion of X_S would also be consumed to form protein + # The X_S which is left would form lipid and carbohydrates in a percentage define by the user X_li = self.xs_to_li * (X_S - X_pr) X_ch = (X_S - X_pr) - X_li + # All particulate organic N would thus be consumed in amino acid formation X_ND_asm1 = 0 # Step 4: convert active biomass into protein, lipids, @@ -899,7 +923,7 @@ def asm2adm(asm_vals): X_ch, X_pr, X_li, 0, 0, 0, 0, 0, 0, 0, # X_su, X_aa, X_fa, X_c4, X_pro, X_ac, X_h2, X_I, X_PHA, X_PP, X_PAO, - 0, 0, # S_K, S_Mg, + 0, 0, # S_K, S_Mg, (since we are not aiming for K and Mg balance, confirm with Joy) X_MeOH, X_MeP, S_cat, S_an, H2O]) From 8cb6d495278d3f4ddb22e6210201de3db782c53a Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Thu, 4 May 2023 17:31:30 -0500 Subject: [PATCH 116/424] Commented step 4 of ASM2d-ADM1 Commented Step 4 of the ASM2d-ADM1 interface for better readability. --- qsdsan/sanunits/_junction_copy.py | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/qsdsan/sanunits/_junction_copy.py b/qsdsan/sanunits/_junction_copy.py index 1d7adb9e..f9e88b50 100644 --- a/qsdsan/sanunits/_junction_copy.py +++ b/qsdsan/sanunits/_junction_copy.py @@ -837,30 +837,49 @@ def asm2adm(asm_vals): # Step 4: convert active biomass into protein, lipids, # carbohydrates and potentially particulate TKN + + # First the amount of biomass N available for protein, lipid etc is determined + # For this calculation, from total biomass N available the amount + # of particulate inert expected in ADM1 is subtracted available_bioN = bioN - (X_H + X_AUT) * (1-frac_deg) * adm_X_I_i_N if available_bioN < 0: - raise RuntimeError('Not enough N in X_BA and X_BH to fully convert ' + raise RuntimeError('Not enough N in X_H and X_AUT to fully convert ' 'the non-biodegradable portion into X_I in ADM1.') + # Then the amount of biomass N required for biomass conversion to protein is determined req_bioN = (X_H + X_AUT) * frac_deg * X_pr_i_N + # if available biomass N and particulate organic nitrogen is greater than required biomass N for conversion to protein if available_bioN + X_ND_asm1 >= req_bioN: + # then all biodegradable biomass N (corrsponding to protein demand) is converted to protein X_pr += (X_H + X_AUT) * frac_deg + # the remaining biomass N is transfered as organic N X_ND_asm1 += available_bioN - req_bioN + # if available biomass N and organic nitrogen is less than required biomass N for conversion to protein else: + # all available N and particulate organic N is converted to protein bio2pr = (available_bioN + X_ND_asm1)/X_pr_i_N X_pr += bio2pr + # Biodegradable biomass available after conversion to protein is calculated bio_to_split = (X_H + X_AUT) * frac_deg - bio2pr + # Part of the remaining biomass is mapped to lipid based on user defined value bio_split_to_li = bio_to_split * self.bio_to_li X_li += bio_split_to_li + # The other portion of the remanining biomass is mapped to carbohydrates X_ch += (bio_to_split - bio_split_to_li) + # Since all organic N has been mapped to protein, none is left X_ND_asm1 = 0 # Step 5: map particulate inerts # xi_nsp = X_P_i_N * X_P + asm_X_I_i_N * X_I - # Think about leftover N + # Think about leftover N + # First determine the amount of particulate inert N available from ASM2d xi_nsp_asm2d = X_I * asm_X_I_i_N + # Then determine the amount of particulate inert N that could be produced + # in ADM1 given the ASM1 X_I xi_ndm = X_I * adm_X_I_i_N + # if particulate inert N available in ASM1 is greater than ADM1 demand if xi_nsp_asm2d + X_ND_asm1 >= xi_ndm: + # deficit would be a -ive value deficit = xi_ndm - xi_nsp_asm2d # X_I += X_P + (X_H+X_AUT) * (1-frac_deg) X_I += (X_H+X_AUT) * (1-frac_deg) @@ -870,8 +889,8 @@ def asm2adm(asm_vals): X_I += (X_H+X_AUT) * (1-frac_deg) X_ND_asm1 = 0 else: - raise RuntimeError('Not enough N in X_I, X_P, X_ND to fully ' - 'convert X_I and X_P into X_I in ADM1.') + raise RuntimeError('Not enough N in X_I, X_ND_asm1 to fully ' + 'convert X_I in ASM2d into X_I in ADM1.') # S_I_i_N is for ADM1 req_sn = S_I * S_I_i_N @@ -903,7 +922,7 @@ def asm2adm(asm_vals): # X_PAO (ADM1) = X_PAO (ASM2d) # X_PP (ADM1) = X_PP (ASM2d) # X_PHA (ADM1) = X_PHA (ASM2d) - S_IP = S_PO4 + S_IP = S_PO4 # correct, as they are both measured as P # adm_vals = np.array([ # S_su, S_aa, @@ -923,7 +942,7 @@ def asm2adm(asm_vals): X_ch, X_pr, X_li, 0, 0, 0, 0, 0, 0, 0, # X_su, X_aa, X_fa, X_c4, X_pro, X_ac, X_h2, X_I, X_PHA, X_PP, X_PAO, - 0, 0, # S_K, S_Mg, (since we are not aiming for K and Mg balance, confirm with Joy) + 0, 0, # S_K, S_Mg, X_MeOH, X_MeP, S_cat, S_an, H2O]) From 80a747c94ca8f885b01af1ed2a4ce6f0e37decd4 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Mon, 8 May 2023 12:31:05 -0500 Subject: [PATCH 117/424] Minor changes (ASM2d-ADM1) Minor changes --- qsdsan/sanunits/_junction_copy.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/qsdsan/sanunits/_junction_copy.py b/qsdsan/sanunits/_junction_copy.py index f9e88b50..dbdaa994 100644 --- a/qsdsan/sanunits/_junction_copy.py +++ b/qsdsan/sanunits/_junction_copy.py @@ -891,6 +891,7 @@ def asm2adm(asm_vals): else: raise RuntimeError('Not enough N in X_I, X_ND_asm1 to fully ' 'convert X_I in ASM2d into X_I in ADM1.') + # S_I_i_N is for ADM1 req_sn = S_I * S_I_i_N @@ -922,17 +923,13 @@ def asm2adm(asm_vals): # X_PAO (ADM1) = X_PAO (ASM2d) # X_PP (ADM1) = X_PP (ASM2d) # X_PHA (ADM1) = X_PHA (ASM2d) + # X_MeOH (ADM1) = X_MeOH (ASM2d) + # X_MeP (ADM1) = X_MeP (ASM2d) S_IP = S_PO4 # correct, as they are both measured as P - # adm_vals = np.array([ - # S_su, S_aa, - # 0, 0, 0, 0, 0, # S_fa, S_va, S_bu, S_pro, S_ac, - # 0, 0, # S_h2, S_ch4, - # S_IC, S_IN, S_I, - # 0, # X_c, - # X_ch, X_pr, X_li, - # 0, 0, 0, 0, 0, 0, 0, # X_su, X_aa, X_fa, X_c4, X_pro, X_ac, X_h2, - # X_I, S_cat, S_an, H2O]) + # When mapping components directly in Step 9 ensure the values of + # cmps.i_N, cmps.i_P, and cmps.i_COD are same in both ASM2d and ADM1 + # ^ Need to code this (05/08) adm_vals = np.array([ S_su, S_aa, From 47944b18082ca138c79653257777e997e167be6a Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Wed, 10 May 2023 16:47:30 -0500 Subject: [PATCH 118/424] Incorporating P balance in ASM2d-ADM1 interface Initial steps towards P balance --- qsdsan/sanunits/_junction_copy.py | 142 ++++++++++++++++++++++++++++-- 1 file changed, 134 insertions(+), 8 deletions(-) diff --git a/qsdsan/sanunits/_junction_copy.py b/qsdsan/sanunits/_junction_copy.py index dbdaa994..d5386f90 100644 --- a/qsdsan/sanunits/_junction_copy.py +++ b/qsdsan/sanunits/_junction_copy.py @@ -695,28 +695,48 @@ def _compile_reactions(self): atol = self.atol cmps_asm = ins.components + + # For COD balance S_NO3_i_COD = cmps_asm.S_NO3.i_COD + + # For N balance X_H_i_N = cmps_asm.X_H.i_N X_AUT_i_N = cmps_asm.X_AUT.i_N - X_PAO_i_N = cmps_asm.X_PAO.i_N S_F_i_N = cmps_asm.S_F.i_N X_S_i_N = cmps_asm.X_S.i_N asm_X_I_i_N = cmps_asm.X_I.i_N - #X_P_i_N = cmps_asm.X_P.i_N (Doesn't exist in ASM2d) + + # For P balance + X_H_i_P = cmps_asm.X_H.i_P + X_AUT_i_P = cmps_asm.X_AUT.i_P + S_F_i_P = cmps_asm.S_F.i_P + X_S_i_P = cmps_asm.X_S.i_P + asm_X_I_i_P = cmps_asm.X_I.i_P + if cmps_asm.X_S.i_N > 0: warn(f'X_S in ASM has positive nitrogen content: {cmps_asm.X_S.i_N} gN/gCOD. ' 'These nitrogen will be ignored by the interface model ' 'and could lead to imbalance of TKN after conversion.') - if cmps_asm.S_S.i_N > 0: - warn(f'S_S in ASM has positive nitrogen content: {cmps_asm.S_S.i_N} gN/gCOD. ' + if cmps_asm.S_A.i_N > 0: + warn(f'S_A in ASM has positive nitrogen content: {cmps_asm.S_S.i_N} gN/gCOD. ' 'These nitrogen will be ignored by the interface model ' 'and could lead to imbalance of TKN after conversion.') + # We do not need to check if S_F.i_N != 0 since we take care of it using S_ND_asm1 cmps_adm = outs.components + + # For nitrogen balance S_aa_i_N = cmps_adm.S_aa.i_N X_pr_i_N = cmps_adm.X_pr.i_N S_I_i_N = cmps_adm.S_I.i_N adm_X_I_i_N = cmps_adm.X_I.i_N + + # For phosphorous balance + X_pr_i_P = cmps_adm.X_pr.i_P + adm_S_I_i_P = cmps_adm.S_I.i_P + adm_X_I_i_P = cmps_adm.X_I.i_P + + adm_ions_idx = cmps_adm.indices(['S_IN', 'S_IC', 'S_cat', 'S_an']) frac_deg = self.frac_deg @@ -751,11 +771,16 @@ def asm2adm(asm_vals): # bioN = X_BH*X_BH_i_N + X_BA*X_BA_i_N bioN = X_H*X_H_i_N + X_AUT*X_AUT_i_N + bioP = X_H*X_H_i_P + X_AUT*X_AUT_i_P # To be used in Step 2 S_ND_asm1 = S_F*S_F_i_N #S_ND (in asm1) equals the N content in S_F (Joy) # To be used in Step 3 X_ND_asm1 = X_S*X_S_i_N #X_ND (in asm1) equals the N content in X_S (Joy) + # To be used in Step 5 (a) + X_S_P = X_S*X_S_i_P + # To be used in Step 5 (b) + S_F_P = S_F*S_F_i_P if cod_spl <= O2_coddm: S_O2 = O2_coddm - cod_spl @@ -838,9 +863,11 @@ def asm2adm(asm_vals): # Step 4: convert active biomass into protein, lipids, # carbohydrates and potentially particulate TKN + # -------------------------------For N balance------------------------- + # First the amount of biomass N available for protein, lipid etc is determined # For this calculation, from total biomass N available the amount - # of particulate inert expected in ADM1 is subtracted + # of particulate inert N expected in ADM1 is subtracted available_bioN = bioN - (X_H + X_AUT) * (1-frac_deg) * adm_X_I_i_N if available_bioN < 0: raise RuntimeError('Not enough N in X_H and X_AUT to fully convert ' @@ -867,8 +894,52 @@ def asm2adm(asm_vals): X_ch += (bio_to_split - bio_split_to_li) # Since all organic N has been mapped to protein, none is left X_ND_asm1 = 0 + + # ------------------------------For P balance-------------------------- + + # First the amount of biomass P available for protein, lipid etc is determined + # For this calculation, from total biomass P available the amount + # of particulate inert P expected in ADM1 is subtracted + available_bioP = bioP - (X_H + X_AUT) * (1-frac_deg) * adm_X_I_i_P + if available_bioP < 0: + raise RuntimeError('Not enough P in X_H and X_AUT to fully convert ' + 'the non-biodegradable portion into X_I in ADM1.') + # Then the amount of biomass P required for biomass conversion to protein is determined + req_bioP = (X_H + X_AUT) * frac_deg * X_pr_i_P + # if available biomass P and particulate organic P is greater than required biomass P for conversion to protein + if available_bioP + X_S_P >= req_bioP: + # then all biodegradable biomass N (corrsponding to protein demand) is converted to protein + + # THIS STEP SHOULD NOT BE EXECUTED + X_pr += (X_H + X_AUT) * frac_deg + + + # the remaining biomass N is transfered as organic N + X_S_P += available_bioP - req_bioP + # if available biomass N and organic nitrogen is less than required biomass N for conversion to protein + else: + # all available N and particulate organic N is converted to protein + + # Because of different bio2pr value for P rest becomes different + bio2pr = (available_bioP + X_S_P)/X_pr_i_P + X_pr += bio2pr + + # THIS STEP IS COMMON + # Biodegradable biomass available after conversion to protein is calculated + bio_to_split = (X_H + X_AUT) * frac_deg - bio2pr + # Part of the remaining biomass is mapped to lipid based on user defined value + bio_split_to_li = bio_to_split * self.bio_to_li + X_li += bio_split_to_li + # The other portion of the remanining biomass is mapped to carbohydrates + X_ch += (bio_to_split - bio_split_to_li) + + # Since all organic N has been mapped to protein, none is left + X_S_P = 0 # Step 5: map particulate inerts + + # -------------------For N and COD balance----------------------------- + # xi_nsp = X_P_i_N * X_P + asm_X_I_i_N * X_I # Think about leftover N # First determine the amount of particulate inert N available from ASM2d @@ -882,17 +953,51 @@ def asm2adm(asm_vals): # deficit would be a -ive value deficit = xi_ndm - xi_nsp_asm2d # X_I += X_P + (X_H+X_AUT) * (1-frac_deg) + # The next line is for COD balance X_I += (X_H+X_AUT) * (1-frac_deg) + # The next line is for N balance X_ND_asm1 -= deficit elif isclose(xi_nsp_asm2d+X_ND_asm1, xi_ndm, rel_tol=rtol, abs_tol=atol): # X_I += X_P + (X_H+X_AUT) * (1-frac_deg) + # The next line is for COD balance X_I += (X_H+X_AUT) * (1-frac_deg) + # The next line is for N balance X_ND_asm1 = 0 else: raise RuntimeError('Not enough N in X_I, X_ND_asm1 to fully ' 'convert X_I in ASM2d into X_I in ADM1.') - + # --------------- For P balance --------------------------------------- + + # Look at the next 20 lines of code, it does not disturb the COD or N balance at all + # The code first determines the P content available/demanded in ASM2d/ADM1 respectively + # For ASM2d that P content is from X_I and X_S, and for ADM1 it'll go to X_I + # If P is not balanced, it's saved in the variable X_S_P + + # First determine the amount of particulate inert P available from ASM2d + xi_psp_asm2d = X_I * asm_X_I_i_P + # Then determine the amount of particulate inert N that could be produced + # in ADM1 given the ASM1 X_I + xi_pdm = X_I * adm_X_I_i_P + + # if particulate inert P available in ASM1 is greater than ADM1 demand + if xi_psp_asm2d + X_S_P >= xi_pdm: + # deficit would be a -ive value + deficit = xi_pdm - xi_psp_asm2d + # Don't need to repeat the next step since already been done + # X_I += (X_H+X_AUT) * (1-frac_deg) + X_S_P -= deficit + elif isclose(xi_psp_asm2d+X_S_P, xi_pdm, rel_tol=rtol, abs_tol=atol): + # X_I += X_P + (X_H+X_AUT) * (1-frac_deg) + # Don't need to repeat the next step since already been done + # X_I += (X_H+X_AUT) * (1-frac_deg) + X_S_P = 0 + else: + raise RuntimeError('Not enough P in X_I, X_S to fully ' + 'convert X_I in ASM2d into X_I in ADM1.') + + # -------------------For N and COD balance----------------------------- + # S_I_i_N is for ADM1 req_sn = S_I * S_I_i_N if req_sn <= S_ND_asm1: @@ -910,8 +1015,29 @@ def asm2adm(asm_vals): S_I = SI_cod S_ND_asm1 = X_ND_asm1 = S_NH4 = 0 - # Step 6: map any remaining TKN - S_IN = S_ND_asm1 + X_ND_asm1 + S_NH4 + + # --------------- For P and COD balance --------------------------------------- + req_sp = S_I * adm_S_I_i_P + + if req_sp <= S_F_P: + S_F_P -= req_sp + elif req_sp <= S_F_P + X_S_P: + X_S_P -= (req_sp - S_F_P) + S_F_P = 0 + elif req_sp <= S_F_P + X_S_P + S_PO4: + S_PO4 -= (req_sp - S_F_P - X_S_P) + S_F_P = X_S_P = 0 + else: + warn('Additional soluble inert COD is mapped to S_su.') + SI_cod = (S_F_P + X_S_P + S_PO4)/adm_S_I_i_P + S_su += S_I - SI_cod + S_I = SI_cod + S_F_P = X_S_P = S_PO4 = 0 + + # Step 6(a): map any remaining TKN + S_IN = S_ND_asm1 + X_ND_asm1 + S_NH4 + # Step 6(b): map any remaining phosphorous + S_IP = S_F_P + X_S_P + S_PO4 # Step 8: check COD and TKN balance # has TKN: S_aa, S_IN, S_I, X_pr, X_I From e4fa2a8486a1aebe00d6ea6516f4fb3e44159dfa Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Fri, 12 May 2023 16:55:00 -0500 Subject: [PATCH 119/424] Update _sludge_treatment.py --- qsdsan/sanunits/_sludge_treatment.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py index 8a623bbe..083492fb 100644 --- a/qsdsan/sanunits/_sludge_treatment.py +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -628,15 +628,17 @@ def _run(self): if fuel.phase != 'g': raise ValueError(f'The phase of fuel is expected to be gas not {fuel.phase}') - inf = (sludge.mass + air.mass + fuel.mass) + inf = np.asarray(sludge.mass + air.mass + fuel.mass) idx_n2 = cmps.index(nitrogen_ID) idx_h2o = cmps.index(water_ID) n2 = inf[idx_n2] h2o = inf[idx_h2o] + mass_ash = np.sum(inf*cmps.i_mass*(1-cmps.f_Vmass_Totmass)) \ - - h2o*cmps.H2O.i_mass*(1-cmps.H2O.f_Vmass_Totmass) - n2*cmps.N2.i_mass*(1-cmps.N2.f_Vmass_Totmass) + - h2o*cmps.H2O.i_mass*(1-cmps.H2O.f_Vmass_Totmass) \ + - n2*cmps.N2.i_mass*(1-cmps.N2.f_Vmass_Totmass) # Conservation of mass mass_flue_gas = np.sum(inf*cmps.i_mass) - mass_ash From bccda485cdaa285473774625176f0bb19019022e Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Fri, 12 May 2023 17:05:25 -0500 Subject: [PATCH 120/424] alternative fix --- qsdsan/sanunits/_sludge_treatment.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py index 083492fb..7f7acc90 100644 --- a/qsdsan/sanunits/_sludge_treatment.py +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -628,15 +628,14 @@ def _run(self): if fuel.phase != 'g': raise ValueError(f'The phase of fuel is expected to be gas not {fuel.phase}') - inf = np.asarray(sludge.mass + air.mass + fuel.mass) + inf = sludge.mass + air.mass + fuel.mass idx_n2 = cmps.index(nitrogen_ID) idx_h2o = cmps.index(water_ID) n2 = inf[idx_n2] h2o = inf[idx_h2o] - - mass_ash = np.sum(inf*cmps.i_mass*(1-cmps.f_Vmass_Totmass)) \ + mass_ash = sum(inf*cmps.i_mass*(1-cmps.f_Vmass_Totmass)) \ - h2o*cmps.H2O.i_mass*(1-cmps.H2O.f_Vmass_Totmass) \ - n2*cmps.N2.i_mass*(1-cmps.N2.f_Vmass_Totmass) From af6020da0a7bc9f174ec621a5811d076b8b0c733 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Mon, 15 May 2023 13:28:27 -0500 Subject: [PATCH 121/424] Step 4: Incorporation of N/P balance in biomass conversion The step is responsible for converting degradable fraction of biomass to carbohydrate, lipids, and protein. An attempt has been made to conserve both N and P. --- qsdsan/sanunits/_junction_copy.py | 111 +++++++++++++++++++----------- 1 file changed, 70 insertions(+), 41 deletions(-) diff --git a/qsdsan/sanunits/_junction_copy.py b/qsdsan/sanunits/_junction_copy.py index d5386f90..411d0fbd 100644 --- a/qsdsan/sanunits/_junction_copy.py +++ b/qsdsan/sanunits/_junction_copy.py @@ -863,25 +863,38 @@ def asm2adm(asm_vals): # Step 4: convert active biomass into protein, lipids, # carbohydrates and potentially particulate TKN - # -------------------------------For N balance------------------------- - - # First the amount of biomass N available for protein, lipid etc is determined + # First the amount of biomass N/P available for protein, lipid etc is determined # For this calculation, from total biomass N available the amount - # of particulate inert N expected in ADM1 is subtracted + # of particulate inert N/P expected in ADM1 is subtracted + available_bioN = bioN - (X_H + X_AUT) * (1-frac_deg) * adm_X_I_i_N if available_bioN < 0: raise RuntimeError('Not enough N in X_H and X_AUT to fully convert ' 'the non-biodegradable portion into X_I in ADM1.') - # Then the amount of biomass N required for biomass conversion to protein is determined + + available_bioP = bioP - (X_H + X_AUT) * (1-frac_deg) * adm_X_I_i_P + if available_bioP < 0: + raise RuntimeError('Not enough P in X_H and X_AUT to fully convert ' + 'the non-biodegradable portion into X_I in ADM1.') + + # Then the amount of biomass N/P required for biomass conversion to protein is determined req_bioN = (X_H + X_AUT) * frac_deg * X_pr_i_N - # if available biomass N and particulate organic nitrogen is greater than required biomass N for conversion to protein - if available_bioN + X_ND_asm1 >= req_bioN: - # then all biodegradable biomass N (corrsponding to protein demand) is converted to protein + req_bioP = (X_H + X_AUT) * frac_deg * X_pr_i_P + + # Case I: if both available biomass N/P and particulate organic N/P is greater than + # required biomass N/P for conversion to protein + if available_bioN + X_ND_asm1 >= req_bioN and available_bioP + X_S_P >= req_bioP: + # then all biodegradable biomass N/P (corrsponding to protein demand) is converted to protein X_pr += (X_H + X_AUT) * frac_deg - # the remaining biomass N is transfered as organic N - X_ND_asm1 += available_bioN - req_bioN - # if available biomass N and organic nitrogen is less than required biomass N for conversion to protein - else: + # the remaining biomass N/P is transfered as organic N/P + X_ND_asm1 += available_bioN - req_bioN + X_S_P += available_bioP - req_bioP + + # Case II: if available biomass N and particulate organic N is less than + # required biomass N for conversion to protein, but available biomass P and + # particulate organic P is greater than required biomass P for conversion to protein + elif available_bioN + X_ND_asm1 < req_bioN and available_bioP + X_S_P >= req_bioP: + # all available N and particulate organic N is converted to protein bio2pr = (available_bioN + X_ND_asm1)/X_pr_i_N X_pr += bio2pr @@ -895,36 +908,17 @@ def asm2adm(asm_vals): # Since all organic N has been mapped to protein, none is left X_ND_asm1 = 0 - # ------------------------------For P balance-------------------------- - - # First the amount of biomass P available for protein, lipid etc is determined - # For this calculation, from total biomass P available the amount - # of particulate inert P expected in ADM1 is subtracted - available_bioP = bioP - (X_H + X_AUT) * (1-frac_deg) * adm_X_I_i_P - if available_bioP < 0: - raise RuntimeError('Not enough P in X_H and X_AUT to fully convert ' - 'the non-biodegradable portion into X_I in ADM1.') - # Then the amount of biomass P required for biomass conversion to protein is determined - req_bioP = (X_H + X_AUT) * frac_deg * X_pr_i_P - # if available biomass P and particulate organic P is greater than required biomass P for conversion to protein - if available_bioP + X_S_P >= req_bioP: - # then all biodegradable biomass N (corrsponding to protein demand) is converted to protein - - # THIS STEP SHOULD NOT BE EXECUTED - X_pr += (X_H + X_AUT) * frac_deg - - - # the remaining biomass N is transfered as organic N - X_S_P += available_bioP - req_bioP - # if available biomass N and organic nitrogen is less than required biomass N for conversion to protein - else: - # all available N and particulate organic N is converted to protein + # the remaining biomass P is transfered as organic P + X_S_P += available_bioP - (X_pr*X_pr_i_P) + + # Case III: if available biomass P and particulate organic P is less than + # required biomass P for conversion to protein, but available biomass N and + # particulate organic N is greater than required biomass N for conversion to protein + elif available_bioN + X_ND_asm1 >= req_bioN and available_bioP + X_S_P < req_bioP: - # Because of different bio2pr value for P rest becomes different + # all available P and particulate organic P is converted to protein bio2pr = (available_bioP + X_S_P)/X_pr_i_P X_pr += bio2pr - - # THIS STEP IS COMMON # Biodegradable biomass available after conversion to protein is calculated bio_to_split = (X_H + X_AUT) * frac_deg - bio2pr # Part of the remaining biomass is mapped to lipid based on user defined value @@ -932,9 +926,44 @@ def asm2adm(asm_vals): X_li += bio_split_to_li # The other portion of the remanining biomass is mapped to carbohydrates X_ch += (bio_to_split - bio_split_to_li) - - # Since all organic N has been mapped to protein, none is left + # Since all organic P has been mapped to protein, none is left X_S_P = 0 + + # the remaining biomass N is transfered as organic N + X_ND_asm1 += available_bioN - (X_pr*X_pr_i_P) + + # Case IV: if both available biomass N/P and particulate organic N/P is less than + # required biomass N/P for conversion to protein + elif available_bioN + X_ND_asm1 < req_bioN and available_bioP + X_S_P < req_bioP: + if X_pr_i_N > X_pr_i_P: + bio2pr = (available_bioN + X_ND_asm1)/X_pr_i_N + X_pr += bio2pr + # Biodegradable biomass available after conversion to protein is calculated + bio_to_split = (X_H + X_AUT) * frac_deg - bio2pr + # Part of the remaining biomass is mapped to lipid based on user defined value + bio_split_to_li = bio_to_split * self.bio_to_li + X_li += bio_split_to_li + # The other portion of the remanining biomass is mapped to carbohydrates + X_ch += (bio_to_split - bio_split_to_li) + # Since all organic N has been mapped to protein, none is left + X_ND_asm1 = 0 + # the remaining biomass P is transfered as organic P + X_S_P += available_bioP - (X_pr*X_pr_i_P) + else: + bio2pr = (available_bioP + X_ND_asm1)/X_pr_i_P + X_pr += bio2pr + # Biodegradable biomass available after conversion to protein is calculated + bio_to_split = (X_H + X_AUT) * frac_deg - bio2pr + # Part of the remaining biomass is mapped to lipid based on user defined value + bio_split_to_li = bio_to_split * self.bio_to_li + X_li += bio_split_to_li + # The other portion of the remanining biomass is mapped to carbohydrates + X_ch += (bio_to_split - bio_split_to_li) + # Since all organic P has been mapped to protein, none is left + X_S_P = 0 + # the remaining biomass N is transfered as organic N + X_ND_asm1 += available_bioN - (X_pr*X_pr_i_N) + # Step 5: map particulate inerts From bfdc27bfc6d7dad44a3f810d0ad822178bcd1c3d Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Mon, 15 May 2023 13:52:42 -0500 Subject: [PATCH 122/424] Step 5(a): P balance ASM2d-ADM1 Incorporation of N/P balance in mapping of particulate inerts --- qsdsan/sanunits/_junction_copy.py | 76 ++++++++++++++----------------- 1 file changed, 34 insertions(+), 42 deletions(-) diff --git a/qsdsan/sanunits/_junction_copy.py b/qsdsan/sanunits/_junction_copy.py index 411d0fbd..6c26b660 100644 --- a/qsdsan/sanunits/_junction_copy.py +++ b/qsdsan/sanunits/_junction_copy.py @@ -967,63 +967,55 @@ def asm2adm(asm_vals): # Step 5: map particulate inerts - # -------------------For N and COD balance----------------------------- - - # xi_nsp = X_P_i_N * X_P + asm_X_I_i_N * X_I - # Think about leftover N - # First determine the amount of particulate inert N available from ASM2d + # 5 (a) + # First determine the amount of particulate inert N/P available from ASM2d xi_nsp_asm2d = X_I * asm_X_I_i_N - # Then determine the amount of particulate inert N that could be produced + xi_psp_asm2d = X_I * asm_X_I_i_P + + # Then determine the amount of particulate inert N/P that could be produced # in ADM1 given the ASM1 X_I xi_ndm = X_I * adm_X_I_i_N - + xi_pdm = X_I * adm_X_I_i_P + # if particulate inert N available in ASM1 is greater than ADM1 demand if xi_nsp_asm2d + X_ND_asm1 >= xi_ndm: # deficit would be a -ive value deficit = xi_ndm - xi_nsp_asm2d - # X_I += X_P + (X_H+X_AUT) * (1-frac_deg) - # The next line is for COD balance + # COD balance X_I += (X_H+X_AUT) * (1-frac_deg) - # The next line is for N balance + # N balance X_ND_asm1 -= deficit + # P balance + if xi_psp_asm2d + X_S_P >= xi_pdm: + # deficit would be a -ive value + deficit = xi_pdm - xi_psp_asm2d + X_S_P -= deficit + elif isclose(xi_psp_asm2d+X_S_P, xi_pdm, rel_tol=rtol, abs_tol=atol): + X_S_P = 0 + else: + raise RuntimeError('Not enough P in X_I, X_S to fully ' + 'convert X_I in ASM2d into X_I in ADM1.') elif isclose(xi_nsp_asm2d+X_ND_asm1, xi_ndm, rel_tol=rtol, abs_tol=atol): - # X_I += X_P + (X_H+X_AUT) * (1-frac_deg) - # The next line is for COD balance + # COD balance X_I += (X_H+X_AUT) * (1-frac_deg) - # The next line is for N balance + # N balance X_ND_asm1 = 0 + # P balance + if xi_psp_asm2d + X_S_P >= xi_pdm: + # deficit would be a -ive value + deficit = xi_pdm - xi_psp_asm2d + X_S_P -= deficit + elif isclose(xi_psp_asm2d+X_S_P, xi_pdm, rel_tol=rtol, abs_tol=atol): + X_S_P = 0 + else: + raise RuntimeError('Not enough P in X_I, X_S to fully ' + 'convert X_I in ASM2d into X_I in ADM1.') else: - raise RuntimeError('Not enough N in X_I, X_ND_asm1 to fully ' + # Since the N balance cannot hold, the P balance is not futher checked + raise RuntimeError('Not enough N in X_I, X_ND_asm1, X_S_P to fully ' 'convert X_I in ASM2d into X_I in ADM1.') - # --------------- For P balance --------------------------------------- - - # Look at the next 20 lines of code, it does not disturb the COD or N balance at all - # The code first determines the P content available/demanded in ASM2d/ADM1 respectively - # For ASM2d that P content is from X_I and X_S, and for ADM1 it'll go to X_I - # If P is not balanced, it's saved in the variable X_S_P - - # First determine the amount of particulate inert P available from ASM2d - xi_psp_asm2d = X_I * asm_X_I_i_P - # Then determine the amount of particulate inert N that could be produced - # in ADM1 given the ASM1 X_I - xi_pdm = X_I * adm_X_I_i_P - - # if particulate inert P available in ASM1 is greater than ADM1 demand - if xi_psp_asm2d + X_S_P >= xi_pdm: - # deficit would be a -ive value - deficit = xi_pdm - xi_psp_asm2d - # Don't need to repeat the next step since already been done - # X_I += (X_H+X_AUT) * (1-frac_deg) - X_S_P -= deficit - elif isclose(xi_psp_asm2d+X_S_P, xi_pdm, rel_tol=rtol, abs_tol=atol): - # X_I += X_P + (X_H+X_AUT) * (1-frac_deg) - # Don't need to repeat the next step since already been done - # X_I += (X_H+X_AUT) * (1-frac_deg) - X_S_P = 0 - else: - raise RuntimeError('Not enough P in X_I, X_S to fully ' - 'convert X_I in ASM2d into X_I in ADM1.') + # -------------------For N and COD balance----------------------------- From a15c8ad60aa978174f00477f8398c4052477cadf Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Mon, 15 May 2023 15:47:39 -0500 Subject: [PATCH 123/424] Step 5 (b): P balance ASM2D-ADM1 Incorporation of N/P balance in mapping of particulate and soluble inerts --- qsdsan/sanunits/_junction_copy.py | 120 ++++++++++++++++++++++-------- 1 file changed, 90 insertions(+), 30 deletions(-) diff --git a/qsdsan/sanunits/_junction_copy.py b/qsdsan/sanunits/_junction_copy.py index 6c26b660..fa0e9608 100644 --- a/qsdsan/sanunits/_junction_copy.py +++ b/qsdsan/sanunits/_junction_copy.py @@ -930,7 +930,7 @@ def asm2adm(asm_vals): X_S_P = 0 # the remaining biomass N is transfered as organic N - X_ND_asm1 += available_bioN - (X_pr*X_pr_i_P) + X_ND_asm1 += available_bioN - (X_pr*X_pr_i_N) # Case IV: if both available biomass N/P and particulate organic N/P is less than # required biomass N/P for conversion to protein @@ -979,7 +979,6 @@ def asm2adm(asm_vals): # if particulate inert N available in ASM1 is greater than ADM1 demand if xi_nsp_asm2d + X_ND_asm1 >= xi_ndm: - # deficit would be a -ive value deficit = xi_ndm - xi_nsp_asm2d # COD balance X_I += (X_H+X_AUT) * (1-frac_deg) @@ -987,7 +986,6 @@ def asm2adm(asm_vals): X_ND_asm1 -= deficit # P balance if xi_psp_asm2d + X_S_P >= xi_pdm: - # deficit would be a -ive value deficit = xi_pdm - xi_psp_asm2d X_S_P -= deficit elif isclose(xi_psp_asm2d+X_S_P, xi_pdm, rel_tol=rtol, abs_tol=atol): @@ -1002,7 +1000,6 @@ def asm2adm(asm_vals): X_ND_asm1 = 0 # P balance if xi_psp_asm2d + X_S_P >= xi_pdm: - # deficit would be a -ive value deficit = xi_pdm - xi_psp_asm2d X_S_P -= deficit elif isclose(xi_psp_asm2d+X_S_P, xi_pdm, rel_tol=rtol, abs_tol=atol): @@ -1015,56 +1012,119 @@ def asm2adm(asm_vals): raise RuntimeError('Not enough N in X_I, X_ND_asm1, X_S_P to fully ' 'convert X_I in ASM2d into X_I in ADM1.') - - - # -------------------For N and COD balance----------------------------- + # 5(b) - # S_I_i_N is for ADM1 + # Then determine the amount of soluble inert N/P that could be produced + # in ADM1 given the ASM1 X_I + # S_I_i_N is for ADM1 req_sn = S_I * S_I_i_N + req_sp = S_I * adm_S_I_i_P + + # N balance if req_sn <= S_ND_asm1: S_ND_asm1 -= req_sn + # P balance + if req_sp <= S_F_P: + S_F_P -= req_sp + elif req_sp <= S_F_P + X_S_P: + X_S_P -= (req_sp - S_F_P) + S_F_P = 0 + elif req_sp <= S_F_P + X_S_P + S_PO4: + S_PO4 -= (req_sp - S_F_P - X_S_P) + S_F_P = X_S_P = 0 + else: + warn('Additional soluble inert COD is mapped to S_su.') + # Can these be executed in this case? I think so + SI_cod = (S_F_P + X_S_P + S_PO4)/adm_S_I_i_P + S_su += S_I - SI_cod + S_I = SI_cod + S_F_P = X_S_P = S_PO4 = 0 + # N balance elif req_sn <= S_ND_asm1 + X_ND_asm1: X_ND_asm1 -= (req_sn - S_ND_asm1) S_ND_asm1 = 0 + # P balance + if req_sp <= S_F_P: + S_F_P -= req_sp + elif req_sp <= S_F_P + X_S_P: + X_S_P -= (req_sp - S_F_P) + S_F_P = 0 + elif req_sp <= S_F_P + X_S_P + S_PO4: + S_PO4 -= (req_sp - S_F_P - X_S_P) + S_F_P = X_S_P = 0 + else: + warn('Additional soluble inert COD is mapped to S_su.') + # Can these be executed in this case? I think so + SI_cod = (S_F_P + X_S_P + S_PO4)/adm_S_I_i_P + S_su += S_I - SI_cod + S_I = SI_cod + S_F_P = X_S_P = S_PO4 = 0 + # N balance elif req_sn <= S_ND_asm1 + X_ND_asm1 + S_NH4: S_NH4 -= (req_sn - S_ND_asm1 - X_ND_asm1) S_ND_asm1 = X_ND_asm1 = 0 - else: + # P balance + if req_sp <= S_F_P: + S_F_P -= req_sp + elif req_sp <= S_F_P + X_S_P: + X_S_P -= (req_sp - S_F_P) + S_F_P = 0 + elif req_sp <= S_F_P + X_S_P + S_PO4: + S_PO4 -= (req_sp - S_F_P - X_S_P) + S_F_P = X_S_P = 0 + else: + warn('Additional soluble inert COD is mapped to S_su.') + # Can these be executed in this case? I think so + SI_cod = (S_F_P + X_S_P + S_PO4)/adm_S_I_i_P + S_su += S_I - SI_cod + S_I = SI_cod + S_F_P = X_S_P = S_PO4 = 0 + elif req_sp <= S_F_P or req_sp <= S_F_P + X_S_P or req_sp <= S_F_P + X_S_P + S_PO4: warn('Additional soluble inert COD is mapped to S_su.') SI_cod = (S_ND_asm1 + X_ND_asm1 + S_NH4)/S_I_i_N S_su += S_I - SI_cod S_I = SI_cod S_ND_asm1 = X_ND_asm1 = S_NH4 = 0 - - - # --------------- For P and COD balance --------------------------------------- - req_sp = S_I * adm_S_I_i_P - - if req_sp <= S_F_P: - S_F_P -= req_sp - elif req_sp <= S_F_P + X_S_P: - X_S_P -= (req_sp - S_F_P) - S_F_P = 0 - elif req_sp <= S_F_P + X_S_P + S_PO4: - S_PO4 -= (req_sp - S_F_P - X_S_P) - S_F_P = X_S_P = 0 + req_sp = S_I * adm_S_I_i_P + if req_sp <= S_F_P: + S_F_P -= req_sp + elif req_sp <= S_F_P + X_S_P: + X_S_P -= (req_sp - S_F_P) + S_F_P = 0 + elif req_sp <= S_F_P + X_S_P + S_PO4: + S_PO4 -= (req_sp - S_F_P - X_S_P) + S_F_P = X_S_P = 0 else: - warn('Additional soluble inert COD is mapped to S_su.') - SI_cod = (S_F_P + X_S_P + S_PO4)/adm_S_I_i_P - S_su += S_I - SI_cod - S_I = SI_cod - S_F_P = X_S_P = S_PO4 = 0 + if S_I_i_N > adm_S_I_i_P: + warn('Additional soluble inert COD is mapped to S_su.') + SI_cod = (S_ND_asm1 + X_ND_asm1 + S_NH4)/S_I_i_N + S_su += S_I - SI_cod + S_I = SI_cod + S_ND_asm1 = X_ND_asm1 = S_NH4 = 0 + + req_sp = S_I * adm_S_I_i_P + S_PO4 -= (req_sp - S_F_P - X_S_P) + S_F_P = X_S_P = 0 + else: + warn('Additional soluble inert COD is mapped to S_su.') + SI_cod = (S_F_P + X_S_P + S_PO4)/adm_S_I_i_P + S_su += S_I - SI_cod + S_I = SI_cod + S_F_P = X_S_P = S_PO4 = 0 + + req_sn = S_I * S_I_i_N + S_NH4 -= (req_sn - S_ND_asm1 - X_ND_asm1) + S_ND_asm1 = X_ND_asm1 = 0 - # Step 6(a): map any remaining TKN + # Step 6: Step map any remaining TKN/P S_IN = S_ND_asm1 + X_ND_asm1 + S_NH4 - # Step 6(b): map any remaining phosphorous S_IP = S_F_P + X_S_P + S_PO4 # Step 8: check COD and TKN balance # has TKN: S_aa, S_IN, S_I, X_pr, X_I S_IC = S_cat = S_an = 0 - # Step 9: Mapping common state variables directly + # Step 9: Mapping common state variables directly # The next three commented lines are executed when outputting # array of ADM1 components # X_PAO (ADM1) = X_PAO (ASM2d) From 2757c76537103239551dab4cabeec976f615df47 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Thu, 18 May 2023 15:31:48 -0500 Subject: [PATCH 124/424] Step 4: P balanced corrected! (ASM2d-ADM1) In Step 4, when updating P in Case II where N is the limiting case, we need to update X_S_P only by the amount of Protein change in that part which is bio2pr, not X_pr. This is because X_pr has already been formed in Step 3. Additionally, in case III when limiting component is P we cannot simply decide the limiting case, because X_pr_i_N >> X_pr_i_P which form the denominator. Hence two cases within case III. The first of these two changes led to TP balance for METRO influent. WOOT! --- qsdsan/sanunits/_junction_copy.py | 63 +++++++++++++++++++++++-------- 1 file changed, 47 insertions(+), 16 deletions(-) diff --git a/qsdsan/sanunits/_junction_copy.py b/qsdsan/sanunits/_junction_copy.py index fa0e9608..79a41acc 100644 --- a/qsdsan/sanunits/_junction_copy.py +++ b/qsdsan/sanunits/_junction_copy.py @@ -840,6 +840,7 @@ def asm2adm(asm_vals): # First we calculate the amount of protein required in ADM1 # if all available particulate organic N can be mapped to amino acid req_xcod = X_ND_asm1 / X_pr_i_N + # Since X_pr_i_N >> X_pr_i_P there's no need to check req_xcod for N and P separately (CONFIRM LATER 05/16) # if available X_S is not enough to fulfill that protein requirement if X_S < req_xcod: @@ -849,6 +850,11 @@ def asm2adm(asm_vals): X_li = X_ch = 0 # This needs to be followed by a corresponding loss in particulate organic N X_ND_asm1 -= X_pr * X_pr_i_N + + # For P balance (CONFIRM LATER 05/16) + # This needs to be followed by a corresponding loss in particulate organic N + X_S_P -= X_pr * X_pr_i_P + # if available X_S is more than enough to fulfill that protein requirement else: # All particulate organic N will be mapped to amino acid @@ -859,6 +865,10 @@ def asm2adm(asm_vals): X_ch = (X_S - X_pr) - X_li # All particulate organic N would thus be consumed in amino acid formation X_ND_asm1 = 0 + + # For P balance (CONFIRM LATER 05/16) + # This needs to be followed by a corresponding loss in particulate organic N + X_S_P -= X_pr * X_pr_i_P # Step 4: convert active biomass into protein, lipids, # carbohydrates and potentially particulate TKN @@ -909,28 +919,46 @@ def asm2adm(asm_vals): X_ND_asm1 = 0 # the remaining biomass P is transfered as organic P - X_S_P += available_bioP - (X_pr*X_pr_i_P) + X_S_P += available_bioP - (bio2pr*X_pr_i_P) # Case III: if available biomass P and particulate organic P is less than # required biomass P for conversion to protein, but available biomass N and # particulate organic N is greater than required biomass N for conversion to protein elif available_bioN + X_ND_asm1 >= req_bioN and available_bioP + X_S_P < req_bioP: - # all available P and particulate organic P is converted to protein - bio2pr = (available_bioP + X_S_P)/X_pr_i_P - X_pr += bio2pr - # Biodegradable biomass available after conversion to protein is calculated - bio_to_split = (X_H + X_AUT) * frac_deg - bio2pr - # Part of the remaining biomass is mapped to lipid based on user defined value - bio_split_to_li = bio_to_split * self.bio_to_li - X_li += bio_split_to_li - # The other portion of the remanining biomass is mapped to carbohydrates - X_ch += (bio_to_split - bio_split_to_li) - # Since all organic P has been mapped to protein, none is left - X_S_P = 0 + if (available_bioP + X_S_P)/X_pr_i_P < (available_bioN + X_ND_asm1)/X_pr_i_N: + # all available P and particulate organic P is converted to protein + bio2pr = (available_bioP + X_S_P)/X_pr_i_P + X_pr += bio2pr + # Biodegradable biomass available after conversion to protein is calculated + bio_to_split = (X_H + X_AUT) * frac_deg - bio2pr + # Part of the remaining biomass is mapped to lipid based on user defined value + bio_split_to_li = bio_to_split * self.bio_to_li + X_li += bio_split_to_li + # The other portion of the remanining biomass is mapped to carbohydrates + X_ch += (bio_to_split - bio_split_to_li) + # Since all organic P has been mapped to protein, none is left + X_S_P = 0 + + # the remaining biomass N is transfered as organic N + X_ND_asm1 += available_bioN - (bio2pr*X_pr_i_N) - # the remaining biomass N is transfered as organic N - X_ND_asm1 += available_bioN - (X_pr*X_pr_i_N) + else: + # all available N and particulate organic N is converted to protein + bio2pr = (available_bioN + X_ND_asm1)/X_pr_i_N + X_pr += bio2pr + # Biodegradable biomass available after conversion to protein is calculated + bio_to_split = (X_H + X_AUT) * frac_deg - bio2pr + # Part of the remaining biomass is mapped to lipid based on user defined value + bio_split_to_li = bio_to_split * self.bio_to_li + X_li += bio_split_to_li + # The other portion of the remanining biomass is mapped to carbohydrates + X_ch += (bio_to_split - bio_split_to_li) + # Since all organic N has been mapped to protein, none is left + X_ND_asm1 = 0 + + # the remaining biomass P is transfered as organic P + X_S_P += available_bioP - (bio2pr*X_pr_i_P) # Case IV: if both available biomass N/P and particulate organic N/P is less than # required biomass N/P for conversion to protein @@ -1039,6 +1067,7 @@ def asm2adm(asm_vals): S_su += S_I - SI_cod S_I = SI_cod S_F_P = X_S_P = S_PO4 = 0 + # Should I redo N balance here? # N balance elif req_sn <= S_ND_asm1 + X_ND_asm1: X_ND_asm1 -= (req_sn - S_ND_asm1) @@ -1059,6 +1088,7 @@ def asm2adm(asm_vals): S_su += S_I - SI_cod S_I = SI_cod S_F_P = X_S_P = S_PO4 = 0 + # Should I redo N balance here? # N balance elif req_sn <= S_ND_asm1 + X_ND_asm1 + S_NH4: S_NH4 -= (req_sn - S_ND_asm1 - X_ND_asm1) @@ -1079,6 +1109,7 @@ def asm2adm(asm_vals): S_su += S_I - SI_cod S_I = SI_cod S_F_P = X_S_P = S_PO4 = 0 + # Should I redo N balance here? elif req_sp <= S_F_P or req_sp <= S_F_P + X_S_P or req_sp <= S_F_P + X_S_P + S_PO4: warn('Additional soluble inert COD is mapped to S_su.') SI_cod = (S_ND_asm1 + X_ND_asm1 + S_NH4)/S_I_i_N @@ -1132,7 +1163,7 @@ def asm2adm(asm_vals): # X_PHA (ADM1) = X_PHA (ASM2d) # X_MeOH (ADM1) = X_MeOH (ASM2d) # X_MeP (ADM1) = X_MeP (ASM2d) - S_IP = S_PO4 # correct, as they are both measured as P + # S_IP = S_PO4 # correct, but not using to balance P # When mapping components directly in Step 9 ensure the values of # cmps.i_N, cmps.i_P, and cmps.i_COD are same in both ASM2d and ADM1 From 4256a1ab2f2d76c2534cbb0e32a9a829fdee0ea9 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Thu, 18 May 2023 17:07:14 -0500 Subject: [PATCH 125/424] Step 4: P balance (ASM2d-ADM1) To complete changes initiated in the last step. --- qsdsan/sanunits/_junction_copy.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/qsdsan/sanunits/_junction_copy.py b/qsdsan/sanunits/_junction_copy.py index 79a41acc..016b2bb6 100644 --- a/qsdsan/sanunits/_junction_copy.py +++ b/qsdsan/sanunits/_junction_copy.py @@ -728,7 +728,7 @@ def _compile_reactions(self): # For nitrogen balance S_aa_i_N = cmps_adm.S_aa.i_N X_pr_i_N = cmps_adm.X_pr.i_N - S_I_i_N = cmps_adm.S_I.i_N + adm_S_I_i_N = cmps_adm.S_I.i_N adm_X_I_i_N = cmps_adm.X_I.i_N # For phosphorous balance @@ -963,7 +963,7 @@ def asm2adm(asm_vals): # Case IV: if both available biomass N/P and particulate organic N/P is less than # required biomass N/P for conversion to protein elif available_bioN + X_ND_asm1 < req_bioN and available_bioP + X_S_P < req_bioP: - if X_pr_i_N > X_pr_i_P: + if (available_bioN + X_ND_asm1)/X_pr_i_N < (available_bioP + X_S_P)/X_pr_i_P: bio2pr = (available_bioN + X_ND_asm1)/X_pr_i_N X_pr += bio2pr # Biodegradable biomass available after conversion to protein is calculated @@ -976,7 +976,7 @@ def asm2adm(asm_vals): # Since all organic N has been mapped to protein, none is left X_ND_asm1 = 0 # the remaining biomass P is transfered as organic P - X_S_P += available_bioP - (X_pr*X_pr_i_P) + X_S_P += available_bioP - (bio2pr*X_pr_i_P) else: bio2pr = (available_bioP + X_ND_asm1)/X_pr_i_P X_pr += bio2pr @@ -990,7 +990,7 @@ def asm2adm(asm_vals): # Since all organic P has been mapped to protein, none is left X_S_P = 0 # the remaining biomass N is transfered as organic N - X_ND_asm1 += available_bioN - (X_pr*X_pr_i_N) + X_ND_asm1 += available_bioN - (bio2pr*X_pr_i_N) # Step 5: map particulate inerts @@ -1044,8 +1044,7 @@ def asm2adm(asm_vals): # Then determine the amount of soluble inert N/P that could be produced # in ADM1 given the ASM1 X_I - # S_I_i_N is for ADM1 - req_sn = S_I * S_I_i_N + req_sn = S_I * adm_S_I_i_N req_sp = S_I * adm_S_I_i_P # N balance @@ -1112,7 +1111,7 @@ def asm2adm(asm_vals): # Should I redo N balance here? elif req_sp <= S_F_P or req_sp <= S_F_P + X_S_P or req_sp <= S_F_P + X_S_P + S_PO4: warn('Additional soluble inert COD is mapped to S_su.') - SI_cod = (S_ND_asm1 + X_ND_asm1 + S_NH4)/S_I_i_N + SI_cod = (S_ND_asm1 + X_ND_asm1 + S_NH4)/adm_S_I_i_N S_su += S_I - SI_cod S_I = SI_cod S_ND_asm1 = X_ND_asm1 = S_NH4 = 0 @@ -1126,9 +1125,9 @@ def asm2adm(asm_vals): S_PO4 -= (req_sp - S_F_P - X_S_P) S_F_P = X_S_P = 0 else: - if S_I_i_N > adm_S_I_i_P: + if (S_ND_asm1 + X_ND_asm1 + S_NH4)/adm_S_I_i_N < (S_F_P + X_S_P + S_PO4)/adm_S_I_i_P: warn('Additional soluble inert COD is mapped to S_su.') - SI_cod = (S_ND_asm1 + X_ND_asm1 + S_NH4)/S_I_i_N + SI_cod = (S_ND_asm1 + X_ND_asm1 + S_NH4)/adm_S_I_i_N S_su += S_I - SI_cod S_I = SI_cod S_ND_asm1 = X_ND_asm1 = S_NH4 = 0 @@ -1143,7 +1142,7 @@ def asm2adm(asm_vals): S_I = SI_cod S_F_P = X_S_P = S_PO4 = 0 - req_sn = S_I * S_I_i_N + req_sn = S_I * adm_S_I_i_N S_NH4 -= (req_sn - S_ND_asm1 - X_ND_asm1) S_ND_asm1 = X_ND_asm1 = 0 From 5d8731625bb905c1d57b4c2dce6b72f2aa09cdfc Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Thu, 18 May 2023 17:33:42 -0500 Subject: [PATCH 126/424] Step 6: N balance holds! (ASM2d-ADM1) In Step 6, The original ASM1-ADM1 implementation never accounted for N present in soluble inert component because ASM1 (S_I_i_N) = 0, but ASM2d (S_I_i_N) = 0.01. This resulted in an error equal to the N content in S_I. Now we are accounting for N in S_I and thus, this issue is resolved. N is balanced in the developed interface. WOOT! --- qsdsan/sanunits/_junction_copy.py | 36 +++++++++++++++++-------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/qsdsan/sanunits/_junction_copy.py b/qsdsan/sanunits/_junction_copy.py index 016b2bb6..755f4538 100644 --- a/qsdsan/sanunits/_junction_copy.py +++ b/qsdsan/sanunits/_junction_copy.py @@ -705,6 +705,7 @@ def _compile_reactions(self): S_F_i_N = cmps_asm.S_F.i_N X_S_i_N = cmps_asm.X_S.i_N asm_X_I_i_N = cmps_asm.X_I.i_N + asm_S_I_i_N = cmps_asm.S_I.i_N # For P balance X_H_i_P = cmps_asm.X_H.i_P @@ -1047,9 +1048,12 @@ def asm2adm(asm_vals): req_sn = S_I * adm_S_I_i_N req_sp = S_I * adm_S_I_i_P + supply_inert_n_asm2d = S_I * asm_S_I_i_N + # N balance - if req_sn <= S_ND_asm1: - S_ND_asm1 -= req_sn + if req_sn <= S_ND_asm1 + supply_inert_n_asm2d: + S_ND_asm1 -= (req_sn - supply_inert_n_asm2d) + supply_inert_n_asm2d = 0 # P balance if req_sp <= S_F_P: S_F_P -= req_sp @@ -1068,9 +1072,9 @@ def asm2adm(asm_vals): S_F_P = X_S_P = S_PO4 = 0 # Should I redo N balance here? # N balance - elif req_sn <= S_ND_asm1 + X_ND_asm1: - X_ND_asm1 -= (req_sn - S_ND_asm1) - S_ND_asm1 = 0 + elif req_sn <= S_ND_asm1 + X_ND_asm1 + supply_inert_n_asm2d: + X_ND_asm1 -= (req_sn - S_ND_asm1 - supply_inert_n_asm2d) + S_ND_asm1 = supply_inert_n_asm2d = 0 # P balance if req_sp <= S_F_P: S_F_P -= req_sp @@ -1089,9 +1093,9 @@ def asm2adm(asm_vals): S_F_P = X_S_P = S_PO4 = 0 # Should I redo N balance here? # N balance - elif req_sn <= S_ND_asm1 + X_ND_asm1 + S_NH4: - S_NH4 -= (req_sn - S_ND_asm1 - X_ND_asm1) - S_ND_asm1 = X_ND_asm1 = 0 + elif req_sn <= S_ND_asm1 + X_ND_asm1 + S_NH4 + supply_inert_n_asm2d: + S_NH4 -= (req_sn - S_ND_asm1 - X_ND_asm1 - supply_inert_n_asm2d) + S_ND_asm1 = X_ND_asm1 = supply_inert_n_asm2d = 0 # P balance if req_sp <= S_F_P: S_F_P -= req_sp @@ -1111,10 +1115,10 @@ def asm2adm(asm_vals): # Should I redo N balance here? elif req_sp <= S_F_P or req_sp <= S_F_P + X_S_P or req_sp <= S_F_P + X_S_P + S_PO4: warn('Additional soluble inert COD is mapped to S_su.') - SI_cod = (S_ND_asm1 + X_ND_asm1 + S_NH4)/adm_S_I_i_N + SI_cod = (S_ND_asm1 + X_ND_asm1 + S_NH4 + supply_inert_n_asm2d)/adm_S_I_i_N S_su += S_I - SI_cod S_I = SI_cod - S_ND_asm1 = X_ND_asm1 = S_NH4 = 0 + S_ND_asm1 = X_ND_asm1 = S_NH4 = supply_inert_n_asm2d = 0 req_sp = S_I * adm_S_I_i_P if req_sp <= S_F_P: S_F_P -= req_sp @@ -1125,12 +1129,12 @@ def asm2adm(asm_vals): S_PO4 -= (req_sp - S_F_P - X_S_P) S_F_P = X_S_P = 0 else: - if (S_ND_asm1 + X_ND_asm1 + S_NH4)/adm_S_I_i_N < (S_F_P + X_S_P + S_PO4)/adm_S_I_i_P: + if (S_ND_asm1 + X_ND_asm1 + S_NH4 + supply_inert_n_asm2d)/adm_S_I_i_N < (S_F_P + X_S_P + S_PO4)/adm_S_I_i_P: warn('Additional soluble inert COD is mapped to S_su.') - SI_cod = (S_ND_asm1 + X_ND_asm1 + S_NH4)/adm_S_I_i_N + SI_cod = (S_ND_asm1 + X_ND_asm1 + S_NH4 + supply_inert_n_asm2d)/adm_S_I_i_N S_su += S_I - SI_cod S_I = SI_cod - S_ND_asm1 = X_ND_asm1 = S_NH4 = 0 + S_ND_asm1 = X_ND_asm1 = S_NH4 = supply_inert_n_asm2d = 0 req_sp = S_I * adm_S_I_i_P S_PO4 -= (req_sp - S_F_P - X_S_P) @@ -1143,11 +1147,11 @@ def asm2adm(asm_vals): S_F_P = X_S_P = S_PO4 = 0 req_sn = S_I * adm_S_I_i_N - S_NH4 -= (req_sn - S_ND_asm1 - X_ND_asm1) - S_ND_asm1 = X_ND_asm1 = 0 + S_NH4 -= (req_sn - S_ND_asm1 - X_ND_asm1 - supply_inert_n_asm2d) + S_ND_asm1 = X_ND_asm1 = supply_inert_n_asm2d = 0 # Step 6: Step map any remaining TKN/P - S_IN = S_ND_asm1 + X_ND_asm1 + S_NH4 + S_IN = S_ND_asm1 + X_ND_asm1 + S_NH4 + supply_inert_n_asm2d S_IP = S_F_P + X_S_P + S_PO4 # Step 8: check COD and TKN balance From bda2132c882ea947bcb6902bcdc3332aa740dcb5 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Thu, 25 May 2023 14:30:51 -0500 Subject: [PATCH 127/424] Update Incinerator to fix bug Made changes to incinerator to fix bug arising due to change in thermosteam. --- qsdsan/sanunits/_sludge_treatment.py | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py index 7f7acc90..49468dea 100644 --- a/qsdsan/sanunits/_sludge_treatment.py +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -628,40 +628,39 @@ def _run(self): if fuel.phase != 'g': raise ValueError(f'The phase of fuel is expected to be gas not {fuel.phase}') - inf = sludge.mass + air.mass + fuel.mass + inf = np.asarray(sludge.mass + air.mass + fuel.mass) idx_n2 = cmps.index(nitrogen_ID) idx_h2o = cmps.index(water_ID) n2 = inf[idx_n2] h2o = inf[idx_h2o] - mass_ash = sum(inf*cmps.i_mass*(1-cmps.f_Vmass_Totmass)) \ - - h2o*cmps.H2O.i_mass*(1-cmps.H2O.f_Vmass_Totmass) \ - - n2*cmps.N2.i_mass*(1-cmps.N2.f_Vmass_Totmass) + mass_ash = np.sum(inf*cmps.i_mass*(1-cmps.f_Vmass_Totmass)) \ + - h2o*cmps.H2O.i_mass*(1-cmps.H2O.f_Vmass_Totmass) - n2*cmps.N2.i_mass*(1-cmps.N2.f_Vmass_Totmass) # Conservation of mass mass_flue_gas = np.sum(inf*cmps.i_mass) - mass_ash mass_co2 = mass_flue_gas - n2*cmps.N2.i_mass - h2o*cmps.H2O.i_mass + flue_gas.set_flow([n2, h2o, (mass_co2/cmps.S_CO2.i_mass)], 'kg/hr', (nitrogen_ID, water_ID, carbon_di_oxide_ID)) ash_cmp_ID = self.ash_component_ID ash_idx = cmps.index(ash_cmp_ID) - ash.set_flow([mass_ash/cmps.i_mass[ash_idx]/(1-cmps.f_Vmass_Totmass[ash_idx])], - 'kg/hr', (ash_cmp_ID)) + ash.set_flow([mass_ash/cmps.i_mass[ash_idx]/(1-cmps.f_Vmass_Totmass[ash_idx])],'kg/hr', (ash_cmp_ID,)) # Energy balance - self.Heat_sludge = sludge.dry_mass*sludge.F_vol*self.calorific_value_sludge/1000 #in KJ/hr (mg/L)*(m3/hr)*(KJ/kg)=KJ/hr*(1/1000) - self.Heat_air = np.sum(air.mass*cmps.i_mass)*self.Cp_air #in KJ/hr - self.Heat_fuel = np.sum(fuel.mass*cmps.i_mass)*self.calorific_value_fuel #in KJ/hr - self.Heat_flue_gas = self.process_efficiency*(self.Heat_sludge + self.Heat_air + self.Heat_fuel) + # self.Heat_sludge = sludge.dry_mass*sludge.F_vol*self.calorific_value_sludge/1000 #in KJ/hr (mg/L)*(m3/hr)*(KJ/kg)=KJ/hr*(1/1000) + # self.Heat_air = np.sum(air.mass*cmps.i_mass)*self.Cp_air #in KJ/hr + # self.Heat_fuel = np.sum(fuel.mass*cmps.i_mass)*self.calorific_value_fuel #in KJ/hr + # self.Heat_flue_gas = self.process_efficiency*(self.Heat_sludge + self.Heat_air + self.Heat_fuel) - # Conservation of energy - self.Heat_loss = self.Heat_sludge + self.Heat_air + self.Heat_fuel - self.Heat_flue_gas + # # Conservation of energy + # self.Heat_loss = self.Heat_sludge + self.Heat_air + self.Heat_fuel - self.Heat_flue_gas def _init_state(self): sludge, air, fuel = self.ins - inf = sludge.mass + air.mass + fuel.mass + inf = np.asarray(sludge.mass + air.mass + fuel.mass) self._state = (24*inf)/1000 self._dstate = self._state * 0. self._cached_state = self._state.copy() From cc178e332eab5cefbafaa034ea06f9c91501b183 Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Sun, 28 May 2023 01:23:39 -0500 Subject: [PATCH 128/424] Simplified Step 4 (ASM2d-ADM1) Simplified step 4 of ASM2d-ADM1 interface. Case II/III/IV, all need the same if else conditions. Also rectified an error message in Step 5(a). --- qsdsan/sanunits/_junction_copy.py | 62 ++++--------------------------- 1 file changed, 8 insertions(+), 54 deletions(-) diff --git a/qsdsan/sanunits/_junction_copy.py b/qsdsan/sanunits/_junction_copy.py index 755f4538..d645173b 100644 --- a/qsdsan/sanunits/_junction_copy.py +++ b/qsdsan/sanunits/_junction_copy.py @@ -899,33 +899,19 @@ def asm2adm(asm_vals): X_pr += (X_H + X_AUT) * frac_deg # the remaining biomass N/P is transfered as organic N/P X_ND_asm1 += available_bioN - req_bioN - X_S_P += available_bioP - req_bioP - + X_S_P += available_bioP - req_bioP + # Case II: if available biomass N and particulate organic N is less than # required biomass N for conversion to protein, but available biomass P and # particulate organic P is greater than required biomass P for conversion to protein - elif available_bioN + X_ND_asm1 < req_bioN and available_bioP + X_S_P >= req_bioP: - - # all available N and particulate organic N is converted to protein - bio2pr = (available_bioN + X_ND_asm1)/X_pr_i_N - X_pr += bio2pr - # Biodegradable biomass available after conversion to protein is calculated - bio_to_split = (X_H + X_AUT) * frac_deg - bio2pr - # Part of the remaining biomass is mapped to lipid based on user defined value - bio_split_to_li = bio_to_split * self.bio_to_li - X_li += bio_split_to_li - # The other portion of the remanining biomass is mapped to carbohydrates - X_ch += (bio_to_split - bio_split_to_li) - # Since all organic N has been mapped to protein, none is left - X_ND_asm1 = 0 - - # the remaining biomass P is transfered as organic P - X_S_P += available_bioP - (bio2pr*X_pr_i_P) - + # Case III: if available biomass P and particulate organic P is less than # required biomass P for conversion to protein, but available biomass N and # particulate organic N is greater than required biomass N for conversion to protein - elif available_bioN + X_ND_asm1 >= req_bioN and available_bioP + X_S_P < req_bioP: + + # Case IV: if both available biomass N/P and particulate organic N/P is less than + # required biomass N/P for conversion to protein + else: if (available_bioP + X_S_P)/X_pr_i_P < (available_bioN + X_ND_asm1)/X_pr_i_N: # all available P and particulate organic P is converted to protein @@ -961,38 +947,6 @@ def asm2adm(asm_vals): # the remaining biomass P is transfered as organic P X_S_P += available_bioP - (bio2pr*X_pr_i_P) - # Case IV: if both available biomass N/P and particulate organic N/P is less than - # required biomass N/P for conversion to protein - elif available_bioN + X_ND_asm1 < req_bioN and available_bioP + X_S_P < req_bioP: - if (available_bioN + X_ND_asm1)/X_pr_i_N < (available_bioP + X_S_P)/X_pr_i_P: - bio2pr = (available_bioN + X_ND_asm1)/X_pr_i_N - X_pr += bio2pr - # Biodegradable biomass available after conversion to protein is calculated - bio_to_split = (X_H + X_AUT) * frac_deg - bio2pr - # Part of the remaining biomass is mapped to lipid based on user defined value - bio_split_to_li = bio_to_split * self.bio_to_li - X_li += bio_split_to_li - # The other portion of the remanining biomass is mapped to carbohydrates - X_ch += (bio_to_split - bio_split_to_li) - # Since all organic N has been mapped to protein, none is left - X_ND_asm1 = 0 - # the remaining biomass P is transfered as organic P - X_S_P += available_bioP - (bio2pr*X_pr_i_P) - else: - bio2pr = (available_bioP + X_ND_asm1)/X_pr_i_P - X_pr += bio2pr - # Biodegradable biomass available after conversion to protein is calculated - bio_to_split = (X_H + X_AUT) * frac_deg - bio2pr - # Part of the remaining biomass is mapped to lipid based on user defined value - bio_split_to_li = bio_to_split * self.bio_to_li - X_li += bio_split_to_li - # The other portion of the remanining biomass is mapped to carbohydrates - X_ch += (bio_to_split - bio_split_to_li) - # Since all organic P has been mapped to protein, none is left - X_S_P = 0 - # the remaining biomass N is transfered as organic N - X_ND_asm1 += available_bioN - (bio2pr*X_pr_i_N) - # Step 5: map particulate inerts @@ -1038,7 +992,7 @@ def asm2adm(asm_vals): 'convert X_I in ASM2d into X_I in ADM1.') else: # Since the N balance cannot hold, the P balance is not futher checked - raise RuntimeError('Not enough N in X_I, X_ND_asm1, X_S_P to fully ' + raise RuntimeError('Not enough N in X_I, X_ND_asm1 to fully ' 'convert X_I in ASM2d into X_I in ADM1.') # 5(b) From 5ff41302355ff13aa9bcd46fa1f855d6ce1179fb Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Sun, 28 May 2023 10:11:16 -0500 Subject: [PATCH 129/424] Correct checks for modified ASM2d-ADM1 X_S in ASM1 did not have N content, as it was taken care of by X_ND. However, in ASM2d X_S has non-zero N content, which we use as 'X_ND_asm1' in the code. Therefore, we no longer need the check (same with S_F). On the other hand N/P content in S_A now needs a check. --- qsdsan/sanunits/_junction_copy.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/qsdsan/sanunits/_junction_copy.py b/qsdsan/sanunits/_junction_copy.py index d645173b..328aad67 100644 --- a/qsdsan/sanunits/_junction_copy.py +++ b/qsdsan/sanunits/_junction_copy.py @@ -714,14 +714,15 @@ def _compile_reactions(self): X_S_i_P = cmps_asm.X_S.i_P asm_X_I_i_P = cmps_asm.X_I.i_P - if cmps_asm.X_S.i_N > 0: - warn(f'X_S in ASM has positive nitrogen content: {cmps_asm.X_S.i_N} gN/gCOD. ' - 'These nitrogen will be ignored by the interface model ' - 'and could lead to imbalance of TKN after conversion.') if cmps_asm.S_A.i_N > 0: warn(f'S_A in ASM has positive nitrogen content: {cmps_asm.S_S.i_N} gN/gCOD. ' 'These nitrogen will be ignored by the interface model ' 'and could lead to imbalance of TKN after conversion.') + if cmps_asm.S_A.i_P > 0: + warn(f'S_A in ASM has positive phosphorous content: {cmps_asm.S_S.i_P} gN/gCOD. ' + 'These phosphorous will be ignored by the interface model ' + 'and could lead to imbalance of TP after conversion.') + # We do not need to check if X_S.i_N != 0 since we take care of it using X_ND_asm1 # We do not need to check if S_F.i_N != 0 since we take care of it using S_ND_asm1 cmps_adm = outs.components @@ -992,7 +993,7 @@ def asm2adm(asm_vals): 'convert X_I in ASM2d into X_I in ADM1.') else: # Since the N balance cannot hold, the P balance is not futher checked - raise RuntimeError('Not enough N in X_I, X_ND_asm1 to fully ' + raise RuntimeError('Not enough N in X_I, X_S to fully ' 'convert X_I in ASM2d into X_I in ADM1.') # 5(b) @@ -1120,7 +1121,6 @@ def asm2adm(asm_vals): # X_PHA (ADM1) = X_PHA (ASM2d) # X_MeOH (ADM1) = X_MeOH (ASM2d) # X_MeP (ADM1) = X_MeP (ASM2d) - # S_IP = S_PO4 # correct, but not using to balance P # When mapping components directly in Step 9 ensure the values of # cmps.i_N, cmps.i_P, and cmps.i_COD are same in both ASM2d and ADM1 From 52947023694f91cbd64918b827d6d73a59ddf86e Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Sun, 28 May 2023 10:31:27 -0500 Subject: [PATCH 130/424] Added check for P content in SI (ASM2d-ADM1) Since in step 5(b) soluble inert P is assumed to be 0, a check needs to be put in place. Other changes made with regard to documentation. --- qsdsan/sanunits/_junction_copy.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/qsdsan/sanunits/_junction_copy.py b/qsdsan/sanunits/_junction_copy.py index 328aad67..3957fd2f 100644 --- a/qsdsan/sanunits/_junction_copy.py +++ b/qsdsan/sanunits/_junction_copy.py @@ -604,7 +604,7 @@ class ASMtoADM(ADMjunction): downstream : stream or str Effluent stream with ADM components. adm1_model : obj - The anaerobic digestion process model (:class:`qsdsan.processes.ADM1`). + The anaerobic digestion process model (:class:`qsdsan.processes.ADM1_p_extension`). xs_to_li : float Split of slowly biodegradable substrate COD to lipid, after all N is mapped into protein. @@ -624,6 +624,11 @@ class ASMtoADM(ADMjunction): Alex, J.; Vanrolleghem, P. A. An ASM/ADM Model Interface for Dynamic Plant-Wide Simulation. Water Res. 2009, 43, 1913–1923. + [2] Flores-Alsina, X., Solon, K., Kazadi Mbamba, C., Tait, S., Gernaey, K. V., + Jeppsson, U., & Batstone, D. J. (2016). Modelling phosphorus (P), sulfur (S) + and iron (FE) interactions for dynamic simulations of anaerobic digestion processes. + Water Research, 95, 370–382. + See Also -------- :class:`qsdsan.sanunits.ADMjunction` @@ -722,6 +727,10 @@ def _compile_reactions(self): warn(f'S_A in ASM has positive phosphorous content: {cmps_asm.S_S.i_P} gN/gCOD. ' 'These phosphorous will be ignored by the interface model ' 'and could lead to imbalance of TP after conversion.') + if cmps_asm.S_I.i_P > 0: + warn(f'S_I in ASM has positive phosphorous content: {cmps_asm.S_I.i_P} gN/gCOD. ' + 'These phosphorous will be ignored by the interface model ' + 'and could lead to imbalance of TP after conversion.') # We do not need to check if X_S.i_N != 0 since we take care of it using X_ND_asm1 # We do not need to check if S_F.i_N != 0 since we take care of it using S_ND_asm1 @@ -738,7 +747,6 @@ def _compile_reactions(self): adm_S_I_i_P = cmps_adm.S_I.i_P adm_X_I_i_P = cmps_adm.X_I.i_P - adm_ions_idx = cmps_adm.indices(['S_IN', 'S_IC', 'S_cat', 'S_an']) frac_deg = self.frac_deg From ff165552ed731ab939607a222ce37ef9ee594153 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Sun, 28 May 2023 12:27:30 -0500 Subject: [PATCH 131/424] Updated check on COD/TKN/TP balance Modified the function which checks simultaneous balance of nutrients. In ASM1-ADM1 only COD, and TKN were checked. Now the simultaneous balanced of COD, TKN, and TP would be verified. --- qsdsan/sanunits/_junction_copy.py | 84 ++++++++++++++++++++++++------- 1 file changed, 65 insertions(+), 19 deletions(-) diff --git a/qsdsan/sanunits/_junction_copy.py b/qsdsan/sanunits/_junction_copy.py index 3957fd2f..f4ab01bc 100644 --- a/qsdsan/sanunits/_junction_copy.py +++ b/qsdsan/sanunits/_junction_copy.py @@ -649,7 +649,7 @@ def isbalanced(self, lhs, rhs_vals, rhs_i): tol = max(self.rtol*lhs, self.rtol*rhs, self.atol) return abs(error) <= tol, error, tol, rhs - def balance_cod_tkn(self, asm_vals, adm_vals): + def balance_cod_tkn_tp(self, asm_vals, adm_vals): cmps_asm = self.ins[0].components cmps_adm = self.outs[0].components asm_i_COD = cmps_asm.i_COD @@ -657,40 +657,86 @@ def balance_cod_tkn(self, asm_vals, adm_vals): non_tkn_idx = cmps_asm.indices(('S_NO', 'S_N2')) asm_i_N = cmps_asm.i_N adm_i_N = cmps_adm.i_N + asm_i_P = cmps_asm.i_P + adm_i_P = cmps_adm.i_P asm_cod = sum(asm_vals*asm_i_COD) asm_tkn = sum(asm_vals*asm_i_N) - sum(asm_vals[non_tkn_idx]) + asm_tp = sum(asm_vals*asm_i_P) cod_bl, cod_err, cod_tol, adm_cod = self.isbalanced(asm_cod, adm_vals, adm_i_COD) tkn_bl, tkn_err, tkn_tol, adm_tkn = self.isbalanced(asm_tkn, adm_vals, adm_i_N) - if cod_bl: - if tkn_bl: return adm_vals + tp_bl, tp_err, tp_tol, adm_tp = self.isbalanced(asm_tp, adm_vals, adm_i_P) + + if tkn_bl and tp_bl: + if cod_bl: + return adm_vals + else: + if cod_err > 0: dcod = -(cod_err - cod_tol)/adm_cod + else: dcod = -(cod_err + cod_tol)/adm_cod + _adm_vals = adm_vals * (1 + (adm_i_COD>0)*dcod) + _tkn_bl, _tkn_err, _tkn_tol, _adm_tkn = self.isbalanced(asm_tkn, _adm_vals, adm_i_N) + _tp_bl, _tp_err, _tp_tol, _adm_tp = self.isbalanced(asm_tp, _adm_vals, adm_i_P) + if _tkn_bl and _tp_bl: return _adm_vals + else: + warn('cannot balance COD, TKN, and TP at the same \n' + f'time with rtol={self.rtol} and atol={self.atol}.\n ' + f'influent (ASM) TKN is {asm_tkn}\n ' + f'effluent (ADM) TKN is {adm_tkn} or {_adm_tkn}\n ' + f'influent TP is {asm_tp}\n ' + f'effluent TP is {adm_tp} or {_adm_tp}. ' + f'influent COD is {asm_cod}\n ' + f'effluent COD is {adm_cod} or {adm_cod*(1+dcod)}. ') + return adm_vals + elif cod_bl and tp_bl: + if tkn_bl: + return adm_vals else: if tkn_err > 0: dtkn = -(tkn_err - tkn_tol)/adm_tkn else: dtkn = -(tkn_err + tkn_tol)/adm_tkn _adm_vals = adm_vals * (1 + (adm_i_N>0)*dtkn) _cod_bl, _cod_err, _cod_tol, _adm_cod = self.isbalanced(asm_cod, _adm_vals, adm_i_COD) - if _cod_bl: return _adm_vals + _tp_bl, _tp_err, _tp_tol, _adm_tp = self.isbalanced(asm_tp, _adm_vals, adm_i_P) + if _cod_bl and _tp_bl: return _adm_vals else: - warn('cannot balance COD and TKN at the same ' + warn('cannot balance COD, TKN, and TP at the same time' f'time with rtol={self.rtol} and atol={self.atol}.\n ' f'influent (ASM) COD is {asm_cod}\n ' f'effluent (ADM) COD is {adm_cod} or {_adm_cod}\n ' + f'influent TP is {asm_tp}\n ' + f'effluent TP is {adm_tp} or {_adm_tp}. ' f'influent TKN is {asm_tkn}\n ' f'effluent TKN is {adm_tkn} or {adm_tkn*(1+dtkn)}. ') return adm_vals - else: - if cod_err > 0: dcod = -(cod_err - cod_tol)/adm_cod - else: dcod = -(cod_err + cod_tol)/adm_cod - _adm_vals = adm_vals * (1 + (adm_i_COD>0)*dcod) - _tkn_bl, _tkn_err, _tkn_tol, _adm_tkn = self.isbalanced(asm_tkn, _adm_vals, adm_i_N) - if _tkn_bl: return _adm_vals - else: - warn('cannot balance COD and TKN at the same ' - f'time with rtol={self.rtol} and atol={self.atol}.\n ' - f'influent (ASM) COD is {asm_cod}\n ' - f'effluent (ADM) COD is {adm_cod} or {adm_cod*(1+dcod)}\n ' - f'influent TKN is {asm_tkn}\n ' - f'effluent TKN is {adm_tkn} or {_adm_tkn}. ') + elif cod_bl and tkn_bl: + if tp_bl: return adm_vals + else: + if tp_err > 0: dtp = -(tp_err - tp_tol)/adm_tp + else: dtp = -(tp_err + tp_tol)/adm_tp + _adm_vals = adm_vals * (1 + (adm_i_P>0)*dtp) + _cod_bl, _cod_err, _cod_tol, _adm_cod = self.isbalanced(asm_cod, _adm_vals, adm_i_COD) + _tkn_bl, _tkn_err, _tkn_tol, _adm_tkn = self.isbalanced(asm_tkn, _adm_vals, adm_i_N) + if _cod_bl and _tkn_bl: return _adm_vals + else: + warn('cannot balance COD, TKN, and TP at the same time' + f'time with rtol={self.rtol} and atol={self.atol}.\n ' + f'influent (ASM) COD is {asm_cod}\n ' + f'effluent (ADM) COD is {adm_cod} or {_adm_cod}\n ' + f'influent TKN is {asm_tkn}\n ' + f'effluent TKN is {adm_tkn} or {_adm_tkn}. ' + f'influent TP is {asm_tp}\n ' + f'effluent TP is {adm_tp} or {adm_tp*(1+dtp)}. ') + return adm_vals + else: + warn('cannot balance COD, TKN and TP at the same time. \n' + 'Atleast two of the three COD, TKN, and TP are not balanced \n' + f'time with rtol={self.rtol} and atol={self.atol}.\n ' + f'influent (ASM) COD is {asm_cod}\n ' + f'effluent (ADM) COD is {adm_cod}\n ' + f'influent TP is {asm_tp}\n ' + f'effluent TP is {adm_tp}' + f'influent TKN is {asm_tkn}\n ' + f'effluent TKN is {adm_tkn}. ') + return adm_vals def _compile_reactions(self): # Retrieve constants @@ -753,7 +799,7 @@ def _compile_reactions(self): alpha_IN = self.alpha_IN alpha_IC = self.alpha_IC proton_charge = 10**(-self.pKa[0]+self.pH) - 10**(-self.pH) # self.pKa[0] is pKw - f_corr = self.balance_cod_tkn + f_corr = self.balance_cod_tkn_tp def asm2adm(asm_vals): # S_I, S_S, X_I, X_S, X_BH, X_BA, X_P, S_O, S_NO, S_NH, S_ND, X_ND, S_ALK, S_N2, H2O = asm_vals From 7c5257360d2d8b26c249fa6e932b4c15de4f60c4 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Sun, 28 May 2023 12:57:44 -0500 Subject: [PATCH 132/424] Checks for direct mapping of X_PAO, X_PHA, and X_PP Deployed checks for N and P content in PAO across ASM2d and ADM1, since PAO is mapped directly. Similar checks were not deemed necessary for PHA, and PP. --- qsdsan/sanunits/_junction_copy.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/qsdsan/sanunits/_junction_copy.py b/qsdsan/sanunits/_junction_copy.py index f4ab01bc..b464eecd 100644 --- a/qsdsan/sanunits/_junction_copy.py +++ b/qsdsan/sanunits/_junction_copy.py @@ -793,6 +793,26 @@ def _compile_reactions(self): adm_S_I_i_P = cmps_adm.S_I.i_P adm_X_I_i_P = cmps_adm.X_I.i_P + # Checks for direct mapping of X_PAO, X_PP, X_PHA + + # Check for X_PAO (measured as COD so i_COD = 1 in both ASM2d and ADM1) + asm_X_PAO_i_N = cmps_asm.X_PAO.i_N + adm_X_PAO_i_N = cmps_adm.X_PAO.i_N + if asm_X_PAO_i_N != adm_X_PAO_i_N: + raise RuntimeError('X_PAO cannot be directly mapped as N content' + f'in asm2d_X_PAO_i_N = {asm_X_PAO_i_N} is not equal to' + f'adm_X_PAO_i_N = {adm_X_PAO_i_N}') + + asm_X_PAO_i_P = cmps_asm.X_PAO.i_P + adm_X_PAO_i_P = cmps_adm.X_PAO.i_P + if asm_X_PAO_i_P != adm_X_PAO_i_P: + raise RuntimeError('X_PAO cannot be directly mapped as P content' + f'in asm2d_X_PAO_i_P = {asm_X_PAO_i_P} is not equal to' + f'adm_X_PAO_i_P = {adm_X_PAO_i_P}') + + # Checks not required for X_PP as measured as P in both, with i_COD = i_N = 0 + # Checks not required for X_PHA as measured as COD in both, with i_N = i_P = 0 + adm_ions_idx = cmps_adm.indices(['S_IN', 'S_IC', 'S_cat', 'S_an']) frac_deg = self.frac_deg From 1eb91c8d777266f8d9eb0a35ca0736a3e0b871e8 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Sun, 28 May 2023 13:34:06 -0500 Subject: [PATCH 133/424] Updated charge balance (ASM2d-ADM1) Updated charge balance to include inorganic phosphorous on the ADM1 side. --- qsdsan/sanunits/_junction_copy.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/qsdsan/sanunits/_junction_copy.py b/qsdsan/sanunits/_junction_copy.py index b464eecd..6d09bcc7 100644 --- a/qsdsan/sanunits/_junction_copy.py +++ b/qsdsan/sanunits/_junction_copy.py @@ -813,9 +813,10 @@ def _compile_reactions(self): # Checks not required for X_PP as measured as P in both, with i_COD = i_N = 0 # Checks not required for X_PHA as measured as COD in both, with i_N = i_P = 0 - adm_ions_idx = cmps_adm.indices(['S_IN', 'S_IC', 'S_cat', 'S_an']) + adm_ions_idx = cmps_adm.indices(['S_IN', 'S_IP', 'S_IC', 'S_cat', 'S_an']) frac_deg = self.frac_deg + alpha_IP = self.alpha_IP alpha_IN = self.alpha_IN alpha_IC = self.alpha_IC proton_charge = 10**(-self.pKa[0]+self.pH) - 10**(-self.pH) # self.pKa[0] is pKw @@ -1187,6 +1188,9 @@ def asm2adm(asm_vals): # has TKN: S_aa, S_IN, S_I, X_pr, X_I S_IC = S_cat = S_an = 0 + # When mapping components directly in Step 9 ensure the values of + # cmps.i_N, cmps.i_P, and cmps.i_COD are same in both ASM2d and ADM1 + # Step 9: Mapping common state variables directly # The next three commented lines are executed when outputting # array of ADM1 components @@ -1196,10 +1200,6 @@ def asm2adm(asm_vals): # X_MeOH (ADM1) = X_MeOH (ASM2d) # X_MeP (ADM1) = X_MeP (ASM2d) - # When mapping components directly in Step 9 ensure the values of - # cmps.i_N, cmps.i_P, and cmps.i_COD are same in both ASM2d and ADM1 - # ^ Need to code this (05/08) - adm_vals = np.array([ S_su, S_aa, 0, 0, 0, 0, 0, # S_fa, S_va, S_bu, S_pro, S_ac, @@ -1219,7 +1219,10 @@ def asm2adm(asm_vals): #!!! charge balance should technically include VFAs, # but VFAs concentrations are assumed zero per previous steps?? S_IN = adm_vals[adm_ions_idx[0]] - S_IC = (asm_charge_tot - S_IN*alpha_IN)/alpha_IC + S_IP = adm_vals[adm_ions_idx[1]] + #!!! charge balance from ADM1 should technically include S_K, and S_Mg, + #but since both are zero, it is acceptable + S_IC = (asm_charge_tot -S_IN*alpha_IN -S_IP*alpha_IP)/alpha_IC net_Scat = asm_charge_tot + proton_charge if net_Scat > 0: S_cat = net_Scat @@ -1228,7 +1231,7 @@ def asm2adm(asm_vals): S_cat = 0 S_an = -net_Scat - adm_vals[adm_ions_idx[1:]] = [S_IC, S_cat, S_an] + adm_vals[adm_ions_idx[2:]] = [S_IC, S_cat, S_an] return adm_vals From 4b61c19035c4e9d2c50e9d93a65c55c5107120b0 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Mon, 29 May 2023 11:51:46 -0500 Subject: [PATCH 134/424] ADM1-ASM2d interface Started modifying ADM1-ASM2d interface --- qsdsan/sanunits/_junction_copy.py | 57 +++++++++++++++++++++++++------ 1 file changed, 46 insertions(+), 11 deletions(-) diff --git a/qsdsan/sanunits/_junction_copy.py b/qsdsan/sanunits/_junction_copy.py index 6d09bcc7..739adc9a 100644 --- a/qsdsan/sanunits/_junction_copy.py +++ b/qsdsan/sanunits/_junction_copy.py @@ -477,14 +477,44 @@ def _compile_reactions(self): f_corr = self.balance_cod_tkn def adm2asm(adm_vals): - S_su, S_aa, S_fa, S_va, S_bu, S_pro, S_ac, S_h2, S_ch4, S_IC, S_IN, S_I, \ - X_c, X_ch, X_pr, X_li, X_su, X_aa, X_fa, X_c4, X_pro, X_ac, X_h2, X_I, \ - S_cat, S_an, H2O = adm_vals + S_su, S_aa, S_fa, S_va, S_bu, S_pro, S_ac, S_h2, S_ch4, S_IC, S_IN, S_IP, S_I, \ + X_ch, X_pr, X_li, X_su, X_aa, X_fa, X_c4, X_pro, X_ac, X_h2, X_I, \ + X_PHA, X_PP, X_PAO, S_K, S_Mg, X_MeOH, X_MeP, S_cat, S_an, H2O = adm_vals # Step 0: snapshot of charged components - _ions = np.array([S_IN, S_IC, S_ac, S_pro, S_bu, S_va]) + # Not sure about charge on X_PP, S_Mg, S_K (PHA and PAO would have zero charge) + _ions = np.array([S_IN, S_IC, S_IP, X_PP, S_Mg, S_K, S_ac, S_pro, S_bu, S_va]) # Step 1a: convert biomass into X_S+X_ND and X_P + # bio_cod = X_su + X_aa + X_fa + X_c4 + X_pro + X_ac + X_h2 + # bio_n = sum((adm_vals*adm_i_N)[adm_bio_N_indices]) + # xp_cod = bio_cod * (1-self.bio_to_xs) + # xp_ndm = xp_cod*X_P_i_N + # if xp_ndm > bio_n: + # warn('Not enough biomass N to map the specified proportion of ' + # 'biomass COD into X_P. Mapped as much COD as possible, the rest ' + # 'goes to X_S.') + # X_P = bio_n/asm_X_P_i_N + # bio_n = 0 + # else: + # X_P = xp_cod + # bio_n -= xp_ndm + # X_S = bio_cod - X_P + # xs_ndm = X_S*X_S_i_N + # if xs_ndm <= bio_n: + # X_ND = bio_n - xs_ndm + # bio_n = 0 + # elif xs_ndm <= bio_n + S_IN: + # X_ND = 0 + # S_IN -= (xs_ndm - bio_n) + # bio_n = 0 + # else: + # if isclose(xs_ndm, bio_n + S_IN, rel_tol=rtol, abs_tol=atol): + # X_ND = S_IN = bio_n = 0 + # else: + # raise RuntimeError('Not enough nitrogen (S_IN + biomass) to map ' + # 'all biomass COD into X_P and X_S') + bio_cod = X_su + X_aa + X_fa + X_c4 + X_pro + X_ac + X_h2 bio_n = sum((adm_vals*adm_i_N)[adm_bio_N_indices]) xp_cod = bio_cod * (1-self.bio_to_xs) @@ -562,14 +592,19 @@ def adm2asm(adm_vals): 'substrates into ASM S_S') # Step 6: check COD and TKN balance + # asm_vals = np.array(([ + # S_I, S_S, X_I, X_S, + # 0, 0, # X_BH, X_BA, + # X_P, + # 0, 0, # S_O, S_NO, + # S_NH, S_ND, X_ND, + # 0, 0, # temporary S_ALK, S_N2, + # H2O])) + asm_vals = np.array(([ - S_I, S_S, X_I, X_S, - 0, 0, # X_BH, X_BA, - X_P, - 0, 0, # S_O, S_NO, - S_NH, S_ND, X_ND, - 0, 0, # temporary S_ALK, S_N2, - H2O])) + S_O2, S_N2, S_NH4, S_NO3, S_PO4, S_F, S_A, + S_I, S_ALK, X_I, X_S, X_H, X_PAO, X_PP, X_PHA, + X_AUT, X_MeOH, X_MeP, H2O])) if S_h2 > 0 or S_ch4 > 0: warn('Ignored dissolved H2 or CH4.') From 23fbb1cb8f02dc7d64d4c9ace4e72ae7079aea68 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Mon, 29 May 2023 16:28:15 -0500 Subject: [PATCH 135/424] Updated checks and step 1(a) (ADM1-ASM2d) Modified the function which checks simultaneous balance of nutrients. In ADM1-ASM1 only COD, and TKN were checked. Now the simultaneous balanced of COD, TKN, and TP would be verified. Also modified step 1(a) to update N balance --- qsdsan/sanunits/_junction_copy.py | 164 ++++++++++++++++++------------ 1 file changed, 98 insertions(+), 66 deletions(-) diff --git a/qsdsan/sanunits/_junction_copy.py b/qsdsan/sanunits/_junction_copy.py index 739adc9a..08c5eb27 100644 --- a/qsdsan/sanunits/_junction_copy.py +++ b/qsdsan/sanunits/_junction_copy.py @@ -410,40 +410,87 @@ def balance_cod_tkn(self, adm_vals, asm_vals): gas_idx = cmps_adm.indices(('S_h2', 'S_ch4')) asm_i_N = cmps_asm.i_N adm_i_N = cmps_adm.i_N + asm_i_P = cmps_asm.i_P + adm_i_P = cmps_adm.i_P adm_cod = sum(adm_vals*adm_i_COD) - sum(adm_vals[gas_idx]) adm_tkn = sum(adm_vals*adm_i_N) + adm_tp = sum(adm_vals*adm_i_P) + cod_bl, cod_err, cod_tol, asm_cod = self.isbalanced(adm_cod, asm_vals, asm_i_COD) tkn_bl, tkn_err, tkn_tol, asm_tkn = self.isbalanced(adm_tkn, asm_vals, asm_i_N) - if cod_bl: - if tkn_bl: return asm_vals + tp_bl, tp_err, tp_tol, asm_tp = self.isbalanced(adm_tp, asm_vals, asm_i_P) + + if tkn_bl and tp_bl: + if cod_bl: + return asm_vals + else: + if cod_err > 0: dcod = -(cod_err - cod_tol)/asm_cod + else: dcod = -(cod_err + cod_tol)/asm_cod + _asm_vals = asm_vals * (1 + (asm_i_COD>0)*dcod) + _tkn_bl, _tkn_err, _tkn_tol, _asm_tkn = self.isbalanced(adm_tkn, _asm_vals, asm_i_N) + _tp_bl, _tp_err, _tp_tol, _asm_tp = self.isbalanced(adm_tp, _asm_vals, asm_i_P) + if _tkn_bl and _tp_bl: return _asm_vals + else: + warn('cannot balance COD, TKN, and TP at the same \n' + f'time with rtol={self.rtol} and atol={self.atol}.\n ' + f'influent (ADM) TKN is {adm_tkn}\n ' + f'effluent (ASM) TKN is {asm_tkn} or {_asm_tkn}\n ' + f'influent (ADM) TP is {adm_tp}\n ' + f'effluent (ASM) TP is {asm_tp} or {_asm_tp}. ' + f'influent (ADM) COD is {adm_cod}\n ' + f'effluent (ASM) COD is {asm_cod} or {asm_cod*(1+dcod)}. ') + return asm_vals + elif cod_bl and tp_bl: + if tkn_bl: + return asm_vals else: if tkn_err > 0: dtkn = -(tkn_err - tkn_tol)/asm_tkn else: dtkn = -(tkn_err + tkn_tol)/asm_tkn _asm_vals = asm_vals * (1 + (asm_i_N>0)*dtkn) _cod_bl, _cod_err, _cod_tol, _asm_cod = self.isbalanced(adm_cod, _asm_vals, asm_i_COD) - if _cod_bl: return _asm_vals + _tp_bl, _tp_err, _tp_tol, _asm_tp = self.isbalanced(adm_tp, _asm_vals, asm_i_P) + if _cod_bl and _tp_bl: return _asm_vals else: - warn('cannot balance COD and TKN at the same ' - f'time with rtol={self.rtol} and atol={self.atol}. ' - f'influent (ADM) COD is {adm_cod}, ' - f'effluent (ASM) COD is {asm_cod} or {_asm_cod}. ' - f'influent TKN is {adm_tkn}, ' - f'effluent TKN is {asm_tkn} or {asm_tkn*(1+dtkn)}. ') + warn('cannot balance COD, TKN, and TP at the same time' + f'time with rtol={self.rtol} and atol={self.atol}.\n ' + f'influent (ADM) COD is {adm_cod}\n ' + f'effluent (ASM) COD is {asm_cod} or {_asm_cod}\n ' + f'influent (ADM) TP is {adm_tp}\n ' + f'effluent (ASM) TP is {asm_tp} or {_asm_tp}. ' + f'influent (ADM) TKN is {adm_tkn}\n ' + f'effluent (ASM) TKN is {asm_tkn} or {asm_tkn*(1+dtkn)}. ') return asm_vals - else: - if cod_err > 0: dcod = -(cod_err - cod_tol)/asm_cod - else: dcod = -(cod_err + cod_tol)/asm_cod - _asm_vals = asm_vals * (1 + (asm_i_COD>0)*dcod) - _tkn_bl, _tkn_err, _tkn_tol, _asm_tkn = self.isbalanced(adm_tkn, _asm_vals, asm_i_N) - if _tkn_bl: return _asm_vals - else: - warn('cannot balance COD and TKN at the same ' - f'time with rtol={self.rtol} and atol={self.atol}. ' - f'influent (ADM) COD is {adm_cod}, ' - f'effluent (ASM) COD is {asm_cod} or {asm_cod*(1+dcod)}. ' - f'influent TKN is {adm_tkn}, ' - f'effluent TKN is {asm_tkn} or {_asm_tkn}. ') + elif cod_bl and tkn_bl: + if tp_bl: return asm_vals + else: + if tp_err > 0: dtp = -(tp_err - tp_tol)/asm_tp + else: dtp = -(tp_err + tp_tol)/asm_tp + _asm_vals = asm_vals * (1 + (asm_i_P>0)*dtp) + _cod_bl, _cod_err, _cod_tol, _asm_cod = self.isbalanced(adm_cod, _asm_vals, asm_i_COD) + _tkn_bl, _tkn_err, _tkn_tol, _asm_tkn = self.isbalanced(adm_tkn, _asm_vals, asm_i_N) + if _cod_bl and _tkn_bl: return _asm_vals + else: + warn('cannot balance COD, TKN, and TP at the same time' + f'time with rtol={self.rtol} and atol={self.atol}.\n ' + f'influent (ADM) COD is {adm_cod}\n ' + f'effluent (ASM) COD is {asm_cod} or {_asm_cod}\n ' + f'influent (ADM) TKN is {adm_tkn}\n ' + f'effluent (ASM) TKN is {asm_tkn} or {_asm_tkn}. ' + f'influent (ADM) TP is {adm_tp}\n ' + f'effluent (ASM) TP is {asm_tp} or {asm_tp*(1+dtp)}. ') + return asm_vals + else: + warn('cannot balance COD, TKN and TP at the same time. \n' + 'Atleast two of the three COD, TKN, and TP are not balanced \n' + f'time with rtol={self.rtol} and atol={self.atol}.\n ' + f'influent (ADM) COD is {adm_cod}\n ' + f'effluent (ASM) COD is {asm_cod}\n ' + f'influent (ADM) TP is {adm_tp}\n ' + f'effluent (ASM) TP is {asm_tp}' + f'influent (ADM) TKN is {adm_tkn}\n ' + f'effluent (ASM) TKN is {asm_tkn}. ') + return asm_vals def _compile_reactions(self): # Retrieve constants @@ -463,12 +510,12 @@ def _compile_reactions(self): 'X_c4', 'X_pro', 'X_ac', 'X_h2')) cmps_asm = outs.components - X_P_i_N = cmps_asm.X_P.i_N + # X_P_i_N = cmps_asm.X_P.i_N X_S_i_N = cmps_asm.X_S.i_N S_S_i_N = cmps_asm.S_S.i_N asm_X_I_i_N = cmps_asm.X_I.i_N asm_S_I_i_N = cmps_asm.S_I.i_N - asm_X_P_i_N = cmps_asm.X_P.i_N + # asm_X_P_i_N = cmps_asm.X_P.i_N asm_ions_idx = cmps_asm.indices(('S_NH', 'S_ALK')) alpha_IN = self.alpha_IN @@ -486,63 +533,48 @@ def adm2asm(adm_vals): _ions = np.array([S_IN, S_IC, S_IP, X_PP, S_Mg, S_K, S_ac, S_pro, S_bu, S_va]) # Step 1a: convert biomass into X_S+X_ND and X_P - # bio_cod = X_su + X_aa + X_fa + X_c4 + X_pro + X_ac + X_h2 - # bio_n = sum((adm_vals*adm_i_N)[adm_bio_N_indices]) + bio_cod = X_su + X_aa + X_fa + X_c4 + X_pro + X_ac + X_h2 + bio_n = sum((adm_vals*adm_i_N)[adm_bio_N_indices]) + + # There is no X_P (particulate products arising due to biomass decay) + # or equivalent component in ASM2d # xp_cod = bio_cod * (1-self.bio_to_xs) # xp_ndm = xp_cod*X_P_i_N # if xp_ndm > bio_n: # warn('Not enough biomass N to map the specified proportion of ' - # 'biomass COD into X_P. Mapped as much COD as possible, the rest ' - # 'goes to X_S.') + # 'biomass COD into X_P. Mapped as much COD as possible, the rest ' + # 'goes to X_S.') # X_P = bio_n/asm_X_P_i_N # bio_n = 0 # else: # X_P = xp_cod # bio_n -= xp_ndm + # X_S = bio_cod - X_P - # xs_ndm = X_S*X_S_i_N - # if xs_ndm <= bio_n: - # X_ND = bio_n - xs_ndm - # bio_n = 0 - # elif xs_ndm <= bio_n + S_IN: - # X_ND = 0 - # S_IN -= (xs_ndm - bio_n) - # bio_n = 0 - # else: - # if isclose(xs_ndm, bio_n + S_IN, rel_tol=rtol, abs_tol=atol): - # X_ND = S_IN = bio_n = 0 - # else: - # raise RuntimeError('Not enough nitrogen (S_IN + biomass) to map ' - # 'all biomass COD into X_P and X_S') - - bio_cod = X_su + X_aa + X_fa + X_c4 + X_pro + X_ac + X_h2 - bio_n = sum((adm_vals*adm_i_N)[adm_bio_N_indices]) - xp_cod = bio_cod * (1-self.bio_to_xs) - xp_ndm = xp_cod*X_P_i_N - if xp_ndm > bio_n: - warn('Not enough biomass N to map the specified proportion of ' - 'biomass COD into X_P. Mapped as much COD as possible, the rest ' - 'goes to X_S.') - X_P = bio_n/asm_X_P_i_N - bio_n = 0 - else: - X_P = xp_cod - bio_n -= xp_ndm - X_S = bio_cod - X_P - xs_ndm = X_S*X_S_i_N - if xs_ndm <= bio_n: - X_ND = bio_n - xs_ndm + + # COD balance + X_S = bio_cod + + # N balance + X_S_N = X_S*X_S_i_N + if X_S_N <= bio_n: + # In ASM1-ADM1 interface if there was excess bio_n compared to + # X_S_N, it was transferred to X_ND. But since X_ND does not + # exist in ASM2d, S_IN is used for N balance + # Here, S_IN is also used based on the fact that in case of + # deficit bio_N, S_IN is used to compensate for excess X_S_N + # in Nopens at al. 2009 + S_IN += (bio_n - X_S_N) bio_n = 0 - elif xs_ndm <= bio_n + S_IN: - X_ND = 0 - S_IN -= (xs_ndm - bio_n) + elif X_S_N <= bio_n + S_IN: + S_IN -= (X_S_N - bio_n) bio_n = 0 else: - if isclose(xs_ndm, bio_n + S_IN, rel_tol=rtol, abs_tol=atol): - X_ND = S_IN = bio_n = 0 + if isclose(X_S_N, bio_n + S_IN, rel_tol=rtol, abs_tol=atol): + S_IN = bio_n = 0 else: raise RuntimeError('Not enough nitrogen (S_IN + biomass) to map ' - 'all biomass COD into X_P and X_S') + 'all biomass COD into X_S') # Step 1b: convert particulate substrates into X_S + X_ND xsub_cod = X_c + X_ch + X_pr + X_li From b2fb6b0fb93db1c9ca84940994d57631e0be866e Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Mon, 29 May 2023 17:10:39 -0500 Subject: [PATCH 136/424] Step 2 ADM1-ASM2d now includes TP balance Changed step 2 to include P balance --- qsdsan/sanunits/_junction_copy.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/qsdsan/sanunits/_junction_copy.py b/qsdsan/sanunits/_junction_copy.py index 08c5eb27..31da32b5 100644 --- a/qsdsan/sanunits/_junction_copy.py +++ b/qsdsan/sanunits/_junction_copy.py @@ -532,7 +532,7 @@ def adm2asm(adm_vals): # Not sure about charge on X_PP, S_Mg, S_K (PHA and PAO would have zero charge) _ions = np.array([S_IN, S_IC, S_IP, X_PP, S_Mg, S_K, S_ac, S_pro, S_bu, S_va]) - # Step 1a: convert biomass into X_S+X_ND and X_P + # Step 1a: convert biomass into X_S+X_ND bio_cod = X_su + X_aa + X_fa + X_c4 + X_pro + X_ac + X_h2 bio_n = sum((adm_vals*adm_i_N)[adm_bio_N_indices]) @@ -577,10 +577,15 @@ def adm2asm(adm_vals): 'all biomass COD into X_S') # Step 1b: convert particulate substrates into X_S + X_ND - xsub_cod = X_c + X_ch + X_pr + X_li - xsub_n = X_c*X_c_i_N + X_pr*X_pr_i_N + xsub_cod = X_ch + X_pr + X_li + xsub_n = X_pr*X_pr_i_N + # COD balance X_S += xsub_cod - X_ND += xsub_n - xsub_cod*X_S_i_N # X_S.i_N should technically be zero + + # N balance + X_S_N += xsub_cod*X_S_i_N + + if X_ND < 0: if isclose(X_ND, 0, rel_tol=rtol, abs_tol=atol): X_ND = 0 else: @@ -594,6 +599,13 @@ def adm2asm(adm_vals): if isclose(S_IN, 0, rel_tol=rtol, abs_tol=atol): S_IN = 0 raise RuntimeError('Not enough nitrogen (X_I + S_IN) to map ' 'all ADM X_I into ASM X_I') + + excess_XIp = X_I * (adm_X_I_i_P - asm_X_I_i_P) + S_IP += excess_XIp + if S_IP < 0: + if isclose(S_IP, 0, rel_tol=rtol, abs_tol=atol): S_IP = 0 + raise RuntimeError('Not enough phosphorous (X_I + S_IN) to map ' + 'all ADM X_I into ASM X_I') # Step 3: map ADM S_I into ASM S_I and S_NH excess_SIn = S_I * (adm_S_I_i_N - asm_S_I_i_N) From 638067add47680803072bc83585c82ee777ae382 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Tue, 30 May 2023 10:36:50 -0500 Subject: [PATCH 137/424] Step 1(a) and 1(b): ADM1-ASM2d interface Modified step 1(a) and 1(b) to include P balance. --- qsdsan/sanunits/_junction_copy.py | 83 +++++++++++++++++++++++++------ 1 file changed, 69 insertions(+), 14 deletions(-) diff --git a/qsdsan/sanunits/_junction_copy.py b/qsdsan/sanunits/_junction_copy.py index 31da32b5..c777283d 100644 --- a/qsdsan/sanunits/_junction_copy.py +++ b/qsdsan/sanunits/_junction_copy.py @@ -500,7 +500,7 @@ def _compile_reactions(self): atol = self.atol cmps_adm = ins.components - X_c_i_N = cmps_adm.X_c.i_N + # N balance X_pr_i_N = cmps_adm.X_pr.i_N S_aa_i_N = cmps_adm.S_aa.i_N adm_X_I_i_N = cmps_adm.X_I.i_N @@ -508,16 +508,26 @@ def _compile_reactions(self): adm_i_N = cmps_adm.i_N adm_bio_N_indices = cmps_adm.indices(('X_su', 'X_aa', 'X_fa', 'X_c4', 'X_pro', 'X_ac', 'X_h2')) - + + # P balance + X_pr_i_P = cmps_adm.X_pr.i_P + adm_X_I_i_P = cmps_adm.X_I.i_P + adm_i_P = cmps_adm.i_P + adm_bio_P_indices = cmps_adm.indices(('X_su', 'X_aa', 'X_fa', + 'X_c4', 'X_pro', 'X_ac', 'X_h2')) + cmps_asm = outs.components - # X_P_i_N = cmps_asm.X_P.i_N + # N balance X_S_i_N = cmps_asm.X_S.i_N S_S_i_N = cmps_asm.S_S.i_N asm_X_I_i_N = cmps_asm.X_I.i_N asm_S_I_i_N = cmps_asm.S_I.i_N - # asm_X_P_i_N = cmps_asm.X_P.i_N asm_ions_idx = cmps_asm.indices(('S_NH', 'S_ALK')) + # P balance + X_S_i_P = cmps_asm.X_S.i_P + asm_X_I_i_P = cmps_asm.X_I.i_P + alpha_IN = self.alpha_IN alpha_IC = self.alpha_IC alpha_vfa = self.alpha_vfa @@ -535,6 +545,7 @@ def adm2asm(adm_vals): # Step 1a: convert biomass into X_S+X_ND bio_cod = X_su + X_aa + X_fa + X_c4 + X_pro + X_ac + X_h2 bio_n = sum((adm_vals*adm_i_N)[adm_bio_N_indices]) + bio_p = sum((adm_vals*adm_i_P)[adm_bio_P_indices]) # There is no X_P (particulate products arising due to biomass decay) # or equivalent component in ASM2d @@ -571,32 +582,75 @@ def adm2asm(adm_vals): bio_n = 0 else: if isclose(X_S_N, bio_n + S_IN, rel_tol=rtol, abs_tol=atol): - S_IN = bio_n = 0 + S_IN = bio_n = 0 else: raise RuntimeError('Not enough nitrogen (S_IN + biomass) to map ' 'all biomass COD into X_S') + # P balance + X_S_P = X_S*X_S_i_P + if X_S_P <= bio_p: + # Here, S_IP is based on the fact that in case of + # deficit bio_N, S_IN is used to compensate for excess X_S_N + # in Nopens at al. 2009 + S_IP += (bio_p - X_S_P) + bio_p = 0 + elif X_S_P <= bio_p + S_IP: + S_IP -= (X_S_P - bio_p) + bio_p = 0 + else: + if isclose(X_S_P, bio_p + S_IP, rel_tol=rtol, abs_tol=atol): + S_IP = bio_p = 0 + else: + raise RuntimeError('Not enough phosphorous (S_IP + biomass) to map ' + 'all biomass COD into X_S') + # Step 1b: convert particulate substrates into X_S + X_ND + + # First we calculate the COD, N, and P content in particulate substrate xsub_cod = X_ch + X_pr + X_li xsub_n = X_pr*X_pr_i_N - # COD balance - X_S += xsub_cod + xsub_p = X_pr*X_pr_i_P - # N balance - X_S_N += xsub_cod*X_S_i_N + # Then we determine the amount of X_S required for COD, N, and P balance + X_S_cod = xsub_cod + X_S_n = xsub_n/X_S_i_N + X_S_p = xsub_p/X_S_i_P + # Identify the limiting component and accordingly form X_S + X_S_add = min(X_S_cod, X_S_n, X_S_p) + X_S += X_S_add + + # Calculate the imbalance in the non-limiting components + if X_S_add == X_S_cod: + def_N = (xsub_n - X_S_add*X_S_i_N) + def_P = (xsub_p - X_S_add*X_S_i_P) + if isclose(def_N, 0, rel_tol=rtol, abs_tol=atol): def_N = 0 + if isclose(def_P, 0, rel_tol=rtol, abs_tol=atol): def_P = 0 + elif X_S_add == X_S_n: + def_COD = (xsub_cod - X_S_add) + def_P = (xsub_p - X_S_add*X_S_i_P) + if isclose(def_COD, 0, rel_tol=rtol, abs_tol=atol): def_COD = 0 + if isclose(def_P, 0, rel_tol=rtol, abs_tol=atol): def_P = 0 + else: + def_COD = (xsub_cod - X_S_add) + def_N = (xsub_n - X_S_add*X_S_i_N) + if isclose(def_COD, 0, rel_tol=rtol, abs_tol=atol): def_COD = 0 + if isclose(def_N, 0, rel_tol=rtol, abs_tol=atol): def_N = 0 + + # How should I balance deficit COD/N/P? + + # Alternatives: + # N, and P through S_IN and S_IP respectively based on precedent + # COD through? - if X_ND < 0: - if isclose(X_ND, 0, rel_tol=rtol, abs_tol=atol): X_ND = 0 - else: - raise RuntimeError('Not enough nitrogen (substrate + excess X_ND) ' - 'to map all particulate substrate COD into X_S') # Step 2: map all X_I from ADM to ASM excess_XIn = X_I * (adm_X_I_i_N - asm_X_I_i_N) S_IN += excess_XIn if S_IN < 0: if isclose(S_IN, 0, rel_tol=rtol, abs_tol=atol): S_IN = 0 + # This seems to be a typo. Should there be an else here? raise RuntimeError('Not enough nitrogen (X_I + S_IN) to map ' 'all ADM X_I into ASM X_I') @@ -604,6 +658,7 @@ def adm2asm(adm_vals): S_IP += excess_XIp if S_IP < 0: if isclose(S_IP, 0, rel_tol=rtol, abs_tol=atol): S_IP = 0 + # This seems to be a typo. Should there be an else here? raise RuntimeError('Not enough phosphorous (X_I + S_IN) to map ' 'all ADM X_I into ASM X_I') From 1ad0f24447b9ab272d8e98a80bb54cc2ac104635 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Wed, 31 May 2023 16:18:54 -0500 Subject: [PATCH 138/424] Made changes in ADM1-ASM2d interface Made several changes for ADM1-ASM2d interface to accommodate COD, TN, and TP balance. --- qsdsan/sanunits/_junction_copy.py | 115 ++++++++++++++++-------------- 1 file changed, 62 insertions(+), 53 deletions(-) diff --git a/qsdsan/sanunits/_junction_copy.py b/qsdsan/sanunits/_junction_copy.py index c777283d..91677e7f 100644 --- a/qsdsan/sanunits/_junction_copy.py +++ b/qsdsan/sanunits/_junction_copy.py @@ -367,10 +367,7 @@ class ADMtoASM(ADMjunction): downstream : stream or str Effluent stream with ASM components. adm1_model : obj - The anaerobic digestion process model (:class:`qsdsan.processes.ADM1`). - bio_to_xs : float - Split of the total biomass COD to slowly biodegradable substrate (X_S), - the rest is assumed to be mapped into X_P. + The anaerobic digestion process model (:class:`qsdsan.processes.ADM1_p_extension`). rtol : float Relative tolerance for COD and TKN balance. atol : float @@ -391,7 +388,7 @@ class ADMtoASM(ADMjunction): `math.isclose ` ''' # User defined values - bio_to_xs = 0.7 + # bio_to_xs = 0.7 (Not using this split since no X_P exists in ASM2d) # Should be constants cod_vfa = np.array([64, 112, 160, 208]) @@ -512,6 +509,7 @@ def _compile_reactions(self): # P balance X_pr_i_P = cmps_adm.X_pr.i_P adm_X_I_i_P = cmps_adm.X_I.i_P + adm_S_I_i_P = cmps_adm.S_I.i_P adm_i_P = cmps_adm.i_P adm_bio_P_indices = cmps_adm.indices(('X_su', 'X_aa', 'X_fa', 'X_c4', 'X_pro', 'X_ac', 'X_h2')) @@ -519,17 +517,20 @@ def _compile_reactions(self): cmps_asm = outs.components # N balance X_S_i_N = cmps_asm.X_S.i_N - S_S_i_N = cmps_asm.S_S.i_N + S_F_i_N = cmps_asm.S_F.i_N + S_A_i_N = cmps_asm.S_A.i_N asm_X_I_i_N = cmps_asm.X_I.i_N asm_S_I_i_N = cmps_asm.S_I.i_N - asm_ions_idx = cmps_asm.indices(('S_NH', 'S_ALK')) + asm_ions_idx = cmps_asm.indices(('S_NH4', 'S_ALK')) # P balance X_S_i_P = cmps_asm.X_S.i_P asm_X_I_i_P = cmps_asm.X_I.i_P + asm_S_I_i_P = cmps_asm.S_I.i_P alpha_IN = self.alpha_IN alpha_IC = self.alpha_IC + alpha_IP = self.alpha_IP alpha_vfa = self.alpha_vfa f_corr = self.balance_cod_tkn @@ -625,32 +626,25 @@ def adm2asm(adm_vals): if X_S_add == X_S_cod: def_N = (xsub_n - X_S_add*X_S_i_N) def_P = (xsub_p - X_S_add*X_S_i_P) - if isclose(def_N, 0, rel_tol=rtol, abs_tol=atol): def_N = 0 - if isclose(def_P, 0, rel_tol=rtol, abs_tol=atol): def_P = 0 + S_IN += def_N + S_IP += def_P elif X_S_add == X_S_n: def_COD = (xsub_cod - X_S_add) def_P = (xsub_p - X_S_add*X_S_i_P) - if isclose(def_COD, 0, rel_tol=rtol, abs_tol=atol): def_COD = 0 - if isclose(def_P, 0, rel_tol=rtol, abs_tol=atol): def_P = 0 + S_A = def_COD # suggested by Jeremy + S_IP += def_P else: def_COD = (xsub_cod - X_S_add) def_N = (xsub_n - X_S_add*X_S_i_N) - if isclose(def_COD, 0, rel_tol=rtol, abs_tol=atol): def_COD = 0 - if isclose(def_N, 0, rel_tol=rtol, abs_tol=atol): def_N = 0 - - # How should I balance deficit COD/N/P? - - # Alternatives: - # N, and P through S_IN and S_IP respectively based on precedent - # COD through? - + S_A = def_COD # suggested by Jeremy + S_IN += def_N # Step 2: map all X_I from ADM to ASM excess_XIn = X_I * (adm_X_I_i_N - asm_X_I_i_N) S_IN += excess_XIn if S_IN < 0: if isclose(S_IN, 0, rel_tol=rtol, abs_tol=atol): S_IN = 0 - # This seems to be a typo. Should there be an else here? + # This seems to be a typo. Shouldn't there be an else here? raise RuntimeError('Not enough nitrogen (X_I + S_IN) to map ' 'all ADM X_I into ASM X_I') @@ -658,52 +652,66 @@ def adm2asm(adm_vals): S_IP += excess_XIp if S_IP < 0: if isclose(S_IP, 0, rel_tol=rtol, abs_tol=atol): S_IP = 0 - # This seems to be a typo. Should there be an else here? - raise RuntimeError('Not enough phosphorous (X_I + S_IN) to map ' + # This seems to be a typo. Shouldn't there be an else here? + raise RuntimeError('Not enough phosphorous (X_I + S_IP) to map ' 'all ADM X_I into ASM X_I') - # Step 3: map ADM S_I into ASM S_I and S_NH + # Step 3: map ADM S_I into ASM S_I and S_NH4 excess_SIn = S_I * (adm_S_I_i_N - asm_S_I_i_N) if excess_SIn > 0: - S_NH = excess_SIn + S_NH4 = excess_SIn else: - S_NH = 0 + S_NH4 = 0 S_IN += excess_SIn if S_IN < 0: if isclose(S_IN, 0, rel_tol=rtol, abs_tol=atol): S_IN = 0 + # This seems to be a typo. Shouldn't there be an else here? raise RuntimeError('Not enough nitrogen (S_I + S_IN) to map ' 'all ADM S_I into ASM S_I') - S_NH += S_IN + S_NH4 += S_IN + + excess_SIp = S_I * (adm_S_I_i_P - asm_S_I_i_P) + if excess_SIp > 0: + S_PO4 = excess_SIp + else: + S_PO4 = 0 + S_IP += excess_SIp + if S_IP < 0: + if isclose(S_IP, 0, rel_tol=rtol, abs_tol=atol): S_IP = 0 + # This seems to be a typo. Shouldn't there be an else here? + raise RuntimeError('Not enough nitrogen (S_I + S_IP) to map ' + 'all ADM S_I into ASM S_I') + S_PO4 += S_IP - # Step 4: map all soluble substrates into S_S and S_ND + # Step 4: map all soluble substrates into S_A and S_F ssub_cod = S_su + S_aa + S_fa + S_va + S_bu + S_pro + S_ac ssub_n = S_aa * S_aa_i_N - if ssub_cod*S_S_i_N <= ssub_n: - S_S = ssub_cod - S_ND = ssub_n - if S_S_i_N != 0: S_ND -= S_S/S_S_i_N # S_S.i_N should technically be zero + + # P balance not required as all the 6 soluble substrates have no P + + # N balance + S_F = ssub_n/S_F_i_N + # COD balance + S_A = ssub_cod - S_F + + if S_A_i_N == 0: + pass else: - if isclose(ssub_cod*S_S_i_N, ssub_n, rel_tol=rtol, abs_tol=atol): - S_S = ssub_cod - S_ND = 0 - else: - raise RuntimeError('Not enough nitrogen to map all soluble ' - 'substrates into ASM S_S') - + # excess N formed subtracted from S_NH + S_NH4 -= (S_A*S_A_i_N) + # Step 6: check COD and TKN balance - # asm_vals = np.array(([ - # S_I, S_S, X_I, X_S, - # 0, 0, # X_BH, X_BA, - # X_P, - # 0, 0, # S_O, S_NO, - # S_NH, S_ND, X_ND, - # 0, 0, # temporary S_ALK, S_N2, - # H2O])) - asm_vals = np.array(([ - S_O2, S_N2, S_NH4, S_NO3, S_PO4, S_F, S_A, - S_I, S_ALK, X_I, X_S, X_H, X_PAO, X_PP, X_PHA, - X_AUT, X_MeOH, X_MeP, H2O])) + 0, 0, # S_O2, S_N2, + S_NH4, + 0, + S_PO4, S_F, S_A, S_I, + 0, # S_ALK, + X_I, X_S, + 0, # X_H, + X_PAO, X_PP, X_PHA, # directly mapped + 0, # X_AUT, + X_MeOH, X_MeP, H2O])) # directly mapped if S_h2 > 0 or S_ch4 > 0: warn('Ignored dissolved H2 or CH4.') @@ -711,8 +719,8 @@ def adm2asm(adm_vals): asm_vals = f_corr(adm_vals, asm_vals) # Step 5: charge balance for alkalinity - S_NH = asm_vals[asm_ions_idx[0]] - S_ALK = (sum(_ions * np.append([alpha_IN, alpha_IC], alpha_vfa)) - S_NH/14)*(-12) + S_NH4 = asm_vals[asm_ions_idx[0]] + S_ALK = (sum(_ions * np.append([alpha_IN, alpha_IC, alpha_IP], alpha_vfa)) - S_NH4/14)*(-12) asm_vals[asm_ions_idx[1]] = S_ALK return asm_vals @@ -721,6 +729,7 @@ def adm2asm(adm_vals): @property def alpha_vfa(self): + # This may need change based on P-extension of ADM1 return 1.0/self.cod_vfa*(-1.0/(1.0 + 10**(self.pKa[3:]-self.pH))) From 67a8a40f2322d95280405b0b098bda6eee70879c Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Thu, 1 Jun 2023 15:54:43 -0500 Subject: [PATCH 139/424] ADM1-ASM2d interface completed Completed transitions and checks on ADM1-ASM2d interface. COD, TN, and TP balance holds. --- qsdsan/sanunits/_junction_copy.py | 53 +++++++++++++++++++++++++------ 1 file changed, 44 insertions(+), 9 deletions(-) diff --git a/qsdsan/sanunits/_junction_copy.py b/qsdsan/sanunits/_junction_copy.py index 91677e7f..bf1415eb 100644 --- a/qsdsan/sanunits/_junction_copy.py +++ b/qsdsan/sanunits/_junction_copy.py @@ -379,6 +379,10 @@ class ADMtoASM(ADMjunction): Alex, J.; Vanrolleghem, P. A. An ASM/ADM Model Interface for Dynamic Plant-Wide Simulation. Water Res. 2009, 43, 1913–1923. + [2] Flores-Alsina, X., Solon, K., Kazadi Mbamba, C., Tait, S., Gernaey, K. V., + Jeppsson, U., & Batstone, D. J. (2016). Modelling phosphorus (P), sulfur (S) + and iron (FE) interactions for dynamic simulations of anaerobic digestion processes. + Water Research, 95, 370–382. See Also -------- :class:`qsdsan.sanunits.ADMjunction` @@ -515,6 +519,7 @@ def _compile_reactions(self): 'X_c4', 'X_pro', 'X_ac', 'X_h2')) cmps_asm = outs.components + # N balance X_S_i_N = cmps_asm.X_S.i_N S_F_i_N = cmps_asm.S_F.i_N @@ -525,9 +530,31 @@ def _compile_reactions(self): # P balance X_S_i_P = cmps_asm.X_S.i_P + S_F_i_P = cmps_asm.S_F.i_P + S_A_i_P = cmps_asm.S_A.i_P asm_X_I_i_P = cmps_asm.X_I.i_P asm_S_I_i_P = cmps_asm.S_I.i_P + # Checks for direct mapping of X_PAO, X_PP, X_PHA + + # Check for X_PAO (measured as COD so i_COD = 1 in both ASM2d and ADM1) + asm_X_PAO_i_N = cmps_asm.X_PAO.i_N + adm_X_PAO_i_N = cmps_adm.X_PAO.i_N + if asm_X_PAO_i_N != adm_X_PAO_i_N: + raise RuntimeError('X_PAO cannot be directly mapped as N content' + f'in asm2d_X_PAO_i_N = {asm_X_PAO_i_N} is not equal to' + f'adm_X_PAO_i_N = {adm_X_PAO_i_N}') + + asm_X_PAO_i_P = cmps_asm.X_PAO.i_P + adm_X_PAO_i_P = cmps_adm.X_PAO.i_P + if asm_X_PAO_i_P != adm_X_PAO_i_P: + raise RuntimeError('X_PAO cannot be directly mapped as P content' + f'in asm2d_X_PAO_i_P = {asm_X_PAO_i_P} is not equal to' + f'adm_X_PAO_i_P = {adm_X_PAO_i_P}') + + # Checks not required for X_PP as measured as P in both, with i_COD = i_N = 0 + # Checks not required for X_PHA as measured as COD in both, with i_N = i_P = 0 + alpha_IN = self.alpha_IN alpha_IC = self.alpha_IC alpha_IP = self.alpha_IP @@ -687,18 +714,24 @@ def adm2asm(adm_vals): ssub_cod = S_su + S_aa + S_fa + S_va + S_bu + S_pro + S_ac ssub_n = S_aa * S_aa_i_N - # P balance not required as all the 6 soluble substrates have no P + # P balance not required as all the 7 soluble substrates have no P # N balance S_F = ssub_n/S_F_i_N # COD balance - S_A = ssub_cod - S_F + S_A += ssub_cod - S_F - if S_A_i_N == 0: - pass - else: - # excess N formed subtracted from S_NH - S_NH4 -= (S_A*S_A_i_N) + # Technically both S_A_i_N and S_A_i_P would be 0 + if S_A_i_N > 0 or S_A_i_P > 0: + # excess N formed subtracted from S_NH4 + S_NH4 -= (ssub_cod - S_F)*S_A_i_N + # excess P (due to S_A) formed subtracted from S_PO4 + S_PO4 -= (ssub_cod - S_F)*S_A_i_P + + if S_F_i_P > 0: + # excess P (due to S_F) formed subtracted from S_PO4 + S_PO4 -= S_F*S_F_i_P + # Step 6: check COD and TKN balance asm_vals = np.array(([ @@ -729,12 +762,13 @@ def adm2asm(adm_vals): @property def alpha_vfa(self): - # This may need change based on P-extension of ADM1 + # This may need change based on P-extension of ADM1 (ask Joy?) return 1.0/self.cod_vfa*(-1.0/(1.0 + 10**(self.pKa[3:]-self.pH))) # %% +# While using this interface X_I.i_N in ASM2d should be 0.06, instead of 0.02. class ASMtoADM(ADMjunction): ''' Interface unit to convert activated sludge model (ASM) components @@ -847,7 +881,8 @@ def balance_cod_tkn_tp(self, asm_vals, adm_vals): f'influent TP is {asm_tp}\n ' f'effluent TP is {adm_tp} or {_adm_tp}. ' f'influent TKN is {asm_tkn}\n ' - f'effluent TKN is {adm_tkn} or {adm_tkn*(1+dtkn)}. ') + f'effluent TKN is {adm_tkn} or {adm_tkn*(1+dtkn)}. ' + 'To balance TKN please ensure ASM2d(X_I.i_N) = ADM1(X_I.i_N)') return adm_vals elif cod_bl and tkn_bl: if tp_bl: From 1fbe3f84e96a64523c8bf3bddd2d3a80afc89f1f Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Fri, 2 Jun 2023 13:59:37 -0500 Subject: [PATCH 140/424] Modified charge balance (ADM1-ASM2d) Modified charge balance to accommodate ADM1 and ASM2d. --- qsdsan/sanunits/_junction_copy.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/qsdsan/sanunits/_junction_copy.py b/qsdsan/sanunits/_junction_copy.py index bf1415eb..b8d7077a 100644 --- a/qsdsan/sanunits/_junction_copy.py +++ b/qsdsan/sanunits/_junction_copy.py @@ -526,7 +526,7 @@ def _compile_reactions(self): S_A_i_N = cmps_asm.S_A.i_N asm_X_I_i_N = cmps_asm.X_I.i_N asm_S_I_i_N = cmps_asm.S_I.i_N - asm_ions_idx = cmps_asm.indices(('S_NH4', 'S_ALK')) + asm_ions_idx = cmps_asm.indices(('S_NH4', 'S_A', 'S_NO3', 'S_PO4', 'X_PP', 'S_ALK')) # P balance X_S_i_P = cmps_asm.X_S.i_P @@ -633,7 +633,7 @@ def adm2asm(adm_vals): raise RuntimeError('Not enough phosphorous (S_IP + biomass) to map ' 'all biomass COD into X_S') - # Step 1b: convert particulate substrates into X_S + X_ND + # Step 1b: convert particulate substrates into X_S # First we calculate the COD, N, and P content in particulate substrate xsub_cod = X_ch + X_pr + X_li @@ -753,8 +753,13 @@ def adm2asm(adm_vals): # Step 5: charge balance for alkalinity S_NH4 = asm_vals[asm_ions_idx[0]] - S_ALK = (sum(_ions * np.append([alpha_IN, alpha_IC, alpha_IP], alpha_vfa)) - S_NH4/14)*(-12) - asm_vals[asm_ions_idx[1]] = S_ALK + S_A = asm_vals[asm_ions_idx[1]] + S_NO3 = asm_vals[asm_ions_idx[2]] + S_PO4 = asm_vals[asm_ions_idx[3]] + X_PP = asm_vals[asm_ions_idx[4]] + # Need to include S_K, S_Mg in the charge balance (Maybe ask Joy/Jeremy) + S_ALK = (sum(_ions * np.append([alpha_IN, alpha_IC, alpha_IP], alpha_vfa)) - (S_NH4/14 - S_A/64 - S_NO3/14 -1.5*S_PO4/31 - X_PP/31))*(-12) + asm_vals[asm_ions_idx[5]] = S_ALK return asm_vals @@ -1029,9 +1034,9 @@ def asm2adm(asm_vals): bioP = X_H*X_H_i_P + X_AUT*X_AUT_i_P # To be used in Step 2 - S_ND_asm1 = S_F*S_F_i_N #S_ND (in asm1) equals the N content in S_F (Joy) + S_ND_asm1 = S_F*S_F_i_N #S_ND (in asm1) equals the N content in S_F # To be used in Step 3 - X_ND_asm1 = X_S*X_S_i_N #X_ND (in asm1) equals the N content in X_S (Joy) + X_ND_asm1 = X_S*X_S_i_N #X_ND (in asm1) equals the N content in X_S # To be used in Step 5 (a) X_S_P = X_S*X_S_i_P # To be used in Step 5 (b) From 9f0128dcfb75b83a37a95dde2bd780174cd16832 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Fri, 16 Jun 2023 13:27:48 -0500 Subject: [PATCH 141/424] Typo in design function of primary clarifier Corrected typo --- qsdsan/sanunits/_clarifier.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index 330162c0..b8b8d777 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -557,7 +557,7 @@ class PrimaryClarifier(SanUnit): The ratio of sludge to primary influent. The default is 0.007, based on IWA report.[1] f_corr : float Dimensionless correction factor for removal efficiency in the primary clarifier.[1] - oveflow_rate : float + overflow_rate : float The design overflow rate in the primary sedimentation tank. Default value taken from sample design problem. Unit in m/hr[2] @@ -721,13 +721,6 @@ def _run(self): Cs = Zs + Xs of.set_flow(Ce,'kg/hr') uf.set_flow(Cs,'kg/hr') - - def _design(self): - - design = self.design_results - design['Volume'] = 24*self._HRT*self.mixed.get_total_flow('m3/hr') #in m3 - design['Area'] = self.mixed.get_total_flow('m3/hr')/self.overflow_rate #in m2 - design['Length'] = design['Volume']/design['Area'] #in m def _init_state(self): # if multiple wastestreams exist then concentration and total inlow @@ -782,4 +775,12 @@ def yt(t, QC_ins, dQC_ins): _dstate[:-1] = C_dot _update_state() _update_dstate() - self._AE = yt \ No newline at end of file + self._AE = yt + + + def _design(self): + + design = self.design_results + design['Volume'] = 24*self._HRT*self.mixed.get_total_flow('m3/hr') #in m3 + design['Area'] = self.mixed.get_total_flow('m3/hr')/self.overflow_rate #in m2 + design['Length'] = design['Volume']/design['Area'] #in m \ No newline at end of file From c8af3e26cac1ffe6cafcf0cfe605f1f5e4e09eed Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Sun, 2 Jul 2023 11:17:24 -0500 Subject: [PATCH 142/424] Design of clarifier Design of Primary clarifier --- qsdsan/sanunits/_clarifier.py | 83 +++++++++++++++++++++++++++++++---- 1 file changed, 75 insertions(+), 8 deletions(-) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index 330162c0..08220bd5 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -721,13 +721,6 @@ def _run(self): Cs = Zs + Xs of.set_flow(Ce,'kg/hr') uf.set_flow(Cs,'kg/hr') - - def _design(self): - - design = self.design_results - design['Volume'] = 24*self._HRT*self.mixed.get_total_flow('m3/hr') #in m3 - design['Area'] = self.mixed.get_total_flow('m3/hr')/self.overflow_rate #in m2 - design['Length'] = design['Volume']/design['Area'] #in m def _init_state(self): # if multiple wastestreams exist then concentration and total inlow @@ -782,4 +775,78 @@ def yt(t, QC_ins, dQC_ins): _dstate[:-1] = C_dot _update_state() _update_dstate() - self._AE = yt \ No newline at end of file + self._AE = yt + + _units = { + 'Cylinderical volume': 'm3', + 'Cylinderical depth': 'm', + 'Cylinderical diameter': 'm', + + 'Conical radius': 'm', + 'Conical depth': 'm', + 'Conical volume': 'm3', + + 'Volume': 'm3', + 'Center feed depth': 'm', + 'Upflow velocity': 'm/hr', + 'Center feed diameter': 'm', + 'Concrete': 'kg', + 'Stainless steel': 'kg' + } + + def _design(self): + + self.mixed.mix_from(self.ins) + + D = self.design_results + + total_volume = 24*self._HRT*self.mixed.get_total_flow('m3/hr') #in m3 + working_volume = total_volume/0.8 # Assume 80% working volume + + D['Cylinderical volume'] = working_volume + # Sidewater depth of a cylinderical clarifier lies between 2.5-5m + D['Cylinderical depth'] = 3 # in m + # The tank diameter can lie anywhere between 3 m to 100 m + D['Cylinderical diameter'] = (4*D['Cylinderical Volume']/(3.14*D['Cylinderical Depth']))**(1/2) # in m + + D['Conical radius'] = D['Cylinderical Diameter']/2 + # The slope of the bottom conical floor lies between 1:10 to 1:12 + D['Conical depth'] = D['Conical radius']/10 + D['Conical volume'] = (3.14/3)*(D['Conical radius']**2)*D['Conical depth'] + + D['Volume'] = D['Cylinderical volume'] + D['Conical volume'] + + # Primary clarifiers can be center feed or peripheral feed. The design here is for the more + # commonly deployed center feed. + + # Depth of the center feed lies between 30-75% of sidewater depth + D['Center feed depth'] = 0.5*D['Cylinderical depth'] + # Typical conventional feed wells are designed for an average downflow velocity + # of 10-13 mm/s and maximum velocity of 25-30 mm/s + peak_flow_safety_factor = 2.5 # assumed based on average and maximum velocities + upflow_velocity = 43.2 # in m/hr (converted from 12 mm/sec) + D['Upflow velocity'] = upflow_velocity*peak_flow_safety_factor # in m/hr + Center_feed_area = self.mixed.get_total_flow('m3/hr')/D['Upflow velocity'] # in m2 + D['Center feed diameter'] = ((4*Center_feed_area)/3.14)**(1/2) # Sanity check: Diameter of the center feed lies between 15-25% of tank diameter + + # Amount of concrete required + thickness_concrete_wall = 3 # in m (!! NEED A RELIABLE SOURCE !!) + inner_diameter = D['Cylinderical diameter'] + outer_diameter = inner_diameter + thickness_concrete_wall + volume_cylindercal_wall = (3.14*D['Cylinderical depth']/4)*(outer_diameter**2 - inner_diameter**2) + volume_conical_wall = (3.14/3)*(D['Conical depth']/4)*(outer_diameter**2 - inner_diameter**2) + Density_concrete = 2400 # in kg/m3 + D['Concrete'] = (volume_cylindercal_wall + volume_conical_wall)*Density_concrete # in kg + + # Amount of metal required for center feed + thickness_metal_wall = 0.5 # in m (!! NEED A RELIABLE SOURCE !!) + inner_diameter_center_feed = D['Center feed diameter'] + outer_diameter_center_feed = inner_diameter_center_feed + thickness_metal_wall + volume_center_feed = (3.14*D['Center feed depth']/4)*(outer_diameter_center_feed**2 - inner_diameter_center_feed **2) + Density_stainless_steel = 7500 # in kg/m3 + D['Stainless steel'] = volume_center_feed*Density_stainless_steel # in kg + + + + + \ No newline at end of file From 80252f1e2e735b686a1205347ef3669552c393ce Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Sun, 2 Jul 2023 19:20:15 -0500 Subject: [PATCH 143/424] Secondary clarifier design function Added design function to secondary clarifier. For the same purpose, added new property 'height'. --- qsdsan/sanunits/_clarifier.py | 78 +++++++++++++++++++++++++++++++++-- 1 file changed, 75 insertions(+), 3 deletions(-) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index 03c44bd9..4d5bad96 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -93,6 +93,7 @@ def __init__(self, ID='', ins=None, outs=(), thermo=None, rh=5.76e-4, rp=2.86e-3, fns=2.28e-3, isdynamic=True, **kwargs): SanUnit.__init__(self, ID, ins, outs, thermo, init_with, isdynamic=isdynamic) + self._h = height self._Qras = underflow self._Qwas = wastage self._sludge = WasteStream() @@ -116,6 +117,14 @@ def __init__(self, ID='', ins=None, outs=(), thermo=None, for attr, value in kwargs.items(): setattr(self, attr, value) + @property + def height(self): + '''[float] Height of the clarifier in m.''' + return self._h + + @height.setter + def height(self, h): + self._h = h @property def underflow(self): @@ -434,11 +443,74 @@ def dy_dt(t, QC_ins, QC, dQC_ins): _update_dstate() self._ODE = dy_dt - + + + _units = { + 'Cylinderical volume': 'm3', + 'Cylinderical depth': 'm', + 'Cylinderical diameter': 'm', + + 'Conical radius': 'm', + 'Conical depth': 'm', + 'Conical volume': 'm3', + + 'Volume': 'm3', + 'Center feed depth': 'm', + 'Upflow velocity': 'm/hr', + 'Center feed diameter': 'm', + 'Concrete': 'kg', + 'Stainless steel': 'kg' + } + def _design(self): - pass - + + self.mixed.mix_from(self.ins) + + D = self.design_results + + D['Cylinderical volume'] = self._V # in m3 + # Sidewater depth of a cylinderical clarifier lies between 2.5-5m + D['Cylinderical depth'] = self._h # in m + # The tank diameter can lie anywhere between 3 m to 100 m + D['Cylinderical diameter'] = (4*D['Cylinderical Volume']/(3.14*D['Cylinderical Depth']))**(1/2) # in m + + D['Conical radius'] = D['Cylinderical Diameter']/2 + # The slope of the bottom conical floor lies between 1:10 to 1:12 + D['Conical depth'] = D['Conical radius']/10 + D['Conical volume'] = (3.14/3)*(D['Conical radius']**2)*D['Conical depth'] + + D['Volume'] = D['Cylinderical volume'] + D['Conical volume'] + + # Primary clarifiers can be center feed or peripheral feed. The design here is for the more + # commonly deployed center feed. + + # Depth of the center feed lies between 30-75% of sidewater depth + D['Center feed depth'] = 0.5*D['Cylinderical depth'] + # Typical conventional feed wells are designed for an average downflow velocity + # of 10-13 mm/s and maximum velocity of 25-30 mm/s + peak_flow_safety_factor = 2.5 # assumed based on average and maximum velocities + upflow_velocity = 43.2 # in m/hr (converted from 12 mm/sec) + D['Upflow velocity'] = upflow_velocity*peak_flow_safety_factor # in m/hr + Center_feed_area = self.mixed.get_total_flow('m3/hr')/D['Upflow velocity'] # in m2 + D['Center feed diameter'] = ((4*Center_feed_area)/3.14)**(1/2) # Sanity check: Diameter of the center feed lies between 15-25% of tank diameter + # Amount of concrete required + thickness_concrete_wall = 3 # in m (!! NEED A RELIABLE SOURCE !!) + inner_diameter = D['Cylinderical diameter'] + outer_diameter = inner_diameter + thickness_concrete_wall + volume_cylindercal_wall = (3.14*D['Cylinderical depth']/4)*(outer_diameter**2 - inner_diameter**2) + volume_conical_wall = (3.14/3)*(D['Conical depth']/4)*(outer_diameter**2 - inner_diameter**2) + Density_concrete = 2400 # in kg/m3 + D['Concrete'] = (volume_cylindercal_wall + volume_conical_wall)*Density_concrete # in kg + + # Amount of metal required for center feed + thickness_metal_wall = 0.5 # in m (!! NEED A RELIABLE SOURCE !!) + inner_diameter_center_feed = D['Center feed diameter'] + outer_diameter_center_feed = inner_diameter_center_feed + thickness_metal_wall + volume_center_feed = (3.14*D['Center feed depth']/4)*(outer_diameter_center_feed**2 - inner_diameter_center_feed **2) + Density_stainless_steel = 7500 # in kg/m3 + D['Stainless steel'] = volume_center_feed*Density_stainless_steel # in kg + class IdealClarifier(SanUnit): _N_ins = 1 From 35eb24c25d45456a7ec0d4b8e68ab0449b2f6b59 Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Sun, 2 Jul 2023 20:53:08 -0500 Subject: [PATCH 144/424] TEA default parameters (clarifier) Adding default F_BM and lifetime values for pump, and metals. --- qsdsan/sanunits/_clarifier.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index 4d5bad96..a297520b 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -20,6 +20,18 @@ 'IdealClarifier', 'PrimaryClarifier') +F_BM_pump = 1.18*(1+0.007/100) # 0.007 is for miscellaneous costs +default_F_BM = { + 'Pumps': F_BM_pump, + 'Pump building': F_BM_pump, + } + +default_equipment_lifetime = { + 'Pumps': 15, + 'Pump pipe stainless steel': 15, + 'Pump stainless steel': 15, + } + def _settling_flux(X, v_max, v_max_practical, X_min, rh, rp, n0): X_star = npmax(X-X_min, n0) @@ -917,4 +929,9 @@ def _design(self): outer_diameter_center_feed = inner_diameter_center_feed + thickness_metal_wall volume_center_feed = (3.14*D['Center feed depth']/4)*(outer_diameter_center_feed**2 - inner_diameter_center_feed **2) Density_stainless_steel = 7500 # in kg/m3 - D['Stainless steel'] = volume_center_feed*Density_stainless_steel # in kg \ No newline at end of file + D['Stainless steel'] = volume_center_feed*Density_stainless_steel # in kg + + def _cost(self): + self.baseline_purchase_costs['Tank'] = \ + 3 * self.design_results['Stainless steel'] + self.power_utility.consumption = 0.1 * self.outs[0].F_vol \ No newline at end of file From 6bac05478c39a169269173d2ed88c83cd6a9c5c0 Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Thu, 6 Jul 2023 14:08:30 -0500 Subject: [PATCH 145/424] Design_pump added Added design_pump function for pumping of influents. Used it in the main design function. The edits may seem weird as they were made in another desktop and copied here. --- qsdsan/sanunits/_clarifier.py | 215 +++++++++++++++++++++------------- 1 file changed, 132 insertions(+), 83 deletions(-) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index a297520b..c4369ae0 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -16,6 +16,9 @@ from .. import SanUnit, WasteStream import numpy as np +from ..sanunits import WWTpump +from ..utils import auom, calculate_excavation_volume + __all__ = ('FlatBottomCircularClarifier', 'IdealClarifier', 'PrimaryClarifier') @@ -316,20 +319,20 @@ def _update_state(self): X_e = arr[-n] * X_composition C_s = Z + arr[-1] * X_composition eff, ras, was = self._outs - if eff.isproduct() and eff.state is None: + if eff.isproduct() and eff.state is None: eff.state = np.append(Z+X_e, Q_e) else: eff.state[:-1] = Z+X_e # not sure if this works for a setter eff.state[-1] = Q_e #!!! might need to enable dynamic sludge volume flows - if ras.isproduct() and ras.state is None: + if ras.isproduct() and ras.state is None: ras.state = np.append(C_s, self._Qras) - else: + else: ras.state[:-1] = C_s ras.state[-1] = self._Qras - if was.isproduct() and was.state is None: + if was.isproduct() and was.state is None: was.state = np.append(C_s, self._Qwas) - else: + else: was.state[:-1] = C_s was.state[-1] = self._Qwas @@ -345,19 +348,19 @@ def _update_dstate(self): dC_e = dZ + arr[-n] * X_composition + dX_composition * TSS_e dC_s = dZ + arr[-1] * X_composition + dX_composition * TSS_s eff, ras, was = self._outs - if eff.isproduct() and eff.dstate is None: + if eff.isproduct() and eff.dstate is None: eff.dstate = np.append(dC_e, dQ) else: eff.dstate[:-1] = dC_e # not sure if this works for a setter eff.dstate[-1] = dQ #!!! might need to enable dynamic sludge volume flows - if ras.isproduct() and ras.dstate is None: + if ras.isproduct() and ras.dstate is None: ras.dstate = np.append(dC_s, 0.) - else: + else: ras.dstate[:-1] = dC_s - if was.isproduct() and was.dstate is None: + if was.isproduct() and was.dstate is None: was.dstate = np.append(dC_s, 0.) - else: + else: was.dstate[:-1] = dC_s def _run(self): @@ -410,7 +413,7 @@ def _compile_ODE(self): rh_arr = np.full_like(nzeros, self._rh) rp_arr = np.full_like(nzeros, self._rp) func_vx = lambda x_arr, xmin_arr : _settling_flux(x_arr, vmax_arr, vmaxp_arr, xmin_arr, rh_arr, rp_arr, nzeros) - + A, hj, V = self._A, self._hj, self._V A_arr = np.full_like(nzeros, A) hj_arr = np.full_like(nzeros, hj) @@ -455,17 +458,17 @@ def dy_dt(t, QC_ins, QC, dQC_ins): _update_dstate() self._ODE = dy_dt - - + + _units = { 'Cylinderical volume': 'm3', 'Cylinderical depth': 'm', 'Cylinderical diameter': 'm', - + 'Conical radius': 'm', 'Conical depth': 'm', 'Conical volume': 'm3', - + 'Volume': 'm3', 'Center feed depth': 'm', 'Upflow velocity': 'm/hr', @@ -473,29 +476,29 @@ def dy_dt(t, QC_ins, QC, dQC_ins): 'Concrete': 'kg', 'Stainless steel': 'kg' } - + def _design(self): - + self.mixed.mix_from(self.ins) - + D = self.design_results - + D['Cylinderical volume'] = self._V # in m3 # Sidewater depth of a cylinderical clarifier lies between 2.5-5m - D['Cylinderical depth'] = self._h # in m + D['Cylinderical depth'] = self._h # in m # The tank diameter can lie anywhere between 3 m to 100 m D['Cylinderical diameter'] = (4*D['Cylinderical Volume']/(3.14*D['Cylinderical Depth']))**(1/2) # in m - + D['Conical radius'] = D['Cylinderical Diameter']/2 # The slope of the bottom conical floor lies between 1:10 to 1:12 D['Conical depth'] = D['Conical radius']/10 D['Conical volume'] = (3.14/3)*(D['Conical radius']**2)*D['Conical depth'] - + D['Volume'] = D['Cylinderical volume'] + D['Conical volume'] - + # Primary clarifiers can be center feed or peripheral feed. The design here is for the more # commonly deployed center feed. - + # Depth of the center feed lies between 30-75% of sidewater depth D['Center feed depth'] = 0.5*D['Cylinderical depth'] # Typical conventional feed wells are designed for an average downflow velocity @@ -506,23 +509,23 @@ def _design(self): Center_feed_area = self.mixed.get_total_flow('m3/hr')/D['Upflow velocity'] # in m2 D['Center feed diameter'] = ((4*Center_feed_area)/3.14)**(1/2) # Sanity check: Diameter of the center feed lies between 15-25% of tank diameter - # Amount of concrete required - thickness_concrete_wall = 3 # in m (!! NEED A RELIABLE SOURCE !!) + # Amount of concrete required + thickness_concrete_wall = 3 # in m (!! NEED A RELIABLE SOURCE !!) inner_diameter = D['Cylinderical diameter'] outer_diameter = inner_diameter + thickness_concrete_wall volume_cylindercal_wall = (3.14*D['Cylinderical depth']/4)*(outer_diameter**2 - inner_diameter**2) volume_conical_wall = (3.14/3)*(D['Conical depth']/4)*(outer_diameter**2 - inner_diameter**2) Density_concrete = 2400 # in kg/m3 D['Concrete'] = (volume_cylindercal_wall + volume_conical_wall)*Density_concrete # in kg - + # Amount of metal required for center feed - thickness_metal_wall = 0.5 # in m (!! NEED A RELIABLE SOURCE !!) + thickness_metal_wall = 0.5 # in m (!! NEED A RELIABLE SOURCE !!) inner_diameter_center_feed = D['Center feed diameter'] outer_diameter_center_feed = inner_diameter_center_feed + thickness_metal_wall volume_center_feed = (3.14*D['Center feed depth']/4)*(outer_diameter_center_feed**2 - inner_diameter_center_feed **2) Density_stainless_steel = 7500 # in kg/m3 D['Stainless steel'] = volume_center_feed*Density_stainless_steel # in kg - + class IdealClarifier(SanUnit): _N_ins = 1 @@ -621,9 +624,9 @@ def _run(self): def _design(self): pass - + class PrimaryClarifier(SanUnit): - + """ A Primary clarifier based on BSM2 Layout. [1] @@ -632,17 +635,17 @@ class PrimaryClarifier(SanUnit): ID : str ID for the clarifier. The default is ''. ins : class:`WasteStream` - Influent to the clarifier. Expected number of influent is 3. + Influent to the clarifier. Expected number of influent is 3. outs : class:`WasteStream` Treated effluent and sludge. Hydraulic Retention time : float Hydraulic Retention Time in days. The default is 0.04268 days, based on IWA report.[1] ratio_uf : float - The ratio of sludge to primary influent. The default is 0.007, based on IWA report.[1] + The ratio of sludge to primary influent. The default is 0.007, based on IWA report.[1] f_corr : float Dimensionless correction factor for removal efficiency in the primary clarifier.[1] overflow_rate : float - The design overflow rate in the primary sedimentation tank. + The design overflow rate in the primary sedimentation tank. Default value taken from sample design problem. Unit in m/hr[2] Examples @@ -715,20 +718,25 @@ class PrimaryClarifier(SanUnit): TN : 19950.5 mg/L TP : 290.7 mg/L TK : 49.2 mg/L - + References ---------- .. [1] Gernaey, Krist V., Ulf Jeppsson, Peter A. Vanrolleghem, and John B. Copp. Benchmarking of control strategies for wastewater treatment plants. IWA publishing, 2014. - [2] Metcalf, Leonard, Harrison P. Eddy, and Georg Tchobanoglous. Wastewater + [2] Metcalf, Leonard, Harrison P. Eddy, and Georg Tchobanoglous. Wastewater engineering: treatment, disposal, and reuse. Vol. 4. New York: McGraw-Hill, 1991. """ + _N_ins = 3 _N_outs = 2 _ins_size_is_fixed = False + # Costs + wall_concrete_unit_cost = 650 / 0.765 # $/m3, 0.765 is to convert from $/yd3 + stainless_steel_unit_cost=1.8 # $/kg (Taken from Joy's METAB code) https://www.alibaba.com/product-detail/brushed-stainless-steel-plate-304l-stainless_1600391656401.html?spm=a2700.details.0.0.230e67e6IKwwFd + def __init__(self, ID='', ins=None, outs=(), thermo=None, - isdynamic=False, init_with='WasteStream', Hydraulic_Retention_Time=0.04268, + isdynamic=False, init_with='WasteStream', Hydraulic_Retention_Time=0.04268, ratio_uf=0.007, f_corr=0.65, F_BM_default=None, overflow_rate=40, **kwargs): SanUnit.__init__(self, ID, ins, outs, thermo, isdynamic=isdynamic, @@ -738,7 +746,7 @@ def __init__(self, ID='', ins=None, outs=(), thermo=None, self.f_corr = f_corr self.overflow_rate = overflow_rate #in m3/(m2*day) self.mixed = WasteStream('mixed') - + @property def Hydraulic_Retention_Time(self): '''The Hydraulic Retention time in days.''' @@ -746,9 +754,9 @@ def Hydraulic_Retention_Time(self): @Hydraulic_Retention_Time.setter def Hydraulic_Retention_Time(self, HRT): - if HRT is not None: + if HRT is not None: self._HRT = HRT - else: + else: raise ValueError('HRT expected from user') @property @@ -763,7 +771,7 @@ def ratio_uf(self, r): self._r = r else: raise ValueError('Sludge to influent ratio expected from user') - + @property def f_corr(self): return self._corr @@ -776,44 +784,44 @@ def f_corr(self, corr): self._corr = corr else: raise ValueError('correction factor expected from user') - + def _f_i(self): xcod = self.mixed.composite('COD', particle_size='x') fx = xcod/self.mixed.COD - + corr = self._corr HRT = self._HRT n_COD = corr*(2.88*fx - 0.118)*(1.45 + 6.15*np.log(HRT*24*60)) f_i = 1 - (n_COD/100) return f_i - + def _run(self): uf, of = self.outs cmps = self.components self.mixed.mix_from(self.ins) - + r = self._r f_i = self._f_i() - + Xs = (1 - f_i)*self.mixed.mass*cmps.x Xe = (f_i)*self.mixed.mass*cmps.x - + Zs = r*self.mixed.mass*cmps.s Ze = (1-r)*self.mixed.mass*cmps.s - - Ce = Ze + Xe + + Ce = Ze + Xe Cs = Zs + Xs of.set_flow(Ce,'kg/hr') uf.set_flow(Cs,'kg/hr') - + def _init_state(self): - # if multiple wastestreams exist then concentration and total inlow - # would be calculated assumping perfect mixing + # if multiple wastestreams exist then concentration and total inlow + # would be calculated assumping perfect mixing Qs = self._ins_QC[:,-1] Cs = self._ins_QC[:,:-1] self._state = np.append(Qs @ Cs / Qs.sum(), Qs.sum()) self._dstate = self._state * 0. - + uf, of = self.outs s_flow = uf.F_vol/(uf.F_vol+of.F_vol) denominator = uf.mass + of.mass @@ -821,9 +829,9 @@ def _init_state(self): s = uf.mass/denominator self._sludge = np.append(s/s_flow, s_flow) self._effluent = np.append((1-s)/(1-s_flow), 1-s_flow) - + def _update_state(self): - '''updates conditions of output stream based on conditions of the Primary Clarifier''' + '''updates conditions of output stream based on conditions of the Primary Clarifier''' self._outs[0].state = self._sludge * self._state self._outs[1].state = self._effluent * self._state @@ -844,7 +852,7 @@ def _compile_AE(self): _update_state = self._update_state _update_dstate = self._update_dstate def yt(t, QC_ins, dQC_ins): - #Because there are multiple inlets + #Because there are multiple inlets Q_ins = QC_ins[:, -1] C_ins = QC_ins[:, :-1] dQ_ins = dQC_ins[:, -1] @@ -861,49 +869,50 @@ def yt(t, QC_ins, dQC_ins): _update_dstate() self._AE = yt - + _units = { 'Cylinderical volume': 'm3', 'Cylinderical depth': 'm', 'Cylinderical diameter': 'm', - + 'Conical radius': 'm', 'Conical depth': 'm', 'Conical volume': 'm3', - + 'Volume': 'm3', 'Center feed depth': 'm', 'Upflow velocity': 'm/hr', 'Center feed diameter': 'm', - 'Concrete': 'kg', + 'Volume of concrete wall': 'm3', 'Stainless steel': 'kg' } - + + def _design(self): - + self.mixed.mix_from(self.ins) - + D = self.design_results - + total_volume = 24*self._HRT*self.mixed.get_total_flow('m3/hr') #in m3 - working_volume = total_volume/0.8 # Assume 80% working volume - + working_volume = total_volume/0.8 # Assume 80% working volume + D['Cylinderical volume'] = working_volume # Sidewater depth of a cylinderical clarifier lies between 2.5-5m - D['Cylinderical depth'] = 3 # in m + D['Cylinderical depth'] = 3 # in m # The tank diameter can lie anywhere between 3 m to 100 m D['Cylinderical diameter'] = (4*D['Cylinderical Volume']/(3.14*D['Cylinderical Depth']))**(1/2) # in m - + D['Conical radius'] = D['Cylinderical Diameter']/2 # The slope of the bottom conical floor lies between 1:10 to 1:12 D['Conical depth'] = D['Conical radius']/10 D['Conical volume'] = (3.14/3)*(D['Conical radius']**2)*D['Conical depth'] - + D['Volume'] = D['Cylinderical volume'] + D['Conical volume'] - + # Primary clarifiers can be center feed or peripheral feed. The design here is for the more # commonly deployed center feed. - + # Depth of the center feed lies between 30-75% of sidewater depth D['Center feed depth'] = 0.5*D['Cylinderical depth'] # Typical conventional feed wells are designed for an average downflow velocity @@ -914,24 +923,64 @@ def _design(self): Center_feed_area = self.mixed.get_total_flow('m3/hr')/D['Upflow velocity'] # in m2 D['Center feed diameter'] = ((4*Center_feed_area)/3.14)**(1/2) # Sanity check: Diameter of the center feed lies between 15-25% of tank diameter - # Amount of concrete required - thickness_concrete_wall = 3 # in m (!! NEED A RELIABLE SOURCE !!) + # Amount of concrete required + thickness_concrete_wall = 3 # in m (!! NEED A RELIABLE SOURCE !!) inner_diameter = D['Cylinderical diameter'] outer_diameter = inner_diameter + thickness_concrete_wall volume_cylindercal_wall = (3.14*D['Cylinderical depth']/4)*(outer_diameter**2 - inner_diameter**2) volume_conical_wall = (3.14/3)*(D['Conical depth']/4)*(outer_diameter**2 - inner_diameter**2) - Density_concrete = 2400 # in kg/m3 - D['Concrete'] = (volume_cylindercal_wall + volume_conical_wall)*Density_concrete # in kg - + D['Volume of concrete wall'] = volume_cylindercal_wall + volume_conical_wall # in m3 + # Amount of metal required for center feed - thickness_metal_wall = 0.5 # in m (!! NEED A RELIABLE SOURCE !!) + thickness_metal_wall = 0.5 # in m (!! NEED A RELIABLE SOURCE !!) inner_diameter_center_feed = D['Center feed diameter'] outer_diameter_center_feed = inner_diameter_center_feed + thickness_metal_wall volume_center_feed = (3.14*D['Center feed depth']/4)*(outer_diameter_center_feed**2 - inner_diameter_center_feed **2) - Density_stainless_steel = 7500 # in kg/m3 - D['Stainless steel'] = volume_center_feed*Density_stainless_steel # in kg - + density_ss = 7930 # kg/m3, 18/8 Chromium + D['Stainless steel'] = volume_center_feed*density_ss # in kg + + # Pumps + pipe, pumps = self._design_pump() + D['Pump pipe stainless steel'] = pipe + D['Pump stainless steel'] = pumps + + def _design_pump(self): + + ID, pumps = self.ID, self.pumps + + type_dct = dict.fromkeys(pumps, '') + inputs_dct = dict.fromkeys(pumps, (1,)) + + for i in pumps: + if hasattr(self, f'{i}_pump'): + p = getattr(self, f'{i}_pump') + setattr(p, 'add_inputs', inputs_dct[i]) + else: + ID = f'{ID}_{i}' + capacity_factor=1 + pump = WWTpump( + ID=ID, ins=self.ins[i], pump_type=type_dct[i], + Q_mgd=None, add_inputs=inputs_dct[i], + capacity_factor=capacity_factor, + include_pump_cost=True, + include_building_cost=False, + include_OM_cost=False, + ) + setattr(self, f'{i}_pump', pump) + + pipe_ss, pump_ss = 0., 0. + for i in pumps: + p = getattr(self, f'{i}_pump') + p.simulate() + p_design = p.design_results + pipe_ss += p_design['Pump pipe stainless steel'] + pump_ss += p_design['Pump stainless steel'] + return pipe_ss, pump_ss + def _cost(self): - self.baseline_purchase_costs['Tank'] = \ - 3 * self.design_results['Stainless steel'] - self.power_utility.consumption = 0.1 * self.outs[0].F_vol \ No newline at end of file + + D = self.design_results + C = self.baseline_purchase_costs + + C['Wall concrete'] = D['Volume of concrete wall']*self.wall_concrete_unit_cost + C['Wall stainless steel'] = D['Stainless steel']*self.stainless_steel_unit_cost \ No newline at end of file From 524f8263df8afab165479dc116fdc10f15521ec8 Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Fri, 7 Jul 2023 10:42:30 -0500 Subject: [PATCH 146/424] Enhanced design of PC Added cost of pump, and pumping. --- qsdsan/sanunits/_clarifier.py | 108 ++++++++++++++++++++++------------ 1 file changed, 71 insertions(+), 37 deletions(-) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index c4369ae0..0e457894 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -17,7 +17,6 @@ import numpy as np from ..sanunits import WWTpump -from ..utils import auom, calculate_excavation_volume __all__ = ('FlatBottomCircularClarifier', 'IdealClarifier', @@ -884,10 +883,46 @@ def yt(t, QC_ins, dQC_ins): 'Upflow velocity': 'm/hr', 'Center feed diameter': 'm', 'Volume of concrete wall': 'm3', - 'Stainless steel': 'kg' + 'Stainless steel': 'kg', + 'Pump pipe stainless steel' : 'kg', + 'Pump stainless steel': 'kg' } + + def _design_pump(self): + ID, pumps = self.ID, self.pumps + + type_dct = dict.fromkeys(pumps, '') + inputs_dct = dict.fromkeys(pumps, (1,)) + + for i in pumps: + if hasattr(self, f'{i}_pump'): + p = getattr(self, f'{i}_pump') + setattr(p, 'add_inputs', inputs_dct[i]) + else: + ID = f'{ID}_{i}' + capacity_factor=1 + pump = WWTpump( + ID=ID, ins=self.ins[i], pump_type=type_dct[i], + Q_mgd=None, add_inputs=inputs_dct[i], + capacity_factor=capacity_factor, + include_pump_cost=True, + include_building_cost=False, + include_OM_cost=False, + ) + setattr(self, f'{i}_pump', pump) + + pipe_ss, pump_ss = 0., 0. + for i in pumps: + p = getattr(self, f'{i}_pump') + p.simulate() + p_design = p.design_results + pipe_ss += p_design['Pump pipe stainless steel'] + pump_ss += p_design['Pump stainless steel'] + return pipe_ss, pump_ss + + def _design(self): self.mixed.mix_from(self.ins) @@ -944,43 +979,42 @@ def _design(self): D['Pump pipe stainless steel'] = pipe D['Pump stainless steel'] = pumps - def _design_pump(self): - - ID, pumps = self.ID, self.pumps - - type_dct = dict.fromkeys(pumps, '') - inputs_dct = dict.fromkeys(pumps, (1,)) - - for i in pumps: - if hasattr(self, f'{i}_pump'): - p = getattr(self, f'{i}_pump') - setattr(p, 'add_inputs', inputs_dct[i]) - else: - ID = f'{ID}_{i}' - capacity_factor=1 - pump = WWTpump( - ID=ID, ins=self.ins[i], pump_type=type_dct[i], - Q_mgd=None, add_inputs=inputs_dct[i], - capacity_factor=capacity_factor, - include_pump_cost=True, - include_building_cost=False, - include_OM_cost=False, - ) - setattr(self, f'{i}_pump', pump) - - pipe_ss, pump_ss = 0., 0. - for i in pumps: - p = getattr(self, f'{i}_pump') - p.simulate() - p_design = p.design_results - pipe_ss += p_design['Pump pipe stainless steel'] - pump_ss += p_design['Pump stainless steel'] - return pipe_ss, pump_ss - def _cost(self): D = self.design_results C = self.baseline_purchase_costs - + + # Construction of concrete and stainless steel walls C['Wall concrete'] = D['Volume of concrete wall']*self.wall_concrete_unit_cost - C['Wall stainless steel'] = D['Stainless steel']*self.stainless_steel_unit_cost \ No newline at end of file + C['Wall stainless steel'] = D['Stainless steel']*self.stainless_steel_unit_cost + + # Pump (construction and maintainance) + pumps = self.pumps + add_OPEX = self.add_OPEX + pump_cost = 0. + building_cost = 0. + opex_o = 0. + opex_m = 0. + + for i in pumps: + p = getattr(self, f'{i}_pump') + p_cost = p.baseline_purchase_costs + p_add_opex = p.add_OPEX + pump_cost += p_cost['Pump'] + building_cost += p_cost['Pump building'] + opex_o += p_add_opex['Pump operating'] + opex_m += p_add_opex['Pump maintenance'] + + C['Pumps'] = pump_cost + C['Pump building'] = building_cost + add_OPEX['Pump operating'] = opex_o + add_OPEX['Pump maintenance'] = opex_m + + # Power + pumping = 0. + for ID in self.pumps: + p = getattr(self, f'{ID}_pump') + if p is None: + continue + pumping += p.power_utility.rate + self.power_utility.rate = pumping From 3d67e7e76647b042585843e4ee120037fd92e95a Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Fri, 7 Jul 2023 11:21:26 -0500 Subject: [PATCH 147/424] Adding mADM1toASM2d to junction Adding the new junction class mADM1toASM2d, which acts as an interface between modified ADM1 and ASM2d. A copy of this class exists in 'junction_copy'. --- qsdsan/sanunits/_junction.py | 424 ++++++++++++++++++++++++++++++++++- 1 file changed, 423 insertions(+), 1 deletion(-) diff --git a/qsdsan/sanunits/_junction.py b/qsdsan/sanunits/_junction.py index 66eda081..26c0ee4f 100644 --- a/qsdsan/sanunits/_junction.py +++ b/qsdsan/sanunits/_junction.py @@ -865,4 +865,426 @@ def asm2adm(asm_vals): return adm_vals - self._reactions = asm2adm \ No newline at end of file + self._reactions = asm2adm + + +# %% + +class mADM1toASM2d(ADMjunction): + ''' + Interface unit to convert modified anaerobic digestion model no. 1 (ADM1) components + to activated sludge model no. 2d (ASM2d) components. + + Parameters + ---------- + upstream : stream or str + Influent stream with ADM components. + downstream : stream or str + Effluent stream with ASM components. + adm1_model : obj + The anaerobic digestion process model (:class:`qsdsan.processes.ADM1_p_extension`). + rtol : float + Relative tolerance for COD and TKN balance. + atol : float + Absolute tolerance for COD and TKN balance. + + References + ---------- + [1] Nopens, I.; Batstone, D. J.; Copp, J. B.; Jeppsson, U.; Volcke, E.; + Alex, J.; Vanrolleghem, P. A. An ASM/ADM Model Interface for Dynamic + Plant-Wide Simulation. Water Res. 2009, 43, 1913–1923. + + [2] Flores-Alsina, X., Solon, K., Kazadi Mbamba, C., Tait, S., Gernaey, K. V., + Jeppsson, U., & Batstone, D. J. (2016). Modelling phosphorus (P), sulfur (S) + and iron (FE) interactions for dynamic simulations of anaerobic digestion processes. + Water Research, 95, 370–382. + See Also + -------- + :class:`qsdsan.sanunits.ADMjunction` + + :class:`qsdsan.sanunits.ASMtoADM` + + `math.isclose ` + ''' + # User defined values + # bio_to_xs = 0.7 (Not using this split since no X_P exists in ASM2d) + + # Should be constants + cod_vfa = np.array([64, 112, 160, 208]) + + def isbalanced(self, lhs, rhs_vals, rhs_i): + rhs = sum(rhs_vals*rhs_i) + error = rhs - lhs + tol = max(self.rtol*lhs, self.rtol*rhs, self.atol) + return abs(error) <= tol, error, tol, rhs + + def balance_cod_tkn(self, adm_vals, asm_vals): + cmps_adm = self.ins[0].components + cmps_asm = self.outs[0].components + asm_i_COD = cmps_asm.i_COD + adm_i_COD = cmps_adm.i_COD + gas_idx = cmps_adm.indices(('S_h2', 'S_ch4')) + asm_i_N = cmps_asm.i_N + adm_i_N = cmps_adm.i_N + asm_i_P = cmps_asm.i_P + adm_i_P = cmps_adm.i_P + adm_cod = sum(adm_vals*adm_i_COD) - sum(adm_vals[gas_idx]) + adm_tkn = sum(adm_vals*adm_i_N) + adm_tp = sum(adm_vals*adm_i_P) + + cod_bl, cod_err, cod_tol, asm_cod = self.isbalanced(adm_cod, asm_vals, asm_i_COD) + tkn_bl, tkn_err, tkn_tol, asm_tkn = self.isbalanced(adm_tkn, asm_vals, asm_i_N) + tp_bl, tp_err, tp_tol, asm_tp = self.isbalanced(adm_tp, asm_vals, asm_i_P) + + if tkn_bl and tp_bl: + if cod_bl: + return asm_vals + else: + if cod_err > 0: dcod = -(cod_err - cod_tol)/asm_cod + else: dcod = -(cod_err + cod_tol)/asm_cod + _asm_vals = asm_vals * (1 + (asm_i_COD>0)*dcod) + _tkn_bl, _tkn_err, _tkn_tol, _asm_tkn = self.isbalanced(adm_tkn, _asm_vals, asm_i_N) + _tp_bl, _tp_err, _tp_tol, _asm_tp = self.isbalanced(adm_tp, _asm_vals, asm_i_P) + if _tkn_bl and _tp_bl: return _asm_vals + else: + warn('cannot balance COD, TKN, and TP at the same \n' + f'time with rtol={self.rtol} and atol={self.atol}.\n ' + f'influent (ADM) TKN is {adm_tkn}\n ' + f'effluent (ASM) TKN is {asm_tkn} or {_asm_tkn}\n ' + f'influent (ADM) TP is {adm_tp}\n ' + f'effluent (ASM) TP is {asm_tp} or {_asm_tp}. ' + f'influent (ADM) COD is {adm_cod}\n ' + f'effluent (ASM) COD is {asm_cod} or {asm_cod*(1+dcod)}. ') + return asm_vals + elif cod_bl and tp_bl: + if tkn_bl: + return asm_vals + else: + if tkn_err > 0: dtkn = -(tkn_err - tkn_tol)/asm_tkn + else: dtkn = -(tkn_err + tkn_tol)/asm_tkn + _asm_vals = asm_vals * (1 + (asm_i_N>0)*dtkn) + _cod_bl, _cod_err, _cod_tol, _asm_cod = self.isbalanced(adm_cod, _asm_vals, asm_i_COD) + _tp_bl, _tp_err, _tp_tol, _asm_tp = self.isbalanced(adm_tp, _asm_vals, asm_i_P) + if _cod_bl and _tp_bl: return _asm_vals + else: + warn('cannot balance COD, TKN, and TP at the same time' + f'time with rtol={self.rtol} and atol={self.atol}.\n ' + f'influent (ADM) COD is {adm_cod}\n ' + f'effluent (ASM) COD is {asm_cod} or {_asm_cod}\n ' + f'influent (ADM) TP is {adm_tp}\n ' + f'effluent (ASM) TP is {asm_tp} or {_asm_tp}. ' + f'influent (ADM) TKN is {adm_tkn}\n ' + f'effluent (ASM) TKN is {asm_tkn} or {asm_tkn*(1+dtkn)}. ') + return asm_vals + elif cod_bl and tkn_bl: + if tp_bl: + return asm_vals + else: + if tp_err > 0: dtp = -(tp_err - tp_tol)/asm_tp + else: dtp = -(tp_err + tp_tol)/asm_tp + _asm_vals = asm_vals * (1 + (asm_i_P>0)*dtp) + _cod_bl, _cod_err, _cod_tol, _asm_cod = self.isbalanced(adm_cod, _asm_vals, asm_i_COD) + _tkn_bl, _tkn_err, _tkn_tol, _asm_tkn = self.isbalanced(adm_tkn, _asm_vals, asm_i_N) + if _cod_bl and _tkn_bl: return _asm_vals + else: + warn('cannot balance COD, TKN, and TP at the same time' + f'time with rtol={self.rtol} and atol={self.atol}.\n ' + f'influent (ADM) COD is {adm_cod}\n ' + f'effluent (ASM) COD is {asm_cod} or {_asm_cod}\n ' + f'influent (ADM) TKN is {adm_tkn}\n ' + f'effluent (ASM) TKN is {asm_tkn} or {_asm_tkn}. ' + f'influent (ADM) TP is {adm_tp}\n ' + f'effluent (ASM) TP is {asm_tp} or {asm_tp*(1+dtp)}. ') + return asm_vals + else: + warn('cannot balance COD, TKN and TP at the same time. \n' + 'Atleast two of the three COD, TKN, and TP are not balanced \n' + f'time with rtol={self.rtol} and atol={self.atol}.\n ' + f'influent (ADM) COD is {adm_cod}\n ' + f'effluent (ASM) COD is {asm_cod}\n ' + f'influent (ADM) TP is {adm_tp}\n ' + f'effluent (ASM) TP is {asm_tp}' + f'influent (ADM) TKN is {adm_tkn}\n ' + f'effluent (ASM) TKN is {asm_tkn}. ') + return asm_vals + + def _compile_reactions(self): + # Retrieve constants + ins = self.ins[0] + outs = self.outs[0] + rtol = self.rtol + atol = self.atol + + cmps_adm = ins.components + # N balance + X_pr_i_N = cmps_adm.X_pr.i_N + S_aa_i_N = cmps_adm.S_aa.i_N + adm_X_I_i_N = cmps_adm.X_I.i_N + adm_S_I_i_N = cmps_adm.S_I.i_N + adm_i_N = cmps_adm.i_N + adm_bio_N_indices = cmps_adm.indices(('X_su', 'X_aa', 'X_fa', + 'X_c4', 'X_pro', 'X_ac', 'X_h2')) + + # P balance + X_pr_i_P = cmps_adm.X_pr.i_P + adm_X_I_i_P = cmps_adm.X_I.i_P + adm_S_I_i_P = cmps_adm.S_I.i_P + adm_i_P = cmps_adm.i_P + adm_bio_P_indices = cmps_adm.indices(('X_su', 'X_aa', 'X_fa', + 'X_c4', 'X_pro', 'X_ac', 'X_h2')) + + cmps_asm = outs.components + + # N balance + X_S_i_N = cmps_asm.X_S.i_N + S_F_i_N = cmps_asm.S_F.i_N + S_A_i_N = cmps_asm.S_A.i_N + asm_X_I_i_N = cmps_asm.X_I.i_N + asm_S_I_i_N = cmps_asm.S_I.i_N + asm_ions_idx = cmps_asm.indices(('S_NH4', 'S_A', 'S_NO3', 'S_PO4', 'X_PP', 'S_ALK')) + + # P balance + X_S_i_P = cmps_asm.X_S.i_P + S_F_i_P = cmps_asm.S_F.i_P + S_A_i_P = cmps_asm.S_A.i_P + asm_X_I_i_P = cmps_asm.X_I.i_P + asm_S_I_i_P = cmps_asm.S_I.i_P + + # Checks for direct mapping of X_PAO, X_PP, X_PHA + + # Check for X_PAO (measured as COD so i_COD = 1 in both ASM2d and ADM1) + asm_X_PAO_i_N = cmps_asm.X_PAO.i_N + adm_X_PAO_i_N = cmps_adm.X_PAO.i_N + if asm_X_PAO_i_N != adm_X_PAO_i_N: + raise RuntimeError('X_PAO cannot be directly mapped as N content' + f'in asm2d_X_PAO_i_N = {asm_X_PAO_i_N} is not equal to' + f'adm_X_PAO_i_N = {adm_X_PAO_i_N}') + + asm_X_PAO_i_P = cmps_asm.X_PAO.i_P + adm_X_PAO_i_P = cmps_adm.X_PAO.i_P + if asm_X_PAO_i_P != adm_X_PAO_i_P: + raise RuntimeError('X_PAO cannot be directly mapped as P content' + f'in asm2d_X_PAO_i_P = {asm_X_PAO_i_P} is not equal to' + f'adm_X_PAO_i_P = {adm_X_PAO_i_P}') + + # Checks not required for X_PP as measured as P in both, with i_COD = i_N = 0 + # Checks not required for X_PHA as measured as COD in both, with i_N = i_P = 0 + + alpha_IN = self.alpha_IN + alpha_IC = self.alpha_IC + alpha_IP = self.alpha_IP + alpha_vfa = self.alpha_vfa + f_corr = self.balance_cod_tkn + + # To convert components from mADM1 to ASM2d (madm1-2-asm2d) + def madm12asm2d(adm_vals): + S_su, S_aa, S_fa, S_va, S_bu, S_pro, S_ac, S_h2, S_ch4, S_IC, S_IN, S_IP, S_I, \ + X_ch, X_pr, X_li, X_su, X_aa, X_fa, X_c4, X_pro, X_ac, X_h2, X_I, \ + X_PHA, X_PP, X_PAO, S_K, S_Mg, X_MeOH, X_MeP, S_cat, S_an, H2O = adm_vals + + # Step 0: snapshot of charged components + # Not sure about charge on X_PP, S_Mg, S_K (PHA and PAO would have zero charge) + _ions = np.array([S_IN, S_IC, S_IP, X_PP, S_Mg, S_K, S_ac, S_pro, S_bu, S_va]) + + # Step 1a: convert biomass into X_S+X_ND + bio_cod = X_su + X_aa + X_fa + X_c4 + X_pro + X_ac + X_h2 + bio_n = sum((adm_vals*adm_i_N)[adm_bio_N_indices]) + bio_p = sum((adm_vals*adm_i_P)[adm_bio_P_indices]) + + # There is no X_P (particulate products arising due to biomass decay) + # or equivalent component in ASM2d + # xp_cod = bio_cod * (1-self.bio_to_xs) + # xp_ndm = xp_cod*X_P_i_N + # if xp_ndm > bio_n: + # warn('Not enough biomass N to map the specified proportion of ' + # 'biomass COD into X_P. Mapped as much COD as possible, the rest ' + # 'goes to X_S.') + # X_P = bio_n/asm_X_P_i_N + # bio_n = 0 + # else: + # X_P = xp_cod + # bio_n -= xp_ndm + + # X_S = bio_cod - X_P + + # COD balance + X_S = bio_cod + + # N balance + X_S_N = X_S*X_S_i_N + if X_S_N <= bio_n: + # In ASM1-ADM1 interface if there was excess bio_n compared to + # X_S_N, it was transferred to X_ND. But since X_ND does not + # exist in ASM2d, S_IN is used for N balance + # Here, S_IN is also used based on the fact that in case of + # deficit bio_N, S_IN is used to compensate for excess X_S_N + # in Nopens at al. 2009 + S_IN += (bio_n - X_S_N) + bio_n = 0 + elif X_S_N <= bio_n + S_IN: + S_IN -= (X_S_N - bio_n) + bio_n = 0 + else: + if isclose(X_S_N, bio_n + S_IN, rel_tol=rtol, abs_tol=atol): + S_IN = bio_n = 0 + else: + raise RuntimeError('Not enough nitrogen (S_IN + biomass) to map ' + 'all biomass COD into X_S') + + # P balance + X_S_P = X_S*X_S_i_P + if X_S_P <= bio_p: + # Here, S_IP is based on the fact that in case of + # deficit bio_N, S_IN is used to compensate for excess X_S_N + # in Nopens at al. 2009 + S_IP += (bio_p - X_S_P) + bio_p = 0 + elif X_S_P <= bio_p + S_IP: + S_IP -= (X_S_P - bio_p) + bio_p = 0 + else: + if isclose(X_S_P, bio_p + S_IP, rel_tol=rtol, abs_tol=atol): + S_IP = bio_p = 0 + else: + raise RuntimeError('Not enough phosphorous (S_IP + biomass) to map ' + 'all biomass COD into X_S') + + # Step 1b: convert particulate substrates into X_S + + # First we calculate the COD, N, and P content in particulate substrate + xsub_cod = X_ch + X_pr + X_li + xsub_n = X_pr*X_pr_i_N + xsub_p = X_pr*X_pr_i_P + + # Then we determine the amount of X_S required for COD, N, and P balance + X_S_cod = xsub_cod + X_S_n = xsub_n/X_S_i_N + X_S_p = xsub_p/X_S_i_P + + # Identify the limiting component and accordingly form X_S + X_S_add = min(X_S_cod, X_S_n, X_S_p) + X_S += X_S_add + + # Calculate the imbalance in the non-limiting components + if X_S_add == X_S_cod: + def_N = (xsub_n - X_S_add*X_S_i_N) + def_P = (xsub_p - X_S_add*X_S_i_P) + S_IN += def_N + S_IP += def_P + elif X_S_add == X_S_n: + def_COD = (xsub_cod - X_S_add) + def_P = (xsub_p - X_S_add*X_S_i_P) + S_A = def_COD # suggested by Jeremy + S_IP += def_P + else: + def_COD = (xsub_cod - X_S_add) + def_N = (xsub_n - X_S_add*X_S_i_N) + S_A = def_COD # suggested by Jeremy + S_IN += def_N + + # Step 2: map all X_I from ADM to ASM + excess_XIn = X_I * (adm_X_I_i_N - asm_X_I_i_N) + S_IN += excess_XIn + if S_IN < 0: + if isclose(S_IN, 0, rel_tol=rtol, abs_tol=atol): S_IN = 0 + # This seems to be a typo. Shouldn't there be an else here? + raise RuntimeError('Not enough nitrogen (X_I + S_IN) to map ' + 'all ADM X_I into ASM X_I') + + excess_XIp = X_I * (adm_X_I_i_P - asm_X_I_i_P) + S_IP += excess_XIp + if S_IP < 0: + if isclose(S_IP, 0, rel_tol=rtol, abs_tol=atol): S_IP = 0 + # This seems to be a typo. Shouldn't there be an else here? + raise RuntimeError('Not enough phosphorous (X_I + S_IP) to map ' + 'all ADM X_I into ASM X_I') + + # Step 3: map ADM S_I into ASM S_I and S_NH4 + excess_SIn = S_I * (adm_S_I_i_N - asm_S_I_i_N) + if excess_SIn > 0: + S_NH4 = excess_SIn + else: + S_NH4 = 0 + S_IN += excess_SIn + if S_IN < 0: + if isclose(S_IN, 0, rel_tol=rtol, abs_tol=atol): S_IN = 0 + # This seems to be a typo. Shouldn't there be an else here? + raise RuntimeError('Not enough nitrogen (S_I + S_IN) to map ' + 'all ADM S_I into ASM S_I') + S_NH4 += S_IN + + excess_SIp = S_I * (adm_S_I_i_P - asm_S_I_i_P) + if excess_SIp > 0: + S_PO4 = excess_SIp + else: + S_PO4 = 0 + S_IP += excess_SIp + if S_IP < 0: + if isclose(S_IP, 0, rel_tol=rtol, abs_tol=atol): S_IP = 0 + # This seems to be a typo. Shouldn't there be an else here? + raise RuntimeError('Not enough nitrogen (S_I + S_IP) to map ' + 'all ADM S_I into ASM S_I') + S_PO4 += S_IP + + # Step 4: map all soluble substrates into S_A and S_F + ssub_cod = S_su + S_aa + S_fa + S_va + S_bu + S_pro + S_ac + ssub_n = S_aa * S_aa_i_N + + # P balance not required as all the 7 soluble substrates have no P + + # N balance + S_F = ssub_n/S_F_i_N + # COD balance + S_A += ssub_cod - S_F + + # Technically both S_A_i_N and S_A_i_P would be 0 + if S_A_i_N > 0 or S_A_i_P > 0: + # excess N formed subtracted from S_NH4 + S_NH4 -= (ssub_cod - S_F)*S_A_i_N + # excess P (due to S_A) formed subtracted from S_PO4 + S_PO4 -= (ssub_cod - S_F)*S_A_i_P + + if S_F_i_P > 0: + # excess P (due to S_F) formed subtracted from S_PO4 + S_PO4 -= S_F*S_F_i_P + + + # Step 6: check COD and TKN balance + asm_vals = np.array(([ + 0, 0, # S_O2, S_N2, + S_NH4, + 0, + S_PO4, S_F, S_A, S_I, + 0, # S_ALK, + X_I, X_S, + 0, # X_H, + X_PAO, X_PP, X_PHA, # directly mapped + 0, # X_AUT, + X_MeOH, X_MeP, H2O])) # directly mapped + + if S_h2 > 0 or S_ch4 > 0: + warn('Ignored dissolved H2 or CH4.') + + asm_vals = f_corr(adm_vals, asm_vals) + + # Step 5: charge balance for alkalinity + S_NH4 = asm_vals[asm_ions_idx[0]] + S_A = asm_vals[asm_ions_idx[1]] + S_NO3 = asm_vals[asm_ions_idx[2]] + S_PO4 = asm_vals[asm_ions_idx[3]] + X_PP = asm_vals[asm_ions_idx[4]] + # Need to include S_K, S_Mg in the charge balance (Maybe ask Joy/Jeremy) + S_ALK = (sum(_ions * np.append([alpha_IN, alpha_IC, alpha_IP], alpha_vfa)) - (S_NH4/14 - S_A/64 - S_NO3/14 -1.5*S_PO4/31 - X_PP/31))*(-12) + asm_vals[asm_ions_idx[5]] = S_ALK + + return asm_vals + + self._reactions = madm12asm2d + + @property + def alpha_vfa(self): + # This may need change based on P-extension of ADM1 (ask Joy?) + return 1.0/self.cod_vfa*(-1.0/(1.0 + 10**(self.pKa[3:]-self.pH))) + +# %% + From 032cb4040c58fc2927f489507aa5fb11151b4210 Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Fri, 7 Jul 2023 11:34:17 -0500 Subject: [PATCH 148/424] Adding ASM2dtomADM1 to junction Adding the new junction class ASM2dtomADM1, which acts as an interface between ASM2d and modified ADM1. A copy of this class exists in 'junction_copy'. --- qsdsan/sanunits/_junction.py | 647 +++++++++++++++++++++++++++++++++++ 1 file changed, 647 insertions(+) diff --git a/qsdsan/sanunits/_junction.py b/qsdsan/sanunits/_junction.py index 26c0ee4f..e6d9b9a5 100644 --- a/qsdsan/sanunits/_junction.py +++ b/qsdsan/sanunits/_junction.py @@ -1288,3 +1288,650 @@ def alpha_vfa(self): # %% +# While using this interface X_I.i_N in ASM2d should be 0.06, instead of 0.02. +class ASM2dtomADM1(ADMjunction): + ''' + Interface unit to convert activated sludge model (ASM) components + to anaerobic digestion model (ADM) components. + + Parameters + ---------- + upstream : stream or str + Influent stream with ASM components. + downstream : stream or str + Effluent stream with ADM components. + adm1_model : obj + The anaerobic digestion process model (:class:`qsdsan.processes.ADM1_p_extension`). + xs_to_li : float + Split of slowly biodegradable substrate COD to lipid, + after all N is mapped into protein. + bio_to_li : float + Split of biomass COD to lipid, after all biomass N is + mapped into protein. + frac_deg : float + Biodegradable fraction of biomass COD. + rtol : float + Relative tolerance for COD and TKN balance. + atol : float + Absolute tolerance for COD and TKN balance. + + References + ---------- + [1] Nopens, I.; Batstone, D. J.; Copp, J. B.; Jeppsson, U.; Volcke, E.; + Alex, J.; Vanrolleghem, P. A. An ASM/ADM Model Interface for Dynamic + Plant-Wide Simulation. Water Res. 2009, 43, 1913–1923. + + [2] Flores-Alsina, X., Solon, K., Kazadi Mbamba, C., Tait, S., Gernaey, K. V., + Jeppsson, U., & Batstone, D. J. (2016). Modelling phosphorus (P), sulfur (S) + and iron (FE) interactions for dynamic simulations of anaerobic digestion processes. + Water Research, 95, 370–382. + + See Also + -------- + :class:`qsdsan.sanunits.ADMjunction` + + :class:`qsdsan.sanunits.ADMtoASM` + + `math.isclose ` + ''' + # User defined values + xs_to_li = 0.7 + bio_to_li = 0.4 + frac_deg = 0.68 + + + def isbalanced(self, lhs, rhs_vals, rhs_i): + rhs = sum(rhs_vals*rhs_i) + error = rhs - lhs + tol = max(self.rtol*lhs, self.rtol*rhs, self.atol) + return abs(error) <= tol, error, tol, rhs + + def balance_cod_tkn_tp(self, asm_vals, adm_vals): + cmps_asm = self.ins[0].components + cmps_adm = self.outs[0].components + asm_i_COD = cmps_asm.i_COD + adm_i_COD = cmps_adm.i_COD + non_tkn_idx = cmps_asm.indices(('S_NO', 'S_N2')) + asm_i_N = cmps_asm.i_N + adm_i_N = cmps_adm.i_N + asm_i_P = cmps_asm.i_P + adm_i_P = cmps_adm.i_P + asm_cod = sum(asm_vals*asm_i_COD) + asm_tkn = sum(asm_vals*asm_i_N) - sum(asm_vals[non_tkn_idx]) + asm_tp = sum(asm_vals*asm_i_P) + cod_bl, cod_err, cod_tol, adm_cod = self.isbalanced(asm_cod, adm_vals, adm_i_COD) + tkn_bl, tkn_err, tkn_tol, adm_tkn = self.isbalanced(asm_tkn, adm_vals, adm_i_N) + tp_bl, tp_err, tp_tol, adm_tp = self.isbalanced(asm_tp, adm_vals, adm_i_P) + + if tkn_bl and tp_bl: + if cod_bl: + return adm_vals + else: + if cod_err > 0: dcod = -(cod_err - cod_tol)/adm_cod + else: dcod = -(cod_err + cod_tol)/adm_cod + _adm_vals = adm_vals * (1 + (adm_i_COD>0)*dcod) + _tkn_bl, _tkn_err, _tkn_tol, _adm_tkn = self.isbalanced(asm_tkn, _adm_vals, adm_i_N) + _tp_bl, _tp_err, _tp_tol, _adm_tp = self.isbalanced(asm_tp, _adm_vals, adm_i_P) + if _tkn_bl and _tp_bl: return _adm_vals + else: + warn('cannot balance COD, TKN, and TP at the same \n' + f'time with rtol={self.rtol} and atol={self.atol}.\n ' + f'influent (ASM) TKN is {asm_tkn}\n ' + f'effluent (ADM) TKN is {adm_tkn} or {_adm_tkn}\n ' + f'influent TP is {asm_tp}\n ' + f'effluent TP is {adm_tp} or {_adm_tp}. ' + f'influent COD is {asm_cod}\n ' + f'effluent COD is {adm_cod} or {adm_cod*(1+dcod)}. ') + return adm_vals + elif cod_bl and tp_bl: + if tkn_bl: + return adm_vals + else: + if tkn_err > 0: dtkn = -(tkn_err - tkn_tol)/adm_tkn + else: dtkn = -(tkn_err + tkn_tol)/adm_tkn + _adm_vals = adm_vals * (1 + (adm_i_N>0)*dtkn) + _cod_bl, _cod_err, _cod_tol, _adm_cod = self.isbalanced(asm_cod, _adm_vals, adm_i_COD) + _tp_bl, _tp_err, _tp_tol, _adm_tp = self.isbalanced(asm_tp, _adm_vals, adm_i_P) + if _cod_bl and _tp_bl: return _adm_vals + else: + warn('cannot balance COD, TKN, and TP at the same time' + f'time with rtol={self.rtol} and atol={self.atol}.\n ' + f'influent (ASM) COD is {asm_cod}\n ' + f'effluent (ADM) COD is {adm_cod} or {_adm_cod}\n ' + f'influent TP is {asm_tp}\n ' + f'effluent TP is {adm_tp} or {_adm_tp}. ' + f'influent TKN is {asm_tkn}\n ' + f'effluent TKN is {adm_tkn} or {adm_tkn*(1+dtkn)}. ' + 'To balance TKN please ensure ASM2d(X_I.i_N) = ADM1(X_I.i_N)') + return adm_vals + elif cod_bl and tkn_bl: + if tp_bl: + return adm_vals + else: + if tp_err > 0: dtp = -(tp_err - tp_tol)/adm_tp + else: dtp = -(tp_err + tp_tol)/adm_tp + _adm_vals = adm_vals * (1 + (adm_i_P>0)*dtp) + _cod_bl, _cod_err, _cod_tol, _adm_cod = self.isbalanced(asm_cod, _adm_vals, adm_i_COD) + _tkn_bl, _tkn_err, _tkn_tol, _adm_tkn = self.isbalanced(asm_tkn, _adm_vals, adm_i_N) + if _cod_bl and _tkn_bl: return _adm_vals + else: + warn('cannot balance COD, TKN, and TP at the same time' + f'time with rtol={self.rtol} and atol={self.atol}.\n ' + f'influent (ASM) COD is {asm_cod}\n ' + f'effluent (ADM) COD is {adm_cod} or {_adm_cod}\n ' + f'influent TKN is {asm_tkn}\n ' + f'effluent TKN is {adm_tkn} or {_adm_tkn}. ' + f'influent TP is {asm_tp}\n ' + f'effluent TP is {adm_tp} or {adm_tp*(1+dtp)}. ') + return adm_vals + else: + warn('cannot balance COD, TKN and TP at the same time. \n' + 'Atleast two of the three COD, TKN, and TP are not balanced \n' + f'time with rtol={self.rtol} and atol={self.atol}.\n ' + f'influent (ASM) COD is {asm_cod}\n ' + f'effluent (ADM) COD is {adm_cod}\n ' + f'influent TP is {asm_tp}\n ' + f'effluent TP is {adm_tp}' + f'influent TKN is {asm_tkn}\n ' + f'effluent TKN is {adm_tkn}. ') + return adm_vals + + def _compile_reactions(self): + # Retrieve constants + ins = self.ins[0] + outs = self.outs[0] + rtol = self.rtol + atol = self.atol + + cmps_asm = ins.components + + # For COD balance + S_NO3_i_COD = cmps_asm.S_NO3.i_COD + + # For N balance + X_H_i_N = cmps_asm.X_H.i_N + X_AUT_i_N = cmps_asm.X_AUT.i_N + S_F_i_N = cmps_asm.S_F.i_N + X_S_i_N = cmps_asm.X_S.i_N + asm_X_I_i_N = cmps_asm.X_I.i_N + asm_S_I_i_N = cmps_asm.S_I.i_N + + # For P balance + X_H_i_P = cmps_asm.X_H.i_P + X_AUT_i_P = cmps_asm.X_AUT.i_P + S_F_i_P = cmps_asm.S_F.i_P + X_S_i_P = cmps_asm.X_S.i_P + asm_X_I_i_P = cmps_asm.X_I.i_P + + if cmps_asm.S_A.i_N > 0: + warn(f'S_A in ASM has positive nitrogen content: {cmps_asm.S_S.i_N} gN/gCOD. ' + 'These nitrogen will be ignored by the interface model ' + 'and could lead to imbalance of TKN after conversion.') + if cmps_asm.S_A.i_P > 0: + warn(f'S_A in ASM has positive phosphorous content: {cmps_asm.S_S.i_P} gN/gCOD. ' + 'These phosphorous will be ignored by the interface model ' + 'and could lead to imbalance of TP after conversion.') + if cmps_asm.S_I.i_P > 0: + warn(f'S_I in ASM has positive phosphorous content: {cmps_asm.S_I.i_P} gN/gCOD. ' + 'These phosphorous will be ignored by the interface model ' + 'and could lead to imbalance of TP after conversion.') + # We do not need to check if X_S.i_N != 0 since we take care of it using X_ND_asm1 + # We do not need to check if S_F.i_N != 0 since we take care of it using S_ND_asm1 + + cmps_adm = outs.components + + # For nitrogen balance + S_aa_i_N = cmps_adm.S_aa.i_N + X_pr_i_N = cmps_adm.X_pr.i_N + adm_S_I_i_N = cmps_adm.S_I.i_N + adm_X_I_i_N = cmps_adm.X_I.i_N + + # For phosphorous balance + X_pr_i_P = cmps_adm.X_pr.i_P + adm_S_I_i_P = cmps_adm.S_I.i_P + adm_X_I_i_P = cmps_adm.X_I.i_P + + # Checks for direct mapping of X_PAO, X_PP, X_PHA + + # Check for X_PAO (measured as COD so i_COD = 1 in both ASM2d and ADM1) + asm_X_PAO_i_N = cmps_asm.X_PAO.i_N + adm_X_PAO_i_N = cmps_adm.X_PAO.i_N + if asm_X_PAO_i_N != adm_X_PAO_i_N: + raise RuntimeError('X_PAO cannot be directly mapped as N content' + f'in asm2d_X_PAO_i_N = {asm_X_PAO_i_N} is not equal to' + f'adm_X_PAO_i_N = {adm_X_PAO_i_N}') + + asm_X_PAO_i_P = cmps_asm.X_PAO.i_P + adm_X_PAO_i_P = cmps_adm.X_PAO.i_P + if asm_X_PAO_i_P != adm_X_PAO_i_P: + raise RuntimeError('X_PAO cannot be directly mapped as P content' + f'in asm2d_X_PAO_i_P = {asm_X_PAO_i_P} is not equal to' + f'adm_X_PAO_i_P = {adm_X_PAO_i_P}') + + # Checks not required for X_PP as measured as P in both, with i_COD = i_N = 0 + # Checks not required for X_PHA as measured as COD in both, with i_N = i_P = 0 + + adm_ions_idx = cmps_adm.indices(['S_IN', 'S_IP', 'S_IC', 'S_cat', 'S_an']) + + frac_deg = self.frac_deg + alpha_IP = self.alpha_IP + alpha_IN = self.alpha_IN + alpha_IC = self.alpha_IC + proton_charge = 10**(-self.pKa[0]+self.pH) - 10**(-self.pH) # self.pKa[0] is pKw + f_corr = self.balance_cod_tkn_tp + + # To convert components from ASM2d to mADM1 (asm2d-2-madm1) + def asm2d2madm1(asm_vals): + # S_I, S_S, X_I, X_S, X_BH, X_BA, X_P, S_O, S_NO, S_NH, S_ND, X_ND, S_ALK, S_N2, H2O = asm_vals + + S_O2, S_N2, S_NH4, S_NO3, S_PO4, S_F, S_A, S_I, S_ALK, X_I, X_S, X_H, \ + X_PAO, X_PP, X_PHA, X_AUT, X_MeOH, X_MeP, H2O = asm_vals + + # Step 0: charged component snapshot (# pg. 84 of IWA ASM textbook) + _sno3 = S_NO3 + _snh4 = S_NH4 + _salk = S_ALK + _spo4 = S_PO4 + _sa = S_A + _xpp = X_PP + + # Step 1: remove any remaining COD demand + O2_coddm = S_O2 + NO3_coddm = -S_NO3*S_NO3_i_COD + + # cod_spl = S_S + X_S + X_BH + X_BA + # Replacing S_S with S_F + S_A (IWA ASM textbook) + + cod_spl = (S_A + S_F) + X_S + (X_H + X_AUT) + + # bioN = X_BH*X_BH_i_N + X_BA*X_BA_i_N + + bioN = X_H*X_H_i_N + X_AUT*X_AUT_i_N + bioP = X_H*X_H_i_P + X_AUT*X_AUT_i_P + + # To be used in Step 2 + S_ND_asm1 = S_F*S_F_i_N #S_ND (in asm1) equals the N content in S_F + # To be used in Step 3 + X_ND_asm1 = X_S*X_S_i_N #X_ND (in asm1) equals the N content in X_S + # To be used in Step 5 (a) + X_S_P = X_S*X_S_i_P + # To be used in Step 5 (b) + S_F_P = S_F*S_F_i_P + + if cod_spl <= O2_coddm: + S_O2 = O2_coddm - cod_spl + S_F = S_A = X_S = X_H = X_AUT = 0 + elif cod_spl <= O2_coddm + NO3_coddm: + S_O2 = 0 + S_NO3 = -(O2_coddm + NO3_coddm - cod_spl)/S_NO3_i_COD + S_A = S_F = X_S = X_H = X_AUT = 0 + else: + S_A -= O2_coddm + NO3_coddm + if S_A < 0: + S_F += S_A + S_A = 0 + if S_F < 0: + X_S += S_F + S_F = 0 + if X_S < 0: + X_H += X_S + X_S = 0 + if X_H < 0: + X_AUT += X_H + X_H = 0 + S_O2 = S_NO3 = 0 + + # Step 2: convert any readily biodegradable + # COD and TKN into amino acids and sugars + + # S_S (in asm1) equals to the sum of S_F and S_A (pg. 82 IWA ASM models handbook) + S_S_asm1 = S_F + S_A + + # First we calculate the amount of amino acid required in ADM1 + # if all available soluble organic N can be mapped to amino acid + req_scod = S_ND_asm1 / S_aa_i_N + + # if available S_S is not enough to fulfill that amino acid requirement + if S_S_asm1 < req_scod: + # then all available S_S is mapped to amino acids + S_aa = S_S_asm1 + # and no S_S would be available for conversion to sugars + S_su = 0 + # This needs to be followed by a corresponding loss in soluble organic N + S_ND_asm1 -= S_aa * S_aa_i_N + # if available S_S is more than enough to fulfill that amino acid requirement + else: + # All soluble organic N will be mapped to amino acid + S_aa = req_scod + # The line above implies that a certain portion of S_S would also be consumed to form amino acid + # The S_S which is left would form sugar + # In simpler terms; S_S = S_S - S_aa; S_su = S_S + S_su = S_S_asm1 - S_aa + # All soluble organic N would thus be consumed in amino acid formation + S_ND_asm1 = 0 + + # Step 3: convert slowly biodegradable COD and TKN + # into proteins, lipids, and carbohydrates + + # First we calculate the amount of protein required in ADM1 + # if all available particulate organic N can be mapped to amino acid + req_xcod = X_ND_asm1 / X_pr_i_N + # Since X_pr_i_N >> X_pr_i_P there's no need to check req_xcod for N and P separately (CONFIRM LATER 05/16) + + # if available X_S is not enough to fulfill that protein requirement + if X_S < req_xcod: + # then all available X_S is mapped to amino acids + X_pr = X_S + # and no X_S would be available for conversion to lipid or carbohydrates + X_li = X_ch = 0 + # This needs to be followed by a corresponding loss in particulate organic N + X_ND_asm1 -= X_pr * X_pr_i_N + + # For P balance (CONFIRM LATER 05/16) + # This needs to be followed by a corresponding loss in particulate organic N + X_S_P -= X_pr * X_pr_i_P + + # if available X_S is more than enough to fulfill that protein requirement + else: + # All particulate organic N will be mapped to amino acid + X_pr = req_xcod + # The line above implies that a certain portion of X_S would also be consumed to form protein + # The X_S which is left would form lipid and carbohydrates in a percentage define by the user + X_li = self.xs_to_li * (X_S - X_pr) + X_ch = (X_S - X_pr) - X_li + # All particulate organic N would thus be consumed in amino acid formation + X_ND_asm1 = 0 + + # For P balance (CONFIRM LATER 05/16) + # This needs to be followed by a corresponding loss in particulate organic N + X_S_P -= X_pr * X_pr_i_P + + # Step 4: convert active biomass into protein, lipids, + # carbohydrates and potentially particulate TKN + + # First the amount of biomass N/P available for protein, lipid etc is determined + # For this calculation, from total biomass N available the amount + # of particulate inert N/P expected in ADM1 is subtracted + + available_bioN = bioN - (X_H + X_AUT) * (1-frac_deg) * adm_X_I_i_N + if available_bioN < 0: + raise RuntimeError('Not enough N in X_H and X_AUT to fully convert ' + 'the non-biodegradable portion into X_I in ADM1.') + + available_bioP = bioP - (X_H + X_AUT) * (1-frac_deg) * adm_X_I_i_P + if available_bioP < 0: + raise RuntimeError('Not enough P in X_H and X_AUT to fully convert ' + 'the non-biodegradable portion into X_I in ADM1.') + + # Then the amount of biomass N/P required for biomass conversion to protein is determined + req_bioN = (X_H + X_AUT) * frac_deg * X_pr_i_N + req_bioP = (X_H + X_AUT) * frac_deg * X_pr_i_P + + # Case I: if both available biomass N/P and particulate organic N/P is greater than + # required biomass N/P for conversion to protein + if available_bioN + X_ND_asm1 >= req_bioN and available_bioP + X_S_P >= req_bioP: + # then all biodegradable biomass N/P (corrsponding to protein demand) is converted to protein + X_pr += (X_H + X_AUT) * frac_deg + # the remaining biomass N/P is transfered as organic N/P + X_ND_asm1 += available_bioN - req_bioN + X_S_P += available_bioP - req_bioP + + # Case II: if available biomass N and particulate organic N is less than + # required biomass N for conversion to protein, but available biomass P and + # particulate organic P is greater than required biomass P for conversion to protein + + # Case III: if available biomass P and particulate organic P is less than + # required biomass P for conversion to protein, but available biomass N and + # particulate organic N is greater than required biomass N for conversion to protein + + # Case IV: if both available biomass N/P and particulate organic N/P is less than + # required biomass N/P for conversion to protein + else: + + if (available_bioP + X_S_P)/X_pr_i_P < (available_bioN + X_ND_asm1)/X_pr_i_N: + # all available P and particulate organic P is converted to protein + bio2pr = (available_bioP + X_S_P)/X_pr_i_P + X_pr += bio2pr + # Biodegradable biomass available after conversion to protein is calculated + bio_to_split = (X_H + X_AUT) * frac_deg - bio2pr + # Part of the remaining biomass is mapped to lipid based on user defined value + bio_split_to_li = bio_to_split * self.bio_to_li + X_li += bio_split_to_li + # The other portion of the remanining biomass is mapped to carbohydrates + X_ch += (bio_to_split - bio_split_to_li) + # Since all organic P has been mapped to protein, none is left + X_S_P = 0 + + # the remaining biomass N is transfered as organic N + X_ND_asm1 += available_bioN - (bio2pr*X_pr_i_N) + + else: + # all available N and particulate organic N is converted to protein + bio2pr = (available_bioN + X_ND_asm1)/X_pr_i_N + X_pr += bio2pr + # Biodegradable biomass available after conversion to protein is calculated + bio_to_split = (X_H + X_AUT) * frac_deg - bio2pr + # Part of the remaining biomass is mapped to lipid based on user defined value + bio_split_to_li = bio_to_split * self.bio_to_li + X_li += bio_split_to_li + # The other portion of the remanining biomass is mapped to carbohydrates + X_ch += (bio_to_split - bio_split_to_li) + # Since all organic N has been mapped to protein, none is left + X_ND_asm1 = 0 + + # the remaining biomass P is transfered as organic P + X_S_P += available_bioP - (bio2pr*X_pr_i_P) + + + # Step 5: map particulate inerts + + # 5 (a) + # First determine the amount of particulate inert N/P available from ASM2d + xi_nsp_asm2d = X_I * asm_X_I_i_N + xi_psp_asm2d = X_I * asm_X_I_i_P + + # Then determine the amount of particulate inert N/P that could be produced + # in ADM1 given the ASM1 X_I + xi_ndm = X_I * adm_X_I_i_N + xi_pdm = X_I * adm_X_I_i_P + + # if particulate inert N available in ASM1 is greater than ADM1 demand + if xi_nsp_asm2d + X_ND_asm1 >= xi_ndm: + deficit = xi_ndm - xi_nsp_asm2d + # COD balance + X_I += (X_H+X_AUT) * (1-frac_deg) + # N balance + X_ND_asm1 -= deficit + # P balance + if xi_psp_asm2d + X_S_P >= xi_pdm: + deficit = xi_pdm - xi_psp_asm2d + X_S_P -= deficit + elif isclose(xi_psp_asm2d+X_S_P, xi_pdm, rel_tol=rtol, abs_tol=atol): + X_S_P = 0 + else: + raise RuntimeError('Not enough P in X_I, X_S to fully ' + 'convert X_I in ASM2d into X_I in ADM1.') + elif isclose(xi_nsp_asm2d+X_ND_asm1, xi_ndm, rel_tol=rtol, abs_tol=atol): + # COD balance + X_I += (X_H+X_AUT) * (1-frac_deg) + # N balance + X_ND_asm1 = 0 + # P balance + if xi_psp_asm2d + X_S_P >= xi_pdm: + deficit = xi_pdm - xi_psp_asm2d + X_S_P -= deficit + elif isclose(xi_psp_asm2d+X_S_P, xi_pdm, rel_tol=rtol, abs_tol=atol): + X_S_P = 0 + else: + raise RuntimeError('Not enough P in X_I, X_S to fully ' + 'convert X_I in ASM2d into X_I in ADM1.') + else: + # Since the N balance cannot hold, the P balance is not futher checked + raise RuntimeError('Not enough N in X_I, X_S to fully ' + 'convert X_I in ASM2d into X_I in ADM1.') + + # 5(b) + + # Then determine the amount of soluble inert N/P that could be produced + # in ADM1 given the ASM1 X_I + req_sn = S_I * adm_S_I_i_N + req_sp = S_I * adm_S_I_i_P + + supply_inert_n_asm2d = S_I * asm_S_I_i_N + + # N balance + if req_sn <= S_ND_asm1 + supply_inert_n_asm2d: + S_ND_asm1 -= (req_sn - supply_inert_n_asm2d) + supply_inert_n_asm2d = 0 + # P balance + if req_sp <= S_F_P: + S_F_P -= req_sp + elif req_sp <= S_F_P + X_S_P: + X_S_P -= (req_sp - S_F_P) + S_F_P = 0 + elif req_sp <= S_F_P + X_S_P + S_PO4: + S_PO4 -= (req_sp - S_F_P - X_S_P) + S_F_P = X_S_P = 0 + else: + warn('Additional soluble inert COD is mapped to S_su.') + # Can these be executed in this case? I think so + SI_cod = (S_F_P + X_S_P + S_PO4)/adm_S_I_i_P + S_su += S_I - SI_cod + S_I = SI_cod + S_F_P = X_S_P = S_PO4 = 0 + # Should I redo N balance here? + # N balance + elif req_sn <= S_ND_asm1 + X_ND_asm1 + supply_inert_n_asm2d: + X_ND_asm1 -= (req_sn - S_ND_asm1 - supply_inert_n_asm2d) + S_ND_asm1 = supply_inert_n_asm2d = 0 + # P balance + if req_sp <= S_F_P: + S_F_P -= req_sp + elif req_sp <= S_F_P + X_S_P: + X_S_P -= (req_sp - S_F_P) + S_F_P = 0 + elif req_sp <= S_F_P + X_S_P + S_PO4: + S_PO4 -= (req_sp - S_F_P - X_S_P) + S_F_P = X_S_P = 0 + else: + warn('Additional soluble inert COD is mapped to S_su.') + # Can these be executed in this case? I think so + SI_cod = (S_F_P + X_S_P + S_PO4)/adm_S_I_i_P + S_su += S_I - SI_cod + S_I = SI_cod + S_F_P = X_S_P = S_PO4 = 0 + # Should I redo N balance here? + # N balance + elif req_sn <= S_ND_asm1 + X_ND_asm1 + S_NH4 + supply_inert_n_asm2d: + S_NH4 -= (req_sn - S_ND_asm1 - X_ND_asm1 - supply_inert_n_asm2d) + S_ND_asm1 = X_ND_asm1 = supply_inert_n_asm2d = 0 + # P balance + if req_sp <= S_F_P: + S_F_P -= req_sp + elif req_sp <= S_F_P + X_S_P: + X_S_P -= (req_sp - S_F_P) + S_F_P = 0 + elif req_sp <= S_F_P + X_S_P + S_PO4: + S_PO4 -= (req_sp - S_F_P - X_S_P) + S_F_P = X_S_P = 0 + else: + warn('Additional soluble inert COD is mapped to S_su.') + # Can these be executed in this case? I think so + SI_cod = (S_F_P + X_S_P + S_PO4)/adm_S_I_i_P + S_su += S_I - SI_cod + S_I = SI_cod + S_F_P = X_S_P = S_PO4 = 0 + # Should I redo N balance here? + elif req_sp <= S_F_P or req_sp <= S_F_P + X_S_P or req_sp <= S_F_P + X_S_P + S_PO4: + warn('Additional soluble inert COD is mapped to S_su.') + SI_cod = (S_ND_asm1 + X_ND_asm1 + S_NH4 + supply_inert_n_asm2d)/adm_S_I_i_N + S_su += S_I - SI_cod + S_I = SI_cod + S_ND_asm1 = X_ND_asm1 = S_NH4 = supply_inert_n_asm2d = 0 + req_sp = S_I * adm_S_I_i_P + if req_sp <= S_F_P: + S_F_P -= req_sp + elif req_sp <= S_F_P + X_S_P: + X_S_P -= (req_sp - S_F_P) + S_F_P = 0 + elif req_sp <= S_F_P + X_S_P + S_PO4: + S_PO4 -= (req_sp - S_F_P - X_S_P) + S_F_P = X_S_P = 0 + else: + if (S_ND_asm1 + X_ND_asm1 + S_NH4 + supply_inert_n_asm2d)/adm_S_I_i_N < (S_F_P + X_S_P + S_PO4)/adm_S_I_i_P: + warn('Additional soluble inert COD is mapped to S_su.') + SI_cod = (S_ND_asm1 + X_ND_asm1 + S_NH4 + supply_inert_n_asm2d)/adm_S_I_i_N + S_su += S_I - SI_cod + S_I = SI_cod + S_ND_asm1 = X_ND_asm1 = S_NH4 = supply_inert_n_asm2d = 0 + + req_sp = S_I * adm_S_I_i_P + S_PO4 -= (req_sp - S_F_P - X_S_P) + S_F_P = X_S_P = 0 + else: + warn('Additional soluble inert COD is mapped to S_su.') + SI_cod = (S_F_P + X_S_P + S_PO4)/adm_S_I_i_P + S_su += S_I - SI_cod + S_I = SI_cod + S_F_P = X_S_P = S_PO4 = 0 + + req_sn = S_I * adm_S_I_i_N + S_NH4 -= (req_sn - S_ND_asm1 - X_ND_asm1 - supply_inert_n_asm2d) + S_ND_asm1 = X_ND_asm1 = supply_inert_n_asm2d = 0 + + # Step 6: Step map any remaining TKN/P + S_IN = S_ND_asm1 + X_ND_asm1 + S_NH4 + supply_inert_n_asm2d + S_IP = S_F_P + X_S_P + S_PO4 + + # Step 8: check COD and TKN balance + # has TKN: S_aa, S_IN, S_I, X_pr, X_I + S_IC = S_cat = S_an = 0 + + # When mapping components directly in Step 9 ensure the values of + # cmps.i_N, cmps.i_P, and cmps.i_COD are same in both ASM2d and ADM1 + + # Step 9: Mapping common state variables directly + # The next three commented lines are executed when outputting + # array of ADM1 components + # X_PAO (ADM1) = X_PAO (ASM2d) + # X_PP (ADM1) = X_PP (ASM2d) + # X_PHA (ADM1) = X_PHA (ASM2d) + # X_MeOH (ADM1) = X_MeOH (ASM2d) + # X_MeP (ADM1) = X_MeP (ASM2d) + + adm_vals = np.array([ + S_su, S_aa, + 0, 0, 0, 0, 0, # S_fa, S_va, S_bu, S_pro, S_ac, + 0, 0, # S_h2, S_ch4, + S_IC, S_IN, S_IP, S_I, + X_ch, X_pr, X_li, + 0, 0, 0, 0, 0, 0, 0, # X_su, X_aa, X_fa, X_c4, X_pro, X_ac, X_h2, + X_I, X_PHA, X_PP, X_PAO, + 0, 0, # S_K, S_Mg, + X_MeOH, X_MeP, + S_cat, S_an, H2O]) + + adm_vals = f_corr(asm_vals, adm_vals) + + # Step 7: charge balance + asm_charge_tot = - _sa/64 + _snh4/14 - _sno3/14 - 1.5*_spo4/31 - _salk - _xpp/31 #Based on page 84 of IWA ASM handbook + #!!! charge balance should technically include VFAs, + # but VFAs concentrations are assumed zero per previous steps?? + S_IN = adm_vals[adm_ions_idx[0]] + S_IP = adm_vals[adm_ions_idx[1]] + #!!! charge balance from ADM1 should technically include S_K, and S_Mg, + #but since both are zero, it is acceptable + S_IC = (asm_charge_tot -S_IN*alpha_IN -S_IP*alpha_IP)/alpha_IC + net_Scat = asm_charge_tot + proton_charge + if net_Scat > 0: + S_cat = net_Scat + S_an = 0 + else: + S_cat = 0 + S_an = -net_Scat + + adm_vals[adm_ions_idx[2:]] = [S_IC, S_cat, S_an] + + return adm_vals + + self._reactions = asm2d2madm1 \ No newline at end of file From 5ddce721178b109560a79381b6ecd7ba1d5ac9c0 Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Mon, 10 Jul 2023 14:34:25 -0500 Subject: [PATCH 149/424] Spotted typos Corrected typos --- qsdsan/sanunits/_clarifier.py | 21 ++++++++++----------- qsdsan/sanunits/_junction.py | 4 ++-- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index 0e457894..7c07c26c 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -16,8 +16,6 @@ from .. import SanUnit, WasteStream import numpy as np -from ..sanunits import WWTpump - __all__ = ('FlatBottomCircularClarifier', 'IdealClarifier', 'PrimaryClarifier') @@ -887,7 +885,7 @@ def yt(t, QC_ins, dQC_ins): 'Pump pipe stainless steel' : 'kg', 'Pump stainless steel': 'kg' } - + def _design_pump(self): @@ -903,6 +901,7 @@ def _design_pump(self): else: ID = f'{ID}_{i}' capacity_factor=1 + # No. of pumps = No. of influents pump = WWTpump( ID=ID, ins=self.ins[i], pump_type=type_dct[i], Q_mgd=None, add_inputs=inputs_dct[i], @@ -921,8 +920,8 @@ def _design_pump(self): pipe_ss += p_design['Pump pipe stainless steel'] pump_ss += p_design['Pump stainless steel'] return pipe_ss, pump_ss - - + + def _design(self): self.mixed.mix_from(self.ins) @@ -983,19 +982,19 @@ def _cost(self): D = self.design_results C = self.baseline_purchase_costs - + # Construction of concrete and stainless steel walls C['Wall concrete'] = D['Volume of concrete wall']*self.wall_concrete_unit_cost C['Wall stainless steel'] = D['Stainless steel']*self.stainless_steel_unit_cost - + # Pump (construction and maintainance) pumps = self.pumps add_OPEX = self.add_OPEX pump_cost = 0. building_cost = 0. opex_o = 0. - opex_m = 0. - + opex_m = 0. + for i in pumps: p = getattr(self, f'{i}_pump') p_cost = p.baseline_purchase_costs @@ -1009,7 +1008,7 @@ def _cost(self): C['Pump building'] = building_cost add_OPEX['Pump operating'] = opex_o add_OPEX['Pump maintenance'] = opex_m - + # Power pumping = 0. for ID in self.pumps: @@ -1017,4 +1016,4 @@ def _cost(self): if p is None: continue pumping += p.power_utility.rate - self.power_utility.rate = pumping + self.power_utility.rate = pumping \ No newline at end of file diff --git a/qsdsan/sanunits/_junction.py b/qsdsan/sanunits/_junction.py index e6d9b9a5..143f3280 100644 --- a/qsdsan/sanunits/_junction.py +++ b/qsdsan/sanunits/_junction.py @@ -1628,7 +1628,7 @@ def asm2d2madm1(asm_vals): X_ND_asm1 -= X_pr * X_pr_i_N # For P balance (CONFIRM LATER 05/16) - # This needs to be followed by a corresponding loss in particulate organic N + # This needs to be followed by a corresponding loss in particulate organic P X_S_P -= X_pr * X_pr_i_P # if available X_S is more than enough to fulfill that protein requirement @@ -1643,7 +1643,7 @@ def asm2d2madm1(asm_vals): X_ND_asm1 = 0 # For P balance (CONFIRM LATER 05/16) - # This needs to be followed by a corresponding loss in particulate organic N + # This needs to be followed by a corresponding loss in particulate organic P X_S_P -= X_pr * X_pr_i_P # Step 4: convert active biomass into protein, lipids, From 094d4780b8fd2219f6757aaf9c4f2c23ed0cb08a Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Mon, 10 Jul 2023 15:43:35 -0500 Subject: [PATCH 150/424] Updated design and cost (Clarifier) 1. Updated design and cost functions of SC. 2. Added user-defined parameters for fixed design variables such as upflow_velocity, cylinderical_depth. --- qsdsan/sanunits/_clarifier.py | 129 ++++++++++++++++++++++++++++------ 1 file changed, 109 insertions(+), 20 deletions(-) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index 7c07c26c..6d1e9e7b 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -66,6 +66,8 @@ class FlatBottomCircularClarifier(SanUnit): Surface area of the clarifier, in [m^2]. The default is 1500. height : float, optional Height of the clarifier, in [m]. The default is 4. + upflow_velocity : float, optional + Speed with which influent enters the center feed of the clarifier [m/hr]. The default is 43.2. N_layer : int, optional The number of layers to model settling. The default is 10. feed_layer : int, optional @@ -97,15 +99,20 @@ class FlatBottomCircularClarifier(SanUnit): _N_ins = 1 _N_outs = 3 + + # Costs + wall_concrete_unit_cost = 650 / 0.765 # $/m3, 0.765 is to convert from $/yd3 + stainless_steel_unit_cost=1.8 # $/kg (Taken from Joy's METAB code) https://www.alibaba.com/product-detail/brushed-stainless-steel-plate-304l-stainless_1600391656401.html?spm=a2700.details.0.0.230e67e6IKwwFd def __init__(self, ID='', ins=None, outs=(), thermo=None, init_with='WasteStream', underflow=2000, wastage=385, - surface_area=1500, height=4, N_layer=10, feed_layer=4, + surface_area=1500, height=4, upflow_velocity=43.2, N_layer=10, feed_layer=4, X_threshold=3000, v_max=474, v_max_practical=250, rh=5.76e-4, rp=2.86e-3, fns=2.28e-3, isdynamic=True, **kwargs): SanUnit.__init__(self, ID, ins, outs, thermo, init_with, isdynamic=isdynamic) self._h = height + self.upflow_velocity = upflow_velocity # in m/hr (converted from 12 mm/sec) self._Qras = underflow self._Qwas = wastage self._sludge = WasteStream() @@ -455,8 +462,7 @@ def dy_dt(t, QC_ins, QC, dQC_ins): _update_dstate() self._ODE = dy_dt - - + _units = { 'Cylinderical volume': 'm3', 'Cylinderical depth': 'm', @@ -470,10 +476,46 @@ def dy_dt(t, QC_ins, QC, dQC_ins): 'Center feed depth': 'm', 'Upflow velocity': 'm/hr', 'Center feed diameter': 'm', - 'Concrete': 'kg', - 'Stainless steel': 'kg' + 'Volume of concrete wall': 'm3', + 'Stainless steel': 'kg', + 'Pump pipe stainless steel' : 'kg', + 'Pump stainless steel': 'kg' } + + def _design_pump(self): + + ID, pumps = self.ID, self.pumps + type_dct = dict.fromkeys(pumps, '') + inputs_dct = dict.fromkeys(pumps, (1,)) + + for i in pumps: + if hasattr(self, f'{i}_pump'): + p = getattr(self, f'{i}_pump') + setattr(p, 'add_inputs', inputs_dct[i]) + else: + ID = f'{ID}_{i}' + capacity_factor=1 + # No. of pumps = No. of influents + pump = WWTpump( + ID=ID, ins=self.ins[i], pump_type=type_dct[i], + Q_mgd=None, add_inputs=inputs_dct[i], + capacity_factor=capacity_factor, + include_pump_cost=True, + include_building_cost=False, + include_OM_cost=False, + ) + setattr(self, f'{i}_pump', pump) + + pipe_ss, pump_ss = 0., 0. + for i in pumps: + p = getattr(self, f'{i}_pump') + p.simulate() + p_design = p.design_results + pipe_ss += p_design['Pump pipe stainless steel'] + pump_ss += p_design['Pump stainless steel'] + return pipe_ss, pump_ss + def _design(self): self.mixed.mix_from(self.ins) @@ -493,7 +535,7 @@ def _design(self): D['Volume'] = D['Cylinderical volume'] + D['Conical volume'] - # Primary clarifiers can be center feed or peripheral feed. The design here is for the more + # Clarifiers can be center feed or peripheral feed. The design here is for the more # commonly deployed center feed. # Depth of the center feed lies between 30-75% of sidewater depth @@ -501,8 +543,7 @@ def _design(self): # Typical conventional feed wells are designed for an average downflow velocity # of 10-13 mm/s and maximum velocity of 25-30 mm/s peak_flow_safety_factor = 2.5 # assumed based on average and maximum velocities - upflow_velocity = 43.2 # in m/hr (converted from 12 mm/sec) - D['Upflow velocity'] = upflow_velocity*peak_flow_safety_factor # in m/hr + D['Upflow velocity'] = self.upflow_velocity*peak_flow_safety_factor # in m/hr Center_feed_area = self.mixed.get_total_flow('m3/hr')/D['Upflow velocity'] # in m2 D['Center feed diameter'] = ((4*Center_feed_area)/3.14)**(1/2) # Sanity check: Diameter of the center feed lies between 15-25% of tank diameter @@ -512,16 +553,60 @@ def _design(self): outer_diameter = inner_diameter + thickness_concrete_wall volume_cylindercal_wall = (3.14*D['Cylinderical depth']/4)*(outer_diameter**2 - inner_diameter**2) volume_conical_wall = (3.14/3)*(D['Conical depth']/4)*(outer_diameter**2 - inner_diameter**2) - Density_concrete = 2400 # in kg/m3 - D['Concrete'] = (volume_cylindercal_wall + volume_conical_wall)*Density_concrete # in kg - + + D['Volume of concrete wall'] = volume_cylindercal_wall + volume_conical_wall # in m3 # Amount of metal required for center feed thickness_metal_wall = 0.5 # in m (!! NEED A RELIABLE SOURCE !!) inner_diameter_center_feed = D['Center feed diameter'] outer_diameter_center_feed = inner_diameter_center_feed + thickness_metal_wall volume_center_feed = (3.14*D['Center feed depth']/4)*(outer_diameter_center_feed**2 - inner_diameter_center_feed **2) - Density_stainless_steel = 7500 # in kg/m3 - D['Stainless steel'] = volume_center_feed*Density_stainless_steel # in kg + density_ss = 7930 # kg/m3, 18/8 Chromium + D['Stainless steel'] = volume_center_feed*density_ss # in kg + + # Pumps + pipe, pumps = self._design_pump() + D['Pump pipe stainless steel'] = pipe + D['Pump stainless steel'] = pumps + + def _cost(self): + + D = self.design_results + C = self.baseline_purchase_costs + + # Construction of concrete and stainless steel walls + C['Wall concrete'] = D['Volume of concrete wall']*self.wall_concrete_unit_cost + C['Wall stainless steel'] = D['Stainless steel']*self.stainless_steel_unit_cost + + # Pump (construction and maintainance) + pumps = self.pumps + add_OPEX = self.add_OPEX + pump_cost = 0. + building_cost = 0. + opex_o = 0. + opex_m = 0. + + for i in pumps: + p = getattr(self, f'{i}_pump') + p_cost = p.baseline_purchase_costs + p_add_opex = p.add_OPEX + pump_cost += p_cost['Pump'] + building_cost += p_cost['Pump building'] + opex_o += p_add_opex['Pump operating'] + opex_m += p_add_opex['Pump maintenance'] + + C['Pumps'] = pump_cost + C['Pump building'] = building_cost + add_OPEX['Pump operating'] = opex_o + add_OPEX['Pump maintenance'] = opex_m + + # Power + pumping = 0. + for ID in self.pumps: + p = getattr(self, f'{ID}_pump') + if p is None: + continue + pumping += p.power_utility.rate + self.power_utility.rate = pumping class IdealClarifier(SanUnit): @@ -641,9 +726,10 @@ class PrimaryClarifier(SanUnit): The ratio of sludge to primary influent. The default is 0.007, based on IWA report.[1] f_corr : float Dimensionless correction factor for removal efficiency in the primary clarifier.[1] - overflow_rate : float - The design overflow rate in the primary sedimentation tank. - Default value taken from sample design problem. Unit in m/hr[2] + cylinderical_depth : float, optional + The depth of the cylinderical portion of clarifier [in m]. + upflow_velocity : float, optional + Speed with which influent enters the center feed of the clarifier [m/hr]. The default is 43.2. Examples -------- @@ -734,14 +820,17 @@ class PrimaryClarifier(SanUnit): def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, init_with='WasteStream', Hydraulic_Retention_Time=0.04268, - ratio_uf=0.007, f_corr=0.65, F_BM_default=None, overflow_rate=40, **kwargs): + ratio_uf=0.007, f_corr=0.65, cylinderical_depth = 3, upflow_velocity = 43.2, + F_BM_default=None, **kwargs): SanUnit.__init__(self, ID, ins, outs, thermo, isdynamic=isdynamic, init_with=init_with, F_BM_default=F_BM_default) self.Hydraulic_Retention_Time = Hydraulic_Retention_Time #in days self.ratio_uf = ratio_uf self.f_corr = f_corr - self.overflow_rate = overflow_rate #in m3/(m2*day) + self.cylinderical_depth = cylinderical_depth # in m + self.upflow_velocity = upflow_velocity # in m/hr (converted from 12 mm/sec) + self.mixed = WasteStream('mixed') @property @@ -933,7 +1022,7 @@ def _design(self): D['Cylinderical volume'] = working_volume # Sidewater depth of a cylinderical clarifier lies between 2.5-5m - D['Cylinderical depth'] = 3 # in m + D['Cylinderical depth'] = self.cylinderical_depth # in m # The tank diameter can lie anywhere between 3 m to 100 m D['Cylinderical diameter'] = (4*D['Cylinderical Volume']/(3.14*D['Cylinderical Depth']))**(1/2) # in m @@ -952,7 +1041,7 @@ def _design(self): # Typical conventional feed wells are designed for an average downflow velocity # of 10-13 mm/s and maximum velocity of 25-30 mm/s peak_flow_safety_factor = 2.5 # assumed based on average and maximum velocities - upflow_velocity = 43.2 # in m/hr (converted from 12 mm/sec) + upflow_velocity = self.upflow_velocity # in m/hr (converted from 12 mm/sec) D['Upflow velocity'] = upflow_velocity*peak_flow_safety_factor # in m/hr Center_feed_area = self.mixed.get_total_flow('m3/hr')/D['Upflow velocity'] # in m2 D['Center feed diameter'] = ((4*Center_feed_area)/3.14)**(1/2) # Sanity check: Diameter of the center feed lies between 15-25% of tank diameter From 6c482ed7d7deb902909d82e55fd9180648e85845 Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Mon, 10 Jul 2023 23:08:00 -0500 Subject: [PATCH 151/424] Design and cost function of thickener Updated design and cost function of thickener. Diameter is based on solid loading rate. Otherwise the design and cost is similar to that of clarifier. --- qsdsan/sanunits/_sludge_treatment.py | 176 ++++++++++++++++++++++++--- 1 file changed, 162 insertions(+), 14 deletions(-) diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py index 49468dea..a6b6d78c 100644 --- a/qsdsan/sanunits/_sludge_treatment.py +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -36,10 +36,13 @@ class Thickener(SanUnit): TSS_removal_perc : float The percentage of suspended solids removed in the thickener.[1] solids_loading_rate : float - Solid loading rate in the thickener.[2] + Solid loading rate in the thickener in [(kg/day)/m2]. [2] + Typical SLR value for thickener lies between 9.6-24 (lb/day)/ft2. + Here default value of 75 (kg/day)/m2 [15.36 (lb/day)/ft2] is used. h_cylinderical = float Height of cylinder forming the thickener.[2] - + upflow_velocity : float, optional + Speed with which influent enters the center feed of the clarifier [m/hr]. The default is 43.2. Examples -------- @@ -118,9 +121,14 @@ class Thickener(SanUnit): _ins_size_is_fixed = False _outs_size_is_fixed = False + # Costs + wall_concrete_unit_cost = 650 / 0.765 # $/m3, 0.765 is to convert from $/yd3 + stainless_steel_unit_cost=1.8 # $/kg (Taken from Joy's METAB code) https://www.alibaba.com/product-detail/brushed-stainless-steel-plate-304l-stainless_1600391656401.html?spm=a2700.details.0.0.230e67e6IKwwFd + def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, init_with='WasteStream', F_BM_default=None, thickener_perc=7, - TSS_removal_perc=98, solids_loading_rate = 50, h_cylinderical=2, **kwargs): + TSS_removal_perc=98, solids_loading_rate = 75, h_cylinderical=2, + upflow_velocity=43.2, **kwargs): SanUnit.__init__(self, ID, ins, outs, thermo, isdynamic=isdynamic, init_with=init_with, F_BM_default=F_BM_default) @@ -128,6 +136,7 @@ def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, self.TSS_removal_perc = TSS_removal_perc self.solids_loading_rate = solids_loading_rate self.h_cylinderical = h_cylinderical + self.upflow_velocity = upflow_velocity self.mixed = WasteStream(thermo=thermo) @property @@ -251,17 +260,6 @@ def _run(self): of.set_flow(Ce,'kg/hr') uf.set_flow(Cs,'kg/hr') - - def _design(self): - - design = self.design_results - slr = self._slr - - design['Area'] = ((self.ins[0].get_TSS()/1000)*self.ins[0].F_vol*24)/slr # in m2 - design['Hydraulic_Loading'] = (self.ins[0].F_vol*24)/design['Area'] #in m3/(m2*day) - design['Diameter'] = np.sqrt(4*design['Area']/np.pi) #in m - design['Volume'] = np.pi*np.square(design['Diameter']/2)*self.h_cylinderical #in m3 - design['Curved Surface Area'] = np.pi*design['Diameter']*self.h_cylinderical #in m2 def _init_state(self): @@ -371,6 +369,156 @@ def yt(t, QC_ins, dQC_ins): _update_state() _update_dstate() self._AE = yt + + _units = { + 'slr': 'kg/m2', + 'Daily mass of solids handled': 'kg', + 'Surface area': 'm2', + + 'Cylinderical diameter': 'm', + 'Cylinderical depth': 'm', + 'Cylinderical volume': 'm3', + + 'Conical radius': 'm', + 'Conical depth': 'm', + 'Conical volume': 'm3', + + 'Volume': 'm3', + 'Center feed depth': 'm', + 'Upflow velocity': 'm/hr', + 'Center feed diameter': 'm', + 'Volume of concrete wall': 'm3', + 'Stainless steel': 'kg', + 'Pump pipe stainless steel' : 'kg', + 'Pump stainless steel': 'kg' + } + + def _design_pump(self): + + ID, pumps = self.ID, self.pumps + + type_dct = dict.fromkeys(pumps, '') + inputs_dct = dict.fromkeys(pumps, (1,)) + + for i in pumps: + if hasattr(self, f'{i}_pump'): + p = getattr(self, f'{i}_pump') + setattr(p, 'add_inputs', inputs_dct[i]) + else: + ID = f'{ID}_{i}' + capacity_factor=1 + # No. of pumps = No. of influents + pump = WWTpump( + ID=ID, ins=self.ins[i], pump_type=type_dct[i], + Q_mgd=None, add_inputs=inputs_dct[i], + capacity_factor=capacity_factor, + include_pump_cost=True, + include_building_cost=False, + include_OM_cost=False, + ) + setattr(self, f'{i}_pump', pump) + + pipe_ss, pump_ss = 0., 0. + for i in pumps: + p = getattr(self, f'{i}_pump') + p.simulate() + p_design = p.design_results + pipe_ss += p_design['Pump pipe stainless steel'] + pump_ss += p_design['Pump stainless steel'] + return pipe_ss, pump_ss + + def _design(self): + + self.mixed.mix_from(self.ins) + + D = self.design_results + D['slr'] = self.solids_loading_rate # in (kg/day)/m2 + D['Daily mass of solids handled'] = (self.mixed.get_TSS()/1000)*self.mixed.get_total_flow('m3/hr')*24 # (mg/L)*[1/1000(kg*L)/(mg*m3)](m3/hr)*(24hr/day) = (kg/day) + D['Surface area'] = D['Daily mass of solids handled']/D['slr'] # in m2 + + # design['Hydraulic_Loading'] = (self.ins[0].F_vol*24)/design['Area'] #in m3/(m2*day) + D['Cylinderical diameter'] = np.sqrt(4*D['Surface area']/np.pi) #in m + D['Cylinderical depth'] = self.h_cylinderical #in m + D['Cylinderical volume'] = np.pi*np.square(D['Diameter']/2)*D['Cylinderical depth'] #in m3 + + D['Conical radius'] = D['Cylinderical Diameter']/2 + # The slope of the bottom conical floor lies between 1:10 to 1:12 + D['Conical depth'] = D['Conical radius']/10 + D['Conical volume'] = (3.14/3)*(D['Conical radius']**2)*D['Conical depth'] + + D['Volume'] = D['Cylinderical volume'] + D['Conical volume'] + + # The design here is for center feed thickener. + + # Depth of the center feed lies between 30-75% of sidewater depth + D['Center feed depth'] = 0.5*D['Cylinderical depth'] + # Typical conventional feed wells are designed for an average downflow velocity + # of 10-13 mm/s and maximum velocity of 25-30 mm/s + peak_flow_safety_factor = 2.5 # assumed based on average and maximum velocities + D['Upflow velocity'] = self.upflow_velocity*peak_flow_safety_factor # in m/hr + Center_feed_area = self.mixed.get_total_flow('m3/hr')/D['Upflow velocity'] # in m2 + D['Center feed diameter'] = ((4*Center_feed_area)/3.14)**(1/2) # Sanity check: Diameter of the center feed lies between 15-25% of tank diameter + + # Amount of concrete required + thickness_concrete_wall = 3 # in m (!! NEED A RELIABLE SOURCE !!) + inner_diameter = D['Cylinderical diameter'] + outer_diameter = inner_diameter + thickness_concrete_wall + volume_cylindercal_wall = (3.14*D['Cylinderical depth']/4)*(outer_diameter**2 - inner_diameter**2) + volume_conical_wall = (3.14/3)*(D['Conical depth']/4)*(outer_diameter**2 - inner_diameter**2) + + D['Volume of concrete wall'] = volume_cylindercal_wall + volume_conical_wall # in m3 + # Amount of metal required for center feed + thickness_metal_wall = 0.5 # in m (!! NEED A RELIABLE SOURCE !!) + inner_diameter_center_feed = D['Center feed diameter'] + outer_diameter_center_feed = inner_diameter_center_feed + thickness_metal_wall + volume_center_feed = (3.14*D['Center feed depth']/4)*(outer_diameter_center_feed**2 - inner_diameter_center_feed **2) + density_ss = 7930 # kg/m3, 18/8 Chromium + D['Stainless steel'] = volume_center_feed*density_ss # in kg + + # Pumps + pipe, pumps = self._design_pump() + D['Pump pipe stainless steel'] = pipe + D['Pump stainless steel'] = pumps + + def _cost(self): + + D = self.design_results + C = self.baseline_purchase_costs + + # Construction of concrete and stainless steel walls + C['Wall concrete'] = D['Volume of concrete wall']*self.wall_concrete_unit_cost + C['Wall stainless steel'] = D['Stainless steel']*self.stainless_steel_unit_cost + + # Pump (construction and maintainance) + pumps = self.pumps + add_OPEX = self.add_OPEX + pump_cost = 0. + building_cost = 0. + opex_o = 0. + opex_m = 0. + + for i in pumps: + p = getattr(self, f'{i}_pump') + p_cost = p.baseline_purchase_costs + p_add_opex = p.add_OPEX + pump_cost += p_cost['Pump'] + building_cost += p_cost['Pump building'] + opex_o += p_add_opex['Pump operating'] + opex_m += p_add_opex['Pump maintenance'] + + C['Pumps'] = pump_cost + C['Pump building'] = building_cost + add_OPEX['Pump operating'] = opex_o + add_OPEX['Pump maintenance'] = opex_m + + # Power + pumping = 0. + for ID in self.pumps: + p = getattr(self, f'{ID}_pump') + if p is None: + continue + pumping += p.power_utility.rate + self.power_utility.rate = pumping class DewateringUnit(Thickener): From 9754338a13fad4d22448f7d5d696701c1e2bce16 Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Wed, 12 Jul 2023 09:48:13 -0500 Subject: [PATCH 152/424] Changed order of import statements Changed the order of import statements to use pump in clarifier. Co-Authored-By: Yalin --- qsdsan/sanunits/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qsdsan/sanunits/__init__.py b/qsdsan/sanunits/__init__.py index c4aadeac..b424b32e 100644 --- a/qsdsan/sanunits/__init__.py +++ b/qsdsan/sanunits/__init__.py @@ -35,7 +35,6 @@ # Units that do not rely on other units from ._abstract import * -from ._clarifier import * from ._combustion import * from ._compressor import * from ._crop_application import * @@ -60,6 +59,7 @@ # Units that rely on other units from ._activated_sludge_process import * from ._anaerobic_reactors import * +from ._clarifier import * from ._distillation import * from ._flash import * from ._hydroprocessing import * From 8492d0d2b78475f340bcc032bde19851512efae5 Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Wed, 12 Jul 2023 09:49:27 -0500 Subject: [PATCH 153/424] Changed dependency of SanUnits Changed order of import statements to use pump in clarifier Co-Authored-By: Yalin --- qsdsan/sanunits/_clarifier.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index 6d1e9e7b..eab9d5bb 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -15,6 +15,7 @@ from numpy import maximum as npmax, minimum as npmin, exp as npexp from .. import SanUnit, WasteStream import numpy as np +from ..sanunits import WWTpump __all__ = ('FlatBottomCircularClarifier', 'IdealClarifier', @@ -818,6 +819,8 @@ class PrimaryClarifier(SanUnit): wall_concrete_unit_cost = 650 / 0.765 # $/m3, 0.765 is to convert from $/yd3 stainless_steel_unit_cost=1.8 # $/kg (Taken from Joy's METAB code) https://www.alibaba.com/product-detail/brushed-stainless-steel-plate-304l-stainless_1600391656401.html?spm=a2700.details.0.0.230e67e6IKwwFd + pumps = ('inf',) + def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, init_with='WasteStream', Hydraulic_Retention_Time=0.04268, ratio_uf=0.007, f_corr=0.65, cylinderical_depth = 3, upflow_velocity = 43.2, @@ -873,8 +876,10 @@ def f_corr(self, corr): def _f_i(self): xcod = self.mixed.composite('COD', particle_size='x') - fx = xcod/self.mixed.COD - + try: fx = xcod/self.mixed.COD + except: + breakpoint() + # fx = xcod/self.mixed.COD corr = self._corr HRT = self._HRT n_COD = corr*(2.88*fx - 0.118)*(1.45 + 6.15*np.log(HRT*24*60)) @@ -885,7 +890,7 @@ def _run(self): uf, of = self.outs cmps = self.components self.mixed.mix_from(self.ins) - + r = self._r f_i = self._f_i() @@ -979,6 +984,11 @@ def yt(t, QC_ins, dQC_ins): def _design_pump(self): ID, pumps = self.ID, self.pumps + inf = self.ins[0] + + ins_dct = { + 'inf': inf, + } type_dct = dict.fromkeys(pumps, '') inputs_dct = dict.fromkeys(pumps, (1,)) @@ -992,7 +1002,7 @@ def _design_pump(self): capacity_factor=1 # No. of pumps = No. of influents pump = WWTpump( - ID=ID, ins=self.ins[i], pump_type=type_dct[i], + ID=ID, ins=ins_dct[i], pump_type=type_dct[i], Q_mgd=None, add_inputs=inputs_dct[i], capacity_factor=capacity_factor, include_pump_cost=True, @@ -1024,9 +1034,9 @@ def _design(self): # Sidewater depth of a cylinderical clarifier lies between 2.5-5m D['Cylinderical depth'] = self.cylinderical_depth # in m # The tank diameter can lie anywhere between 3 m to 100 m - D['Cylinderical diameter'] = (4*D['Cylinderical Volume']/(3.14*D['Cylinderical Depth']))**(1/2) # in m + D['Cylinderical diameter'] = (4*D['Cylinderical volume']/(3.14*D['Cylinderical depth']))**(1/2) # in m - D['Conical radius'] = D['Cylinderical Diameter']/2 + D['Conical radius'] = D['Cylinderical diameter']/2 # The slope of the bottom conical floor lies between 1:10 to 1:12 D['Conical depth'] = D['Conical radius']/10 D['Conical volume'] = (3.14/3)*(D['Conical radius']**2)*D['Conical depth'] From 356abf8633b6379151dabb50db5c9f7f0198c96e Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Wed, 12 Jul 2023 16:33:07 -0500 Subject: [PATCH 154/424] Updated design of secondary clarifier Fixed typo (cylinderical to cylindrical). Made changes in design_pump() function of secondary clarifier. --- qsdsan/sanunits/_clarifier.py | 62 ++++++++++++++++++++--------------- 1 file changed, 36 insertions(+), 26 deletions(-) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index eab9d5bb..9fd1ce93 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -104,7 +104,9 @@ class FlatBottomCircularClarifier(SanUnit): # Costs wall_concrete_unit_cost = 650 / 0.765 # $/m3, 0.765 is to convert from $/yd3 stainless_steel_unit_cost=1.8 # $/kg (Taken from Joy's METAB code) https://www.alibaba.com/product-detail/brushed-stainless-steel-plate-304l-stainless_1600391656401.html?spm=a2700.details.0.0.230e67e6IKwwFd - + + pumps = ('inf',) + def __init__(self, ID='', ins=None, outs=(), thermo=None, init_with='WasteStream', underflow=2000, wastage=385, surface_area=1500, height=4, upflow_velocity=43.2, N_layer=10, feed_layer=4, @@ -465,9 +467,9 @@ def dy_dt(t, QC_ins, QC, dQC_ins): self._ODE = dy_dt _units = { - 'Cylinderical volume': 'm3', - 'Cylinderical depth': 'm', - 'Cylinderical diameter': 'm', + 'Cylindrical volume': 'm3', + 'Cylindrical depth': 'm', + 'Cylindrical diameter': 'm', 'Conical radius': 'm', 'Conical depth': 'm', @@ -486,6 +488,14 @@ def dy_dt(t, QC_ins, QC, dQC_ins): def _design_pump(self): ID, pumps = self.ID, self.pumps + + self.mixed.mix_from(self.ins) + + inf = self.mixed + + ins_dct = { + 'inf': inf, + } type_dct = dict.fromkeys(pumps, '') inputs_dct = dict.fromkeys(pumps, (1,)) @@ -499,7 +509,7 @@ def _design_pump(self): capacity_factor=1 # No. of pumps = No. of influents pump = WWTpump( - ID=ID, ins=self.ins[i], pump_type=type_dct[i], + ID=ID, ins=ins_dct[i], pump_type=type_dct[i], Q_mgd=None, add_inputs=inputs_dct[i], capacity_factor=capacity_factor, include_pump_cost=True, @@ -523,24 +533,24 @@ def _design(self): D = self.design_results - D['Cylinderical volume'] = self._V # in m3 + D['Cylindrical volume'] = self._V # in m3 # Sidewater depth of a cylinderical clarifier lies between 2.5-5m - D['Cylinderical depth'] = self._h # in m + D['Cylindrical depth'] = self._h # in m # The tank diameter can lie anywhere between 3 m to 100 m - D['Cylinderical diameter'] = (4*D['Cylinderical Volume']/(3.14*D['Cylinderical Depth']))**(1/2) # in m + D['Cylindrical diameter'] = (4*D['Cylindrical volume']/(3.14*D['Cylindrical depth']))**(1/2) # in m - D['Conical radius'] = D['Cylinderical Diameter']/2 + D['Conical radius'] = D['Cylindrical diameter']/2 # The slope of the bottom conical floor lies between 1:10 to 1:12 D['Conical depth'] = D['Conical radius']/10 D['Conical volume'] = (3.14/3)*(D['Conical radius']**2)*D['Conical depth'] - D['Volume'] = D['Cylinderical volume'] + D['Conical volume'] + D['Volume'] = D['Cylindrical volume'] + D['Conical volume'] # Clarifiers can be center feed or peripheral feed. The design here is for the more # commonly deployed center feed. # Depth of the center feed lies between 30-75% of sidewater depth - D['Center feed depth'] = 0.5*D['Cylinderical depth'] + D['Center feed depth'] = 0.5*D['Cylindrical depth'] # Typical conventional feed wells are designed for an average downflow velocity # of 10-13 mm/s and maximum velocity of 25-30 mm/s peak_flow_safety_factor = 2.5 # assumed based on average and maximum velocities @@ -550,12 +560,12 @@ def _design(self): # Amount of concrete required thickness_concrete_wall = 3 # in m (!! NEED A RELIABLE SOURCE !!) - inner_diameter = D['Cylinderical diameter'] + inner_diameter = D['Cylindrical diameter'] outer_diameter = inner_diameter + thickness_concrete_wall - volume_cylindercal_wall = (3.14*D['Cylinderical depth']/4)*(outer_diameter**2 - inner_diameter**2) + volume_cylindrical_wall = (3.14*D['Cylindrical depth']/4)*(outer_diameter**2 - inner_diameter**2) volume_conical_wall = (3.14/3)*(D['Conical depth']/4)*(outer_diameter**2 - inner_diameter**2) + D['Volume of concrete wall'] = volume_cylindrical_wall + volume_conical_wall # in m3 - D['Volume of concrete wall'] = volume_cylindercal_wall + volume_conical_wall # in m3 # Amount of metal required for center feed thickness_metal_wall = 0.5 # in m (!! NEED A RELIABLE SOURCE !!) inner_diameter_center_feed = D['Center feed diameter'] @@ -962,9 +972,9 @@ def yt(t, QC_ins, dQC_ins): _units = { - 'Cylinderical volume': 'm3', - 'Cylinderical depth': 'm', - 'Cylinderical diameter': 'm', + 'Cylindrical volume': 'm3', + 'Cylindrical depth': 'm', + 'Cylindrical diameter': 'm', 'Conical radius': 'm', 'Conical depth': 'm', @@ -1030,24 +1040,24 @@ def _design(self): total_volume = 24*self._HRT*self.mixed.get_total_flow('m3/hr') #in m3 working_volume = total_volume/0.8 # Assume 80% working volume - D['Cylinderical volume'] = working_volume + D['Cylindrical volume'] = working_volume # Sidewater depth of a cylinderical clarifier lies between 2.5-5m - D['Cylinderical depth'] = self.cylinderical_depth # in m + D['Cylindrical depth'] = self.cylindrical_depth # in m # The tank diameter can lie anywhere between 3 m to 100 m - D['Cylinderical diameter'] = (4*D['Cylinderical volume']/(3.14*D['Cylinderical depth']))**(1/2) # in m + D['Cylindrical diameter'] = (4*D['Cylindrical volume']/(3.14*D['Cylindrical depth']))**(1/2) # in m - D['Conical radius'] = D['Cylinderical diameter']/2 + D['Conical radius'] = D['Cylindrical diameter']/2 # The slope of the bottom conical floor lies between 1:10 to 1:12 D['Conical depth'] = D['Conical radius']/10 D['Conical volume'] = (3.14/3)*(D['Conical radius']**2)*D['Conical depth'] - D['Volume'] = D['Cylinderical volume'] + D['Conical volume'] + D['Volume'] = D['Cylindrical volume'] + D['Conical volume'] # Primary clarifiers can be center feed or peripheral feed. The design here is for the more # commonly deployed center feed. # Depth of the center feed lies between 30-75% of sidewater depth - D['Center feed depth'] = 0.5*D['Cylinderical depth'] + D['Center feed depth'] = 0.5*D['Cylindrical depth'] # Typical conventional feed wells are designed for an average downflow velocity # of 10-13 mm/s and maximum velocity of 25-30 mm/s peak_flow_safety_factor = 2.5 # assumed based on average and maximum velocities @@ -1058,11 +1068,11 @@ def _design(self): # Amount of concrete required thickness_concrete_wall = 3 # in m (!! NEED A RELIABLE SOURCE !!) - inner_diameter = D['Cylinderical diameter'] + inner_diameter = D['Cylindrical diameter'] outer_diameter = inner_diameter + thickness_concrete_wall - volume_cylindercal_wall = (3.14*D['Cylinderical depth']/4)*(outer_diameter**2 - inner_diameter**2) + volume_cylindrical_wall = (3.14*D['Cylindrical depth']/4)*(outer_diameter**2 - inner_diameter**2) volume_conical_wall = (3.14/3)*(D['Conical depth']/4)*(outer_diameter**2 - inner_diameter**2) - D['Volume of concrete wall'] = volume_cylindercal_wall + volume_conical_wall # in m3 + D['Volume of concrete wall'] = volume_cylindrical_wall + volume_conical_wall # in m3 # Amount of metal required for center feed thickness_metal_wall = 0.5 # in m (!! NEED A RELIABLE SOURCE !!) From 29552ba0132a671646a1f1a9ff81eec4af551384 Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Wed, 12 Jul 2023 16:45:32 -0500 Subject: [PATCH 155/424] Changed dependency among SanUnits Changed order of import statements to use pump in primary clarifier. --- qsdsan/sanunits/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/qsdsan/sanunits/__init__.py b/qsdsan/sanunits/__init__.py index b424b32e..74197a5e 100644 --- a/qsdsan/sanunits/__init__.py +++ b/qsdsan/sanunits/__init__.py @@ -53,8 +53,6 @@ from ._suspended_growth_bioreactor import * from ._tanks import * from ._trucking import * -from ._sludge_treatment import * - # Units that rely on other units from ._activated_sludge_process import * @@ -70,6 +68,7 @@ from ._membrane_distillation import * from ._polishing_filter import * from ._sedimentation import * +from ._sludge_treatment import * from ._septic_tank import * from ._toilets import * from ._treatment_beds import * From 7063ea90d1d315abab33e81dc794fc8bc659f951 Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Wed, 12 Jul 2023 17:03:59 -0500 Subject: [PATCH 156/424] Design function of thickener Changed design_pump() function to allow use of pump (based on discussion with Yalin). Changed typo (cylinderical to cylindrical). --- qsdsan/sanunits/_sludge_treatment.py | 51 +++++++++++++++++----------- 1 file changed, 31 insertions(+), 20 deletions(-) diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py index a6b6d78c..56ce2afa 100644 --- a/qsdsan/sanunits/_sludge_treatment.py +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -15,6 +15,7 @@ from .. import SanUnit, WasteStream import numpy as np +from ..sanunits import WWTpump __all__ = ('Thickener', 'DewateringUnit', 'Incinerator') @@ -39,7 +40,7 @@ class Thickener(SanUnit): Solid loading rate in the thickener in [(kg/day)/m2]. [2] Typical SLR value for thickener lies between 9.6-24 (lb/day)/ft2. Here default value of 75 (kg/day)/m2 [15.36 (lb/day)/ft2] is used. - h_cylinderical = float + h_cylindrical = float Height of cylinder forming the thickener.[2] upflow_velocity : float, optional Speed with which influent enters the center feed of the clarifier [m/hr]. The default is 43.2. @@ -125,9 +126,11 @@ class Thickener(SanUnit): wall_concrete_unit_cost = 650 / 0.765 # $/m3, 0.765 is to convert from $/yd3 stainless_steel_unit_cost=1.8 # $/kg (Taken from Joy's METAB code) https://www.alibaba.com/product-detail/brushed-stainless-steel-plate-304l-stainless_1600391656401.html?spm=a2700.details.0.0.230e67e6IKwwFd + pumps = ('inf',) + def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, init_with='WasteStream', F_BM_default=None, thickener_perc=7, - TSS_removal_perc=98, solids_loading_rate = 75, h_cylinderical=2, + TSS_removal_perc=98, solids_loading_rate = 75, h_cylindrical=2, upflow_velocity=43.2, **kwargs): SanUnit.__init__(self, ID, ins, outs, thermo, isdynamic=isdynamic, init_with=init_with, F_BM_default=F_BM_default) @@ -135,7 +138,7 @@ def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, self.thickener_perc = thickener_perc self.TSS_removal_perc = TSS_removal_perc self.solids_loading_rate = solids_loading_rate - self.h_cylinderical = h_cylinderical + self.h_cylindrical = h_cylindrical self.upflow_velocity = upflow_velocity self.mixed = WasteStream(thermo=thermo) @@ -375,9 +378,9 @@ def yt(t, QC_ins, dQC_ins): 'Daily mass of solids handled': 'kg', 'Surface area': 'm2', - 'Cylinderical diameter': 'm', - 'Cylinderical depth': 'm', - 'Cylinderical volume': 'm3', + 'Cylindrical diameter': 'm', + 'Cylindrical depth': 'm', + 'Cylindrical volume': 'm3', 'Conical radius': 'm', 'Conical depth': 'm', @@ -396,6 +399,14 @@ def yt(t, QC_ins, dQC_ins): def _design_pump(self): ID, pumps = self.ID, self.pumps + + self.mixed.mix_from(self.ins) + + inf = self.mixed + + ins_dct = { + 'inf': inf, + } type_dct = dict.fromkeys(pumps, '') inputs_dct = dict.fromkeys(pumps, (1,)) @@ -409,7 +420,7 @@ def _design_pump(self): capacity_factor=1 # No. of pumps = No. of influents pump = WWTpump( - ID=ID, ins=self.ins[i], pump_type=type_dct[i], + ID=ID, ins= ins_dct[i], pump_type=type_dct[i], Q_mgd=None, add_inputs=inputs_dct[i], capacity_factor=capacity_factor, include_pump_cost=True, @@ -437,21 +448,21 @@ def _design(self): D['Surface area'] = D['Daily mass of solids handled']/D['slr'] # in m2 # design['Hydraulic_Loading'] = (self.ins[0].F_vol*24)/design['Area'] #in m3/(m2*day) - D['Cylinderical diameter'] = np.sqrt(4*D['Surface area']/np.pi) #in m - D['Cylinderical depth'] = self.h_cylinderical #in m - D['Cylinderical volume'] = np.pi*np.square(D['Diameter']/2)*D['Cylinderical depth'] #in m3 + D['Cylindrical diameter'] = np.sqrt(4*D['Surface area']/np.pi) #in m + D['Cylindrical depth'] = self.h_cylindrical #in m + D['Cylindrical volume'] = np.pi*np.square(D['Diameter']/2)*D['Cylindrical depth'] #in m3 - D['Conical radius'] = D['Cylinderical Diameter']/2 + D['Conical radius'] = D['Cylindrical diameter']/2 # The slope of the bottom conical floor lies between 1:10 to 1:12 D['Conical depth'] = D['Conical radius']/10 D['Conical volume'] = (3.14/3)*(D['Conical radius']**2)*D['Conical depth'] - D['Volume'] = D['Cylinderical volume'] + D['Conical volume'] + D['Volume'] = D['Cylindrical volume'] + D['Conical volume'] # The design here is for center feed thickener. # Depth of the center feed lies between 30-75% of sidewater depth - D['Center feed depth'] = 0.5*D['Cylinderical depth'] + D['Center feed depth'] = 0.5*D['Cylindrical depth'] # Typical conventional feed wells are designed for an average downflow velocity # of 10-13 mm/s and maximum velocity of 25-30 mm/s peak_flow_safety_factor = 2.5 # assumed based on average and maximum velocities @@ -461,9 +472,9 @@ def _design(self): # Amount of concrete required thickness_concrete_wall = 3 # in m (!! NEED A RELIABLE SOURCE !!) - inner_diameter = D['Cylinderical diameter'] + inner_diameter = D['Cylindrical diameter'] outer_diameter = inner_diameter + thickness_concrete_wall - volume_cylindercal_wall = (3.14*D['Cylinderical depth']/4)*(outer_diameter**2 - inner_diameter**2) + volume_cylindercal_wall = (3.14*D['Cylindrical depth']/4)*(outer_diameter**2 - inner_diameter**2) volume_conical_wall = (3.14/3)*(D['Conical depth']/4)*(outer_diameter**2 - inner_diameter**2) D['Volume of concrete wall'] = volume_cylindercal_wall + volume_conical_wall # in m3 @@ -549,8 +560,8 @@ class DewateringUnit(Thickener): rotational speed of the centrifuge.[2,3] polymer_dosage_per_kg_of_sludge : float mass of polymer utilised per kg of influent sludge.[2,3] - h_cylinderical: float - length of cylinderical portion of dewatering unit.[2,3] + h_cylindrical: float + length of cylindrical portion of dewatering unit.[2,3] h_conical: float length of conical portion of dewatering unit.[2,3] @@ -572,7 +583,7 @@ def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, init_with='WasteStream', F_BM_default=None, thickener_perc=28, TSS_removal_perc=98, number_of_centrifuges=1, specific_gravity_sludge=1.03, cake_density=965, g_factor=2500, rotational_speed = 40, polymer_dosage_per_kg_of_sludge = 0.0075, - h_cylinderical=2, h_conical=1, **kwargs): + h_cylindrical=2, h_conical=1, **kwargs): Thickener.__init__(self, ID=ID, ins=ins, outs=outs, thermo=thermo, isdynamic=isdynamic, init_with=init_with, F_BM_default=F_BM_default, thickener_perc=thickener_perc, TSS_removal_perc=TSS_removal_perc, **kwargs) @@ -582,7 +593,7 @@ def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, self.g_factor = g_factor #unitless, centrifugal acceleration = g_factor*9.81 self.rotational_speed = rotational_speed #in revolution/sec self.polymer_dosage_per_kg_of_sludge = polymer_dosage_per_kg_of_sludge #in (kg,polymer/kg,sludge) unitless - self.h_cylinderical = h_cylinderical + self.h_cylindrical = h_cylindrical self.h_conical = h_conical def _design(self): @@ -603,7 +614,7 @@ def _design(self): design['Hydraulic_Loading'] = (self.ins[0].F_vol*24)/design['Projected Area at Inlet'] #in m3/(m2*day) design['Volume'] = np.pi*np.square(design['Diameter']/2)*(self.h_cylinderical + (self.h_conical/3)) #in m3 design['Curved Surface Area'] = np.pi*design['Diameter']/2*(2*self.h_cylinderical + np.sqrt(np.square(design['Diameter']/2) + np.square(self.h_conical))) #in m2 - + class Incinerator(SanUnit): """ From f550667e92d2d7733ee7442e5992cba0c7828995 Mon Sep 17 00:00:00 2001 From: Yalin Li Date: Thu, 13 Jul 2023 09:34:10 -0400 Subject: [PATCH 157/424] fix pump inf setting in `PrimaryClarifier` --- qsdsan/sanunits/_clarifier.py | 57 +++++++++++++++-------------------- 1 file changed, 25 insertions(+), 32 deletions(-) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index 9fd1ce93..5fef3be4 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -486,15 +486,10 @@ def dy_dt(t, QC_ins, QC, dQC_ins): } def _design_pump(self): - ID, pumps = self.ID, self.pumps - self.mixed.mix_from(self.ins) - - inf = self.mixed - ins_dct = { - 'inf': inf, + 'inf': self._inf, } type_dct = dict.fromkeys(pumps, '') @@ -529,12 +524,12 @@ def _design_pump(self): def _design(self): - self.mixed.mix_from(self.ins) + self._mixed.mix_from(self.ins) D = self.design_results D['Cylindrical volume'] = self._V # in m3 - # Sidewater depth of a cylinderical clarifier lies between 2.5-5m + # Sidewater depth of a cylindrical clarifier lies between 2.5-5m D['Cylindrical depth'] = self._h # in m # The tank diameter can lie anywhere between 3 m to 100 m D['Cylindrical diameter'] = (4*D['Cylindrical volume']/(3.14*D['Cylindrical depth']))**(1/2) # in m @@ -555,7 +550,7 @@ def _design(self): # of 10-13 mm/s and maximum velocity of 25-30 mm/s peak_flow_safety_factor = 2.5 # assumed based on average and maximum velocities D['Upflow velocity'] = self.upflow_velocity*peak_flow_safety_factor # in m/hr - Center_feed_area = self.mixed.get_total_flow('m3/hr')/D['Upflow velocity'] # in m2 + Center_feed_area = self._mixed.get_total_flow('m3/hr')/D['Upflow velocity'] # in m2 D['Center feed diameter'] = ((4*Center_feed_area)/3.14)**(1/2) # Sanity check: Diameter of the center feed lies between 15-25% of tank diameter # Amount of concrete required @@ -737,8 +732,8 @@ class PrimaryClarifier(SanUnit): The ratio of sludge to primary influent. The default is 0.007, based on IWA report.[1] f_corr : float Dimensionless correction factor for removal efficiency in the primary clarifier.[1] - cylinderical_depth : float, optional - The depth of the cylinderical portion of clarifier [in m]. + cylindrical_depth : float, optional + The depth of the cylindrical portion of clarifier [in m]. upflow_velocity : float, optional Speed with which influent enters the center feed of the clarifier [m/hr]. The default is 43.2. @@ -833,7 +828,7 @@ class PrimaryClarifier(SanUnit): def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, init_with='WasteStream', Hydraulic_Retention_Time=0.04268, - ratio_uf=0.007, f_corr=0.65, cylinderical_depth = 3, upflow_velocity = 43.2, + ratio_uf=0.007, f_corr=0.65, cylindrical_depth = 3, upflow_velocity = 43.2, F_BM_default=None, **kwargs): SanUnit.__init__(self, ID, ins, outs, thermo, isdynamic=isdynamic, @@ -841,10 +836,11 @@ def __init__(self, ID='', ins=None, outs=(), thermo=None, self.Hydraulic_Retention_Time = Hydraulic_Retention_Time #in days self.ratio_uf = ratio_uf self.f_corr = f_corr - self.cylinderical_depth = cylinderical_depth # in m + self.cylindrical_depth = cylindrical_depth # in m self.upflow_velocity = upflow_velocity # in m/hr (converted from 12 mm/sec) - self.mixed = WasteStream('mixed') + self._mixed = self.ins[0].copy(f'{ID}_mixed') + self._inf = self.ins[0].copy(f'{ID}_inf') @property def Hydraulic_Retention_Time(self): @@ -885,11 +881,8 @@ def f_corr(self, corr): raise ValueError('correction factor expected from user') def _f_i(self): - xcod = self.mixed.composite('COD', particle_size='x') - try: fx = xcod/self.mixed.COD - except: - breakpoint() - # fx = xcod/self.mixed.COD + xcod = self._mixed.composite('COD', particle_size='x') + fx = xcod/self._mixed.COD corr = self._corr HRT = self._HRT n_COD = corr*(2.88*fx - 0.118)*(1.45 + 6.15*np.log(HRT*24*60)) @@ -899,16 +892,16 @@ def _f_i(self): def _run(self): uf, of = self.outs cmps = self.components - self.mixed.mix_from(self.ins) + self._mixed.mix_from(self.ins) r = self._r f_i = self._f_i() - Xs = (1 - f_i)*self.mixed.mass*cmps.x - Xe = (f_i)*self.mixed.mass*cmps.x + Xs = (1 - f_i)*self._mixed.mass*cmps.x + Xe = (f_i)*self._mixed.mass*cmps.x - Zs = r*self.mixed.mass*cmps.s - Ze = (1-r)*self.mixed.mass*cmps.s + Zs = r*self._mixed.mass*cmps.s + Ze = (1-r)*self._mixed.mass*cmps.s Ce = Ze + Xe Cs = Zs + Xs @@ -992,12 +985,11 @@ def yt(t, QC_ins, dQC_ins): def _design_pump(self): - ID, pumps = self.ID, self.pumps - inf = self.ins[0] + self._inf.copy_like(self._mixed) ins_dct = { - 'inf': inf, + 'inf': self._inf, } type_dct = dict.fromkeys(pumps, '') @@ -1024,7 +1016,8 @@ def _design_pump(self): pipe_ss, pump_ss = 0., 0. for i in pumps: p = getattr(self, f'{i}_pump') - p.simulate() + try: p.simulate() + except: breakpoint() p_design = p.design_results pipe_ss += p_design['Pump pipe stainless steel'] pump_ss += p_design['Pump stainless steel'] @@ -1033,15 +1026,15 @@ def _design_pump(self): def _design(self): - self.mixed.mix_from(self.ins) + self._mixed.mix_from(self.ins) D = self.design_results - total_volume = 24*self._HRT*self.mixed.get_total_flow('m3/hr') #in m3 + total_volume = 24*self._HRT*self._mixed.get_total_flow('m3/hr') #in m3 working_volume = total_volume/0.8 # Assume 80% working volume D['Cylindrical volume'] = working_volume - # Sidewater depth of a cylinderical clarifier lies between 2.5-5m + # Sidewater depth of a cylindrical clarifier lies between 2.5-5m D['Cylindrical depth'] = self.cylindrical_depth # in m # The tank diameter can lie anywhere between 3 m to 100 m D['Cylindrical diameter'] = (4*D['Cylindrical volume']/(3.14*D['Cylindrical depth']))**(1/2) # in m @@ -1063,7 +1056,7 @@ def _design(self): peak_flow_safety_factor = 2.5 # assumed based on average and maximum velocities upflow_velocity = self.upflow_velocity # in m/hr (converted from 12 mm/sec) D['Upflow velocity'] = upflow_velocity*peak_flow_safety_factor # in m/hr - Center_feed_area = self.mixed.get_total_flow('m3/hr')/D['Upflow velocity'] # in m2 + Center_feed_area = self._mixed.get_total_flow('m3/hr')/D['Upflow velocity'] # in m2 D['Center feed diameter'] = ((4*Center_feed_area)/3.14)**(1/2) # Sanity check: Diameter of the center feed lies between 15-25% of tank diameter # Amount of concrete required From b4dd959442d6967748678c7c3e7ab9a65cdb6136 Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Thu, 13 Jul 2023 10:04:01 -0500 Subject: [PATCH 158/424] fix pump inf setting in `SecondaryClarifier` Now the 'design_pump' function has a influent stream which is copied from self._mixed, and its properties are updated accordingly. (Adapted based on Yalin's change to Primary clarifier) --- qsdsan/sanunits/_clarifier.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index 5fef3be4..5d1aa42e 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -138,7 +138,10 @@ def __init__(self, ID='', ins=None, outs=(), thermo=None, self._state_header = list(header) + [f'TSS{i+1} [mg/L]' for i in range(N_layer)] for attr, value in kwargs.items(): setattr(self, attr, value) - + + self._mixed = self.ins[0].copy(f'{ID}_mixed') + self._inf = self.ins[0].copy(f'{ID}_inf') + @property def height(self): '''[float] Height of the clarifier in m.''' @@ -487,6 +490,7 @@ def dy_dt(t, QC_ins, QC, dQC_ins): def _design_pump(self): ID, pumps = self.ID, self.pumps + self._inf.copy_like(self._mixed) ins_dct = { 'inf': self._inf, @@ -1016,8 +1020,9 @@ def _design_pump(self): pipe_ss, pump_ss = 0., 0. for i in pumps: p = getattr(self, f'{i}_pump') - try: p.simulate() - except: breakpoint() + p.simulate() + # try: p.simulate() + # except: breakpoint() p_design = p.design_results pipe_ss += p_design['Pump pipe stainless steel'] pump_ss += p_design['Pump stainless steel'] From e626f8da916565a568d5ff1b045346915f110d94 Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Fri, 14 Jul 2023 16:31:04 -0500 Subject: [PATCH 159/424] Design and cost of dewatering unit In progress --- qsdsan/sanunits/_sludge_treatment.py | 195 +++++++++++++++++++++------ 1 file changed, 157 insertions(+), 38 deletions(-) diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py index 56ce2afa..54e73ee6 100644 --- a/qsdsan/sanunits/_sludge_treatment.py +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -548,22 +548,31 @@ class DewateringUnit(Thickener): The percentage of Suspended Sludge in the underflow of the dewatering unit.[1] TSS_removal_perc : float The percentage of suspended solids removed in the dewatering unit.[1] - number_of_centrifuges : float - Number of centrifuges in the dewatering unit.[2,3] - specific_gravity_sludge: float - Specific gravity of influent sludge from secondary clarifier.[2,3] - cake density: float - Density of effleunt dewatered sludge.[2,3] - centrifugal_force : float - Centrifugal force in the centrifuge.[2,3] + solids_feed_rate : float + Rate of solids processed by one centrifuge in dry tonne per day (dtpd). + Default value is 70 dtpd. + + # specific_gravity_sludge: float + # Specific gravity of influent sludge from secondary clarifier.[2,3] + # cake density: float + # Density of effleunt dewatered sludge.[2,3] + + g_factor : float + Factor by which g (9.81 m/s2) is multiplied to obtain centrifugal acceleration. + g_factor typically lies between 1500 and 3000. + centrifugal acceleration = g * g_factor = k * (RPM)^2 * diameter [3] rotational_speed : float - rotational speed of the centrifuge.[2,3] - polymer_dosage_per_kg_of_sludge : float - mass of polymer utilised per kg of influent sludge.[2,3] + rotational speed of the centrifuge in rpm. [3] + LtoD: The ratio of length to diameter of the centrifuge. + The value typically lies between 3-4. [4] + polymer_dosage : float + mass of polymer utilised (lb) per tonne of dry solid waste (lbs/tonne).[5] + Depends on the type of influents, please refer to [5] for appropriate values. h_cylindrical: float - length of cylindrical portion of dewatering unit.[2,3] + length of cylindrical portion of dewatering unit. h_conical: float - length of conical portion of dewatering unit.[2,3] + length of conical portion of dewatering unit.[ + References ---------- @@ -573,47 +582,157 @@ class DewateringUnit(Thickener): engineering: treatment, disposal, and reuse. Vol. 4. New York: McGraw-Hill, 1991. [3]Design of Municipal Wastewater Treatment Plants: WEF Manual of Practice No. 8 ASCE Manuals and Reports on Engineering Practice No. 76, Fifth Edition. + [4] https://www.alibaba.com/product-detail/Multifunctional-Sludge-Dewatering-Decanter-Centrifuge_1600285055254.html?spm=a2700.galleryofferlist.normal_offer.d_title.1cd75229sPf1UW&s=p + [5] United States Environmental Protection Agency (EPA) 'Biosolids Technology Fact Sheet Centrifuge Thickening and Dewatering' """ _N_ins = 1 _N_outs = 2 _ins_size_is_fixed = False + # Costs + stainless_steel_unit_cost=1.8 # $/kg (Taken from Joy's METAB code) https://www.alibaba.com/product-detail/brushed-stainless-steel-plate-304l-stainless_1600391656401.html?spm=a2700.details.0.0.230e67e6IKwwFd + screw_conveyor_unit_cost_by_weight = 1800/800 # $/kg (Source: https://www.alibaba.com/product-detail/Engineers-Available-Service-Stainless-Steel-U_60541536633.html?spm=a2700.galleryofferlist.normal_offer.d_title.1fea1f65v6R3OQ&s=p) + def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, init_with='WasteStream', F_BM_default=None, thickener_perc=28, TSS_removal_perc=98, - number_of_centrifuges=1, specific_gravity_sludge=1.03, cake_density=965, - g_factor=2500, rotational_speed = 40, polymer_dosage_per_kg_of_sludge = 0.0075, - h_cylindrical=2, h_conical=1, **kwargs): + + solids_feed_rate = 70, + # specific_gravity_sludge=1.03, cake_density=965, + g_factor=2500, rotational_speed = 40, LtoD = 4, + polymer_dosage = 0.0075, h_cylindrical=2, h_conical=1, + **kwargs): Thickener.__init__(self, ID=ID, ins=ins, outs=outs, thermo=thermo, isdynamic=isdynamic, init_with=init_with, F_BM_default=F_BM_default, thickener_perc=thickener_perc, TSS_removal_perc=TSS_removal_perc, **kwargs) - self.number_of_centrifuges=number_of_centrifuges - self.specific_gravity_sludge=specific_gravity_sludge - self.cake_density=cake_density #in kg/m3 + + self._mixed = self.ins[0].copy(f'{ID}_mixed') + self._inf = self.ins[0].copy(f'{ID}_inf') + + self.solids_feed_rate = solids_feed_rate + # self.specific_gravity_sludge=specific_gravity_sludge + # self.cake_density=cake_density #in kg/m3 self.g_factor = g_factor #unitless, centrifugal acceleration = g_factor*9.81 - self.rotational_speed = rotational_speed #in revolution/sec - self.polymer_dosage_per_kg_of_sludge = polymer_dosage_per_kg_of_sludge #in (kg,polymer/kg,sludge) unitless + self.rotational_speed = rotational_speed #in revolution/min + self.LtoD = LtoD + self.polymer_dosage = polymer_dosage #in (lbs,polymer/tonne,solids) self.h_cylindrical = h_cylindrical self.h_conical = h_conical + _units = { + 'Number of centrifuges': '?', + 'Diameter of bowl': 'm', + 'Total length of bowl': 'm', + 'Length of cylindrical portion': 'm', + 'Length of conical portion': 'm', + 'Volume of bowl': 'm3', + + 'Stainless steel for bowl': 'kg', + 'Diameter of screw conveyor': 'm', + + 'Polymer feed rate': 'kg/hr', + + 'Pump pipe stainless steel' : 'kg', + 'Pump stainless steel': 'kg' + } + def _design(self): - sludge_feed_rate = ((self.ins[0].get_TSS()*self.ins[0].F_vol)/1000)/self.number_of_centrifuges #in kg/hr - - #TSS_rmv = self._TSS_rmv - #recovery = 1 - TSS_rmv/100 - #cake_mass_discharge_rate = sludge_feed_rate*recovery #in kg/hr - #wetcake_mass_discharge_rate = cake_mass_discharge_rate/(self.thickener_perc/100) #in kg/hr - #cake_density = self.cake_density - #wetcake_flowrate = wetcake_mass_discharge_rate/cake_density #in m3/hr - #volume_reduction_perc= (1 - wetcake_flowrate/(self.ins[0].F_mass/(1000*self.specific_gravity_sludge*self.number_of_centrifuges)))*100 - - design = self.design_results - design['Diameter'] = 2*(self.g_factor*9.81/np.square(2*np.pi*self.rotational_speed)) #in m - design['Polymer feed rate'] = (self.polymer_dosage_per_kg_of_sludge*sludge_feed_rate) # in kg/hr - design['Projected Area at Inlet'] = np.pi*np.square(design['Diameter']/2) #in m2 - design['Hydraulic_Loading'] = (self.ins[0].F_vol*24)/design['Projected Area at Inlet'] #in m3/(m2*day) - design['Volume'] = np.pi*np.square(design['Diameter']/2)*(self.h_cylinderical + (self.h_conical/3)) #in m3 - design['Curved Surface Area'] = np.pi*design['Diameter']/2*(2*self.h_cylinderical + np.sqrt(np.square(design['Diameter']/2) + np.square(self.h_conical))) #in m2 + + self._mixed.mix_from(self.ins) + + D = self.design_results + TSS_rmv = self._TSS_rmv + solids_feed_rate = 44.66*self.solids_feed_rate # 44.66 is factor to convert tonne/day to kg/hr + # Cake's total solids and TSS are essentially the same (pg. 24-6 [3]) + # If TSS_rmv = 98, then total_mass_dry_solids_removed = (0.98)*(influent TSS mass) + total_mass_dry_solids_removed = (TSS_rmv/100)*((self._mixed.get_TSS()*self.ins[0].F_vol)/1000) # in kg/hr + D['Number of centrifuges'] = np.ceil(total_mass_dry_solids_removed/solids_feed_rate) + k = 0.00000056 # Based on emprical formula (pg. 24-23 of [3]) + g = 9.81 # m/s2 + # The inner diameterof the bowl is calculated based on an empirical formula. 1000 is used to convert mm to m. + D['Diameter of bowl'] = (self.g_factor*g)/(k*np.square(self.rotational_speed)*1000) # in m + D['Total length of bowl'] = self.LtoD*D['Diameter'] + # Sanity check: L should be between 1-7 m, diameter should be around 0.25-0.8 m (Source: [4]) + + fraction_cylindrical_portion = 0.8 + fraction_conical_portion = 1 - fraction_cylindrical_portion + D['Length of cylindrical portion'] = fraction_cylindrical_portion*D['Tiotal length of bowl'] + D['Length of conical portion'] = fraction_conical_portion*D['Tiotal length of bowl'] + thickness_of_bowl_wall = 0.1 # in m (!!! NEED A RELIABLE SOURCE !!!) + inner_diameter = D['Diameter of bowl'] + outer_diameter = inner_diameter + thickness_of_bowl_wall + volume_cylindrical_wall = (np.pi*D['Length of cylindrical portion']/4)*(outer_diameter**2 - inner_diameter**2) + volume_conical_wall = (np.pi/3)*(D['Length of conical portion']/4)*(outer_diameter**2 - inner_diameter**2) + D['Volume of bowl'] = volume_cylindrical_wall + volume_conical_wall # in m3 + + density_ss = 7930 # kg/m3, 18/8 Chromium + D['Stainless steel for bowl'] = D['Volume of bowl']*density_ss # in kg + + Inner_diameter_conveyor = 0.108 # in m (based on Alibaba's product) + Length_screw_conveyor = D['Total length of bowl'] # in m + thickness_conveyor_wall = 0.01 # in m (!!! NEED A RELIABLE SOURCE !!!) + Outer_diameter_conveyor = Inner_diameter_conveyor + thickness_conveyor_wall + Cylinder_conveyor_volume = np.pi*(np.square(Outer_diameter_conveyor/2) - np.square(Inner_diameter_conveyor/2))*Length_screw_conveyor # in m2 + + outer_projection_screw = 0.05 # in m (!!! NEED A RELIABLE SOURCE !!!) + Outer_diameter_screw = Outer_diameter_conveyor + outer_projection_screw + thickness_screw = 0.05 # in m (!!! NEED A RELIABLE SOURCE !!!) + Number_of_circles_in_screw = D['Total length of bowl']*10 # !!! NEED A RELIABLE SOURCE !!! (assuming 10 circles every meter unit length) + Screw_volume = np.pi*(np.square(Outer_diameter_screw) - np.square(Outer_diameter_conveyor))*thickness_screw*Number_of_circles_in_screw # in m3 + + + polymer_dosage_rate = 0.000453592*self.polymer_dosage # convert from (lbs, polymer/tonne, solids) to (kg, polymer/kg, solids) + D['Polymer feed rate'] = (polymer_dosage_rate*solids_feed_rate) # in kg, polymer/hr + + # Pumps + pipe, pumps = self._design_pump() + D['Pump pipe stainless steel'] = pipe + D['Pump stainless steel'] = pumps + + # design['Projected Area at Inlet'] = np.pi*np.square(design['Diameter']/2) #in m2 + # design['Hydraulic_Loading'] = (self.ins[0].F_vol*24)/design['Projected Area at Inlet'] #in m3/(m2*day) + + def _cost(self): + + D = self.design_results + C = self.baseline_purchase_costs + + # Construction of concrete and stainless steel walls + C['Bowl stainless steel'] = D['Stainless steel for bowl']*self.stainless_steel_unit_cost + + + + # Pump (construction and maintainance) + pumps = self.pumps + add_OPEX = self.add_OPEX + pump_cost = 0. + building_cost = 0. + opex_o = 0. + opex_m = 0. + + for i in pumps: + p = getattr(self, f'{i}_pump') + p_cost = p.baseline_purchase_costs + p_add_opex = p.add_OPEX + pump_cost += p_cost['Pump'] + building_cost += p_cost['Pump building'] + opex_o += p_add_opex['Pump operating'] + opex_m += p_add_opex['Pump maintenance'] + + C['Pumps'] = pump_cost + C['Pump building'] = building_cost + add_OPEX['Pump operating'] = opex_o + add_OPEX['Pump maintenance'] = opex_m + + # Power + pumping = 0. + for ID in self.pumps: + p = getattr(self, f'{ID}_pump') + if p is None: + continue + pumping += p.power_utility.rate + self.power_utility.rate = pumping + class Incinerator(SanUnit): From 769ab53a17b548b18b27ac5139602d7380438500 Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Sun, 16 Jul 2023 13:17:03 -0500 Subject: [PATCH 160/424] Corrected outer diameter formula --- qsdsan/sanunits/_clarifier.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index 5d1aa42e..d001ead9 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -560,7 +560,7 @@ def _design(self): # Amount of concrete required thickness_concrete_wall = 3 # in m (!! NEED A RELIABLE SOURCE !!) inner_diameter = D['Cylindrical diameter'] - outer_diameter = inner_diameter + thickness_concrete_wall + outer_diameter = inner_diameter + 2*thickness_concrete_wall volume_cylindrical_wall = (3.14*D['Cylindrical depth']/4)*(outer_diameter**2 - inner_diameter**2) volume_conical_wall = (3.14/3)*(D['Conical depth']/4)*(outer_diameter**2 - inner_diameter**2) D['Volume of concrete wall'] = volume_cylindrical_wall + volume_conical_wall # in m3 @@ -568,7 +568,7 @@ def _design(self): # Amount of metal required for center feed thickness_metal_wall = 0.5 # in m (!! NEED A RELIABLE SOURCE !!) inner_diameter_center_feed = D['Center feed diameter'] - outer_diameter_center_feed = inner_diameter_center_feed + thickness_metal_wall + outer_diameter_center_feed = inner_diameter_center_feed + 2*thickness_metal_wall volume_center_feed = (3.14*D['Center feed depth']/4)*(outer_diameter_center_feed**2 - inner_diameter_center_feed **2) density_ss = 7930 # kg/m3, 18/8 Chromium D['Stainless steel'] = volume_center_feed*density_ss # in kg @@ -1067,7 +1067,7 @@ def _design(self): # Amount of concrete required thickness_concrete_wall = 3 # in m (!! NEED A RELIABLE SOURCE !!) inner_diameter = D['Cylindrical diameter'] - outer_diameter = inner_diameter + thickness_concrete_wall + outer_diameter = inner_diameter + 2*thickness_concrete_wall volume_cylindrical_wall = (3.14*D['Cylindrical depth']/4)*(outer_diameter**2 - inner_diameter**2) volume_conical_wall = (3.14/3)*(D['Conical depth']/4)*(outer_diameter**2 - inner_diameter**2) D['Volume of concrete wall'] = volume_cylindrical_wall + volume_conical_wall # in m3 @@ -1075,7 +1075,7 @@ def _design(self): # Amount of metal required for center feed thickness_metal_wall = 0.5 # in m (!! NEED A RELIABLE SOURCE !!) inner_diameter_center_feed = D['Center feed diameter'] - outer_diameter_center_feed = inner_diameter_center_feed + thickness_metal_wall + outer_diameter_center_feed = inner_diameter_center_feed + 2*thickness_metal_wall volume_center_feed = (3.14*D['Center feed depth']/4)*(outer_diameter_center_feed**2 - inner_diameter_center_feed **2) density_ss = 7930 # kg/m3, 18/8 Chromium D['Stainless steel'] = volume_center_feed*density_ss # in kg From 01066e11fd27101eeb2fa7190ab3639cd0406bc1 Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Sun, 16 Jul 2023 13:46:36 -0500 Subject: [PATCH 161/424] To be checked later (self._mixed definition) Added the design and cost of screw conveyor to dewatering unit. Along with changes in the definition of self._mixed stream. Not sure about these changes, to be checked at a later date. --- qsdsan/sanunits/_sludge_treatment.py | 103 ++++++++++++++++++--------- 1 file changed, 70 insertions(+), 33 deletions(-) diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py index 54e73ee6..268028da 100644 --- a/qsdsan/sanunits/_sludge_treatment.py +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -140,7 +140,7 @@ def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, self.solids_loading_rate = solids_loading_rate self.h_cylindrical = h_cylindrical self.upflow_velocity = upflow_velocity - self.mixed = WasteStream(thermo=thermo) + self._mixed = WasteStream(thermo=thermo) @property def thickener_perc(self): @@ -184,8 +184,8 @@ def TSS_removal_perc(self, TSS_rmv): @property def thickener_factor(self): - self.mixed.mix_from(self.ins) - inf = self.mixed + self._mixed.mix_from(self.ins) + inf = self._mixed _cal_thickener_factor = self._cal_thickener_factor if not self.ins: return elif inf.isempty(): return @@ -196,8 +196,8 @@ def thickener_factor(self): @property def thinning_factor(self): - self.mixed.mix_from(self.ins) - inf = self.mixed + self._mixed.mix_from(self.ins) + inf = self._mixed TSS_in = inf.get_TSS() _cal_thickener_factor = self._cal_thickener_factor thickener_factor = _cal_thickener_factor(TSS_in) @@ -239,8 +239,8 @@ def _update_parameters(self): def _run(self): - self.mixed.mix_from(self.ins) - inf = self.mixed + self._mixed.mix_from(self.ins) + inf = self._mixed uf, of = self.outs cmps = self.components @@ -397,17 +397,14 @@ def yt(t, QC_ins, dQC_ins): } def _design_pump(self): - - ID, pumps = self.ID, self.pumps - - self.mixed.mix_from(self.ins) - inf = self.mixed + ID, pumps = self.ID, self.pumps + self._inf.copy_like(self._mixed) ins_dct = { - 'inf': inf, + 'inf': self._inf, } - + type_dct = dict.fromkeys(pumps, '') inputs_dct = dict.fromkeys(pumps, (1,)) @@ -440,11 +437,11 @@ def _design_pump(self): def _design(self): - self.mixed.mix_from(self.ins) + self._mixed.mix_from(self.ins) D = self.design_results D['slr'] = self.solids_loading_rate # in (kg/day)/m2 - D['Daily mass of solids handled'] = (self.mixed.get_TSS()/1000)*self.mixed.get_total_flow('m3/hr')*24 # (mg/L)*[1/1000(kg*L)/(mg*m3)](m3/hr)*(24hr/day) = (kg/day) + D['Daily mass of solids handled'] = (self._mixed.get_TSS()/1000)*self._mixed.get_total_flow('m3/hr')*24 # (mg/L)*[1/1000(kg*L)/(mg*m3)](m3/hr)*(24hr/day) = (kg/day) D['Surface area'] = D['Daily mass of solids handled']/D['slr'] # in m2 # design['Hydraulic_Loading'] = (self.ins[0].F_vol*24)/design['Area'] #in m3/(m2*day) @@ -467,7 +464,7 @@ def _design(self): # of 10-13 mm/s and maximum velocity of 25-30 mm/s peak_flow_safety_factor = 2.5 # assumed based on average and maximum velocities D['Upflow velocity'] = self.upflow_velocity*peak_flow_safety_factor # in m/hr - Center_feed_area = self.mixed.get_total_flow('m3/hr')/D['Upflow velocity'] # in m2 + Center_feed_area = self._mixed.get_total_flow('m3/hr')/D['Upflow velocity'] # in m2 D['Center feed diameter'] = ((4*Center_feed_area)/3.14)**(1/2) # Sanity check: Diameter of the center feed lies between 15-25% of tank diameter # Amount of concrete required @@ -606,8 +603,8 @@ def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, init_with=init_with, F_BM_default=F_BM_default, thickener_perc=thickener_perc, TSS_removal_perc=TSS_removal_perc, **kwargs) - self._mixed = self.ins[0].copy(f'{ID}_mixed') - self._inf = self.ins[0].copy(f'{ID}_inf') + # self._mixed = self.ins[0].copy(f'{ID}_mixed') + # self._inf = self.ins[0].copy(f'{ID}_inf') self.solids_feed_rate = solids_feed_rate # self.specific_gravity_sludge=specific_gravity_sludge @@ -628,14 +625,55 @@ def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, 'Volume of bowl': 'm3', 'Stainless steel for bowl': 'kg', - 'Diameter of screw conveyor': 'm', + 'Stainless steel for conveyor': 'kg', 'Polymer feed rate': 'kg/hr', - 'Pump pipe stainless steel' : 'kg', 'Pump stainless steel': 'kg' } + + def _design_pump(self): + ID, pumps = self.ID, self.pumps + self._inf.copy_like(self._mixed) + + ins_dct = { + 'inf': self._inf, + } + + type_dct = dict.fromkeys(pumps, '') + inputs_dct = dict.fromkeys(pumps, (1,)) + + for i in pumps: + if hasattr(self, f'{i}_pump'): + p = getattr(self, f'{i}_pump') + setattr(p, 'add_inputs', inputs_dct[i]) + else: + ID = f'{ID}_{i}' + capacity_factor=1 + # No. of pumps = No. of influents + pump = WWTpump( + ID=ID, ins=ins_dct[i], pump_type=type_dct[i], + Q_mgd=None, add_inputs=inputs_dct[i], + capacity_factor=capacity_factor, + include_pump_cost=True, + include_building_cost=False, + include_OM_cost=False, + ) + setattr(self, f'{i}_pump', pump) + + pipe_ss, pump_ss = 0., 0. + for i in pumps: + p = getattr(self, f'{i}_pump') + p.simulate() + # try: p.simulate() + # except: breakpoint() + p_design = p.design_results + pipe_ss += p_design['Pump pipe stainless steel'] + pump_ss += p_design['Pump stainless steel'] + return pipe_ss, pump_ss + + def _design(self): self._mixed.mix_from(self.ins) @@ -656,11 +694,11 @@ def _design(self): fraction_cylindrical_portion = 0.8 fraction_conical_portion = 1 - fraction_cylindrical_portion - D['Length of cylindrical portion'] = fraction_cylindrical_portion*D['Tiotal length of bowl'] - D['Length of conical portion'] = fraction_conical_portion*D['Tiotal length of bowl'] + D['Length of cylindrical portion'] = fraction_cylindrical_portion*D['Total length of bowl'] + D['Length of conical portion'] = fraction_conical_portion*D['Total length of bowl'] thickness_of_bowl_wall = 0.1 # in m (!!! NEED A RELIABLE SOURCE !!!) inner_diameter = D['Diameter of bowl'] - outer_diameter = inner_diameter + thickness_of_bowl_wall + outer_diameter = inner_diameter + 2*thickness_of_bowl_wall volume_cylindrical_wall = (np.pi*D['Length of cylindrical portion']/4)*(outer_diameter**2 - inner_diameter**2) volume_conical_wall = (np.pi/3)*(D['Length of conical portion']/4)*(outer_diameter**2 - inner_diameter**2) D['Volume of bowl'] = volume_cylindrical_wall + volume_conical_wall # in m3 @@ -671,15 +709,16 @@ def _design(self): Inner_diameter_conveyor = 0.108 # in m (based on Alibaba's product) Length_screw_conveyor = D['Total length of bowl'] # in m thickness_conveyor_wall = 0.01 # in m (!!! NEED A RELIABLE SOURCE !!!) - Outer_diameter_conveyor = Inner_diameter_conveyor + thickness_conveyor_wall + Outer_diameter_conveyor = Inner_diameter_conveyor + 2*thickness_conveyor_wall Cylinder_conveyor_volume = np.pi*(np.square(Outer_diameter_conveyor/2) - np.square(Inner_diameter_conveyor/2))*Length_screw_conveyor # in m2 outer_projection_screw = 0.05 # in m (!!! NEED A RELIABLE SOURCE !!!) - Outer_diameter_screw = Outer_diameter_conveyor + outer_projection_screw - thickness_screw = 0.05 # in m (!!! NEED A RELIABLE SOURCE !!!) - Number_of_circles_in_screw = D['Total length of bowl']*10 # !!! NEED A RELIABLE SOURCE !!! (assuming 10 circles every meter unit length) - Screw_volume = np.pi*(np.square(Outer_diameter_screw) - np.square(Outer_diameter_conveyor))*thickness_screw*Number_of_circles_in_screw # in m3 - + Outer_diameter_screw = Outer_diameter_conveyor + 2*outer_projection_screw + thickness_screw = 0.01 # in m (!!! NEED A RELIABLE SOURCE !!!) + number_of_screws_per_unit_length = 10 # in m-1 (!!! NEED A RELIABLE SOURCE !!!) + Number_of_circles_in_screw = D['Total length of bowl']*number_of_screws_per_unit_length # !!! NEED A RELIABLE SOURCE !!! + Screw_volume = np.pi*(np.square(Outer_diameter_screw/2) - np.square(Outer_diameter_conveyor/2))*thickness_screw*Number_of_circles_in_screw # in m3 + D['Stainless steel for conveyor'] = (Cylinder_conveyor_volume + Screw_volume)*density_ss # in kg polymer_dosage_rate = 0.000453592*self.polymer_dosage # convert from (lbs, polymer/tonne, solids) to (kg, polymer/kg, solids) D['Polymer feed rate'] = (polymer_dosage_rate*solids_feed_rate) # in kg, polymer/hr @@ -699,9 +738,8 @@ def _cost(self): # Construction of concrete and stainless steel walls C['Bowl stainless steel'] = D['Stainless steel for bowl']*self.stainless_steel_unit_cost + C['Conveyor stainless steel'] = D['Stainless steel for conveyor']*self.screw_conveyor_unit_cost_by_weight - - # Pump (construction and maintainance) pumps = self.pumps add_OPEX = self.add_OPEX @@ -733,7 +771,6 @@ def _cost(self): pumping += p.power_utility.rate self.power_utility.rate = pumping - class Incinerator(SanUnit): """ From 9229e3bad54cd40e95beff212a2e2d640ad8596f Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Tue, 18 Jul 2023 12:27:53 -0500 Subject: [PATCH 162/424] Correct deployment of pump Utility power consumption now included. Co-Authored-By: Yalin --- qsdsan/sanunits/_clarifier.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index d001ead9..cc8dc137 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -512,8 +512,8 @@ def _design_pump(self): Q_mgd=None, add_inputs=inputs_dct[i], capacity_factor=capacity_factor, include_pump_cost=True, - include_building_cost=False, - include_OM_cost=False, + include_building_cost=True, + include_OM_cost=True, ) setattr(self, f'{i}_pump', pump) @@ -996,8 +996,8 @@ def _design_pump(self): 'inf': self._inf, } - type_dct = dict.fromkeys(pumps, '') - inputs_dct = dict.fromkeys(pumps, (1,)) + type_dct = dict.fromkeys(pumps, 'lift') + inputs_dct = dict.fromkeys(pumps, (1, 3),) for i in pumps: if hasattr(self, f'{i}_pump'): @@ -1013,7 +1013,7 @@ def _design_pump(self): capacity_factor=capacity_factor, include_pump_cost=True, include_building_cost=False, - include_OM_cost=False, + include_OM_cost=True, ) setattr(self, f'{i}_pump', pump) @@ -1123,4 +1123,5 @@ def _cost(self): if p is None: continue pumping += p.power_utility.rate - self.power_utility.rate = pumping \ No newline at end of file + self.power_utility.consumption += pumping + \ No newline at end of file From f3b7903f97fd5f6b830590e559fd342e48e9c9f5 Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Tue, 25 Jul 2023 09:35:10 +0530 Subject: [PATCH 163/424] Updated thickness of concrete wall Based on Brian's code --- qsdsan/sanunits/_clarifier.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index cc8dc137..d13f6249 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -558,7 +558,9 @@ def _design(self): D['Center feed diameter'] = ((4*Center_feed_area)/3.14)**(1/2) # Sanity check: Diameter of the center feed lies between 15-25% of tank diameter # Amount of concrete required - thickness_concrete_wall = 3 # in m (!! NEED A RELIABLE SOURCE !!) + D_tank = D['Cylindrical depth']*39.37 # m to inches + # Thickness of the wall concrete, [m]. Default to be minimum of 1 ft with 1 in added for every ft of depth over 12 ft. + thickness_concrete_wall = (1 + max(D_tank-12, 0)/12)*0.3048 # from feet to m inner_diameter = D['Cylindrical diameter'] outer_diameter = inner_diameter + 2*thickness_concrete_wall volume_cylindrical_wall = (3.14*D['Cylindrical depth']/4)*(outer_diameter**2 - inner_diameter**2) @@ -1065,7 +1067,9 @@ def _design(self): D['Center feed diameter'] = ((4*Center_feed_area)/3.14)**(1/2) # Sanity check: Diameter of the center feed lies between 15-25% of tank diameter # Amount of concrete required - thickness_concrete_wall = 3 # in m (!! NEED A RELIABLE SOURCE !!) + D_tank = D['Cylindrical depth']*39.37 # m to inches + # Thickness of the wall concrete, [m]. Default to be minimum of 1 ft with 1 in added for every ft of depth over 12 ft. + thickness_concrete_wall = (1 + max(D_tank-12, 0)/12)*0.3048 # from feet to m inner_diameter = D['Cylindrical diameter'] outer_diameter = inner_diameter + 2*thickness_concrete_wall volume_cylindrical_wall = (3.14*D['Cylindrical depth']/4)*(outer_diameter**2 - inner_diameter**2) From b9e9534ca71e6451cfa4165535b5fae6e5b7eaad Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Tue, 25 Jul 2023 11:36:33 +0530 Subject: [PATCH 164/424] Updated pump setting in PC Primary clarifier has just one pump (of type 'sludge') that is used to pump the waste sludge out of the unit. Usually no pump is employed for the influent and gravity is relied upon. --- qsdsan/sanunits/_clarifier.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index d13f6249..c8bfdfc8 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -830,7 +830,7 @@ class PrimaryClarifier(SanUnit): wall_concrete_unit_cost = 650 / 0.765 # $/m3, 0.765 is to convert from $/yd3 stainless_steel_unit_cost=1.8 # $/kg (Taken from Joy's METAB code) https://www.alibaba.com/product-detail/brushed-stainless-steel-plate-304l-stainless_1600391656401.html?spm=a2700.details.0.0.230e67e6IKwwFd - pumps = ('inf',) + pumps = ('sludge',) def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, init_with='WasteStream', Hydraulic_Retention_Time=0.04268, @@ -846,7 +846,7 @@ def __init__(self, ID='', ins=None, outs=(), thermo=None, self.upflow_velocity = upflow_velocity # in m/hr (converted from 12 mm/sec) self._mixed = self.ins[0].copy(f'{ID}_mixed') - self._inf = self.ins[0].copy(f'{ID}_inf') + self._sludge = self.outs[1].copy(f'{ID}_sludge') @property def Hydraulic_Retention_Time(self): @@ -992,14 +992,18 @@ def yt(t, QC_ins, dQC_ins): def _design_pump(self): ID, pumps = self.ID, self.pumps - self._inf.copy_like(self._mixed) + self._sludge.copy_like(self.outs[1]) ins_dct = { - 'inf': self._inf, + 'sludge': self._sludge, } + + # ins_dct = { + # 'inf': self._inf, + # } - type_dct = dict.fromkeys(pumps, 'lift') - inputs_dct = dict.fromkeys(pumps, (1, 3),) + type_dct = dict.fromkeys(pumps, 'sludge') + inputs_dct = dict.fromkeys(pumps, (1,),) for i in pumps: if hasattr(self, f'{i}_pump'): From 470d15eb4f904ab82f3741326cd22e294e37fd12 Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Tue, 25 Jul 2023 12:27:07 +0530 Subject: [PATCH 165/424] Updated pump setting in secondary clarifier Secondary clarifier would have two sludge pumps for RAS and WAS respectively. No pump required for influent. --- qsdsan/sanunits/_clarifier.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index c8bfdfc8..43658161 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -105,7 +105,7 @@ class FlatBottomCircularClarifier(SanUnit): wall_concrete_unit_cost = 650 / 0.765 # $/m3, 0.765 is to convert from $/yd3 stainless_steel_unit_cost=1.8 # $/kg (Taken from Joy's METAB code) https://www.alibaba.com/product-detail/brushed-stainless-steel-plate-304l-stainless_1600391656401.html?spm=a2700.details.0.0.230e67e6IKwwFd - pumps = ('inf',) + pumps = ('ras', 'was') def __init__(self, ID='', ins=None, outs=(), thermo=None, init_with='WasteStream', underflow=2000, wastage=385, @@ -141,6 +141,8 @@ def __init__(self, ID='', ins=None, outs=(), thermo=None, self._mixed = self.ins[0].copy(f'{ID}_mixed') self._inf = self.ins[0].copy(f'{ID}_inf') + self._ras = self.outs[1].copy(f'{ID}_ras') + self._was = self.outs[2].copy(f'{ID}_was') @property def height(self): @@ -490,14 +492,16 @@ def dy_dt(t, QC_ins, QC, dQC_ins): def _design_pump(self): ID, pumps = self.ID, self.pumps - self._inf.copy_like(self._mixed) + self._ras.copy_like(self.outs[1]) + self._was.copy_like(self.outs[2]) ins_dct = { - 'inf': self._inf, + 'ras': self._ras, + 'was': self._was, } - type_dct = dict.fromkeys(pumps, '') - inputs_dct = dict.fromkeys(pumps, (1,)) + type_dct = dict.fromkeys(pumps, 'sludge', 'sludge') + inputs_dct = dict.fromkeys(pumps, (1,), (1,)) for i in pumps: if hasattr(self, f'{i}_pump'): @@ -506,7 +510,6 @@ def _design_pump(self): else: ID = f'{ID}_{i}' capacity_factor=1 - # No. of pumps = No. of influents pump = WWTpump( ID=ID, ins=ins_dct[i], pump_type=type_dct[i], Q_mgd=None, add_inputs=inputs_dct[i], @@ -997,10 +1000,6 @@ def _design_pump(self): ins_dct = { 'sludge': self._sludge, } - - # ins_dct = { - # 'inf': self._inf, - # } type_dct = dict.fromkeys(pumps, 'sludge') inputs_dct = dict.fromkeys(pumps, (1,),) From 766dd36db90925db96c98d32aa454d02fb526c4f Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Tue, 25 Jul 2023 16:20:53 +0530 Subject: [PATCH 166/424] Allowing multiple clarifier units in a PC --- qsdsan/sanunits/_clarifier.py | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index 43658161..ef2822d1 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -534,7 +534,10 @@ def _design(self): self._mixed.mix_from(self.ins) D = self.design_results - + # Assuming the capacity of one clarifier is 20 MGD = (20*3785.4118) m3/day = 3155 m3/hr + design_flow = 3155 # m3/hr + D['Number of clarifiers'] = np.ceil(self._mixed.get_total_flow('m3/hr')/design_flow) + D['Cylindrical volume'] = self._V # in m3 # Sidewater depth of a cylindrical clarifier lies between 2.5-5m D['Cylindrical depth'] = self._h # in m @@ -557,7 +560,7 @@ def _design(self): # of 10-13 mm/s and maximum velocity of 25-30 mm/s peak_flow_safety_factor = 2.5 # assumed based on average and maximum velocities D['Upflow velocity'] = self.upflow_velocity*peak_flow_safety_factor # in m/hr - Center_feed_area = self._mixed.get_total_flow('m3/hr')/D['Upflow velocity'] # in m2 + Center_feed_area = design_flow/D['Upflow velocity'] # in m2 D['Center feed diameter'] = ((4*Center_feed_area)/3.14)**(1/2) # Sanity check: Diameter of the center feed lies between 15-25% of tank diameter # Amount of concrete required @@ -589,8 +592,8 @@ def _cost(self): C = self.baseline_purchase_costs # Construction of concrete and stainless steel walls - C['Wall concrete'] = D['Volume of concrete wall']*self.wall_concrete_unit_cost - C['Wall stainless steel'] = D['Stainless steel']*self.stainless_steel_unit_cost + C['Wall concrete'] = D['Number of clarifiers']*D['Volume of concrete wall']*self.wall_concrete_unit_cost + C['Wall stainless steel'] = D['Number of clarifiers']*D['Stainless steel']*self.stainless_steel_unit_cost # Pump (construction and maintainance) pumps = self.pumps @@ -1039,8 +1042,12 @@ def _design(self): self._mixed.mix_from(self.ins) D = self.design_results + + # Assuming the capacity of one clarifier is 20 MGD = (20*3785.4118) m3/day = 3155 m3/hr + design_flow = 3155 # m3/hr + D['Number of clarifiers'] = np.ceil(self._mixed.get_total_flow('m3/hr')/design_flow) - total_volume = 24*self._HRT*self._mixed.get_total_flow('m3/hr') #in m3 + total_volume = 24*self._HRT*design_flow #in m3 working_volume = total_volume/0.8 # Assume 80% working volume D['Cylindrical volume'] = working_volume @@ -1066,7 +1073,7 @@ def _design(self): peak_flow_safety_factor = 2.5 # assumed based on average and maximum velocities upflow_velocity = self.upflow_velocity # in m/hr (converted from 12 mm/sec) D['Upflow velocity'] = upflow_velocity*peak_flow_safety_factor # in m/hr - Center_feed_area = self._mixed.get_total_flow('m3/hr')/D['Upflow velocity'] # in m2 + Center_feed_area = design_flow/D['Upflow velocity'] # in m2 D['Center feed diameter'] = ((4*Center_feed_area)/3.14)**(1/2) # Sanity check: Diameter of the center feed lies between 15-25% of tank diameter # Amount of concrete required @@ -1098,8 +1105,8 @@ def _cost(self): C = self.baseline_purchase_costs # Construction of concrete and stainless steel walls - C['Wall concrete'] = D['Volume of concrete wall']*self.wall_concrete_unit_cost - C['Wall stainless steel'] = D['Stainless steel']*self.stainless_steel_unit_cost + C['Wall concrete'] = D['Number of clarifiers']*D['Volume of concrete wall']*self.wall_concrete_unit_cost + C['Wall stainless steel'] = D['Number of clarifiers']*D['Stainless steel']*self.stainless_steel_unit_cost # Pump (construction and maintainance) pumps = self.pumps From c5bfbb6fda0aa6ba358c34f4ab0ddf85ad90b5b0 Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Tue, 25 Jul 2023 21:37:35 +0530 Subject: [PATCH 167/424] Added equipment (scraper) to PC --- qsdsan/sanunits/_clarifier.py | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index ef2822d1..c3655efe 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -827,7 +827,7 @@ class PrimaryClarifier(SanUnit): [2] Metcalf, Leonard, Harrison P. Eddy, and Georg Tchobanoglous. Wastewater engineering: treatment, disposal, and reuse. Vol. 4. New York: McGraw-Hill, 1991. """ - + _N_ins = 3 _N_outs = 2 _ins_size_is_fixed = False @@ -840,8 +840,8 @@ class PrimaryClarifier(SanUnit): def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, init_with='WasteStream', Hydraulic_Retention_Time=0.04268, - ratio_uf=0.007, f_corr=0.65, cylindrical_depth = 3, upflow_velocity = 43.2, - F_BM_default=None, **kwargs): + ratio_uf=0.007, f_corr=0.65, cylindrical_depth = 5, upflow_velocity = 43.2, + design_flow = 3155, F_BM_default=None, **kwargs): SanUnit.__init__(self, ID, ins, outs, thermo, isdynamic=isdynamic, init_with=init_with, F_BM_default=F_BM_default) @@ -850,6 +850,7 @@ def __init__(self, ID='', ins=None, outs=(), thermo=None, self.f_corr = f_corr self.cylindrical_depth = cylindrical_depth # in m self.upflow_velocity = upflow_velocity # in m/hr (converted from 12 mm/sec) + self.design_flow = design_flow # 20 MGD = 3155 m3/hr self._mixed = self.ins[0].copy(f'{ID}_mixed') self._sludge = self.outs[1].copy(f'{ID}_sludge') @@ -1044,7 +1045,7 @@ def _design(self): D = self.design_results # Assuming the capacity of one clarifier is 20 MGD = (20*3785.4118) m3/day = 3155 m3/hr - design_flow = 3155 # m3/hr + design_flow = self.design_flow # m3/hr D['Number of clarifiers'] = np.ceil(self._mixed.get_total_flow('m3/hr')/design_flow) total_volume = 24*self._HRT*design_flow #in m3 @@ -1101,13 +1102,27 @@ def _design(self): def _cost(self): + self._mixed.mix_from(self.ins) D = self.design_results C = self.baseline_purchase_costs # Construction of concrete and stainless steel walls C['Wall concrete'] = D['Number of clarifiers']*D['Volume of concrete wall']*self.wall_concrete_unit_cost C['Wall stainless steel'] = D['Number of clarifiers']*D['Stainless steel']*self.stainless_steel_unit_cost - + + # Cost of equipment + + # Source of scaling exponents: Process Design and Economics for Biochemical Conversion of Lignocellulosic Biomass to Ethanol by NREL. + + # Scraper + # Source: https://www.alibaba.com/product-detail/Peripheral-driving-clarifier-mud-scraper-waste_1600891102019.html?spm=a2700.details.0.0.47ab45a4TP0DLb + base_cost_scraper = 2500 + base_flow_scraper = 1 # in m3/hr (!!! Need to know whether this is for solids or influent !!!) + clarifier_flow = self._mixed.get_total_flow('m3/hr')/D['Number of clarifiers'] + C['Scraper'] = D['Number of clarifiers']*base_cost_scraper*(clarifier_flow/base_flow_scraper)**0.6 + base_power_scraper = 2.75 # in kW + scraper_power = D['Number of clarifiers']*base_power_scraper*(clarifier_flow/base_flow_scraper)**0.6 + # Pump (construction and maintainance) pumps = self.pumps add_OPEX = self.add_OPEX @@ -1138,4 +1153,6 @@ def _cost(self): continue pumping += p.power_utility.rate self.power_utility.consumption += pumping - \ No newline at end of file + + + self.power_utility.consumption += scraper_power \ No newline at end of file From 159e6898b0faf8ecdcdc988e30aea69e026b5164 Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Tue, 25 Jul 2023 22:14:04 +0530 Subject: [PATCH 168/424] Set bare module factor for pumps In both Primary and Secondary clarifier. --- qsdsan/sanunits/_clarifier.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index c3655efe..7764181e 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -21,7 +21,8 @@ 'IdealClarifier', 'PrimaryClarifier') -F_BM_pump = 1.18*(1+0.007/100) # 0.007 is for miscellaneous costs +F_BM_pump = 1.18*(1 + 0.007/100) # 0.007 is for miscellaneous costs + default_F_BM = { 'Pumps': F_BM_pump, 'Pump building': F_BM_pump, @@ -111,9 +112,9 @@ def __init__(self, ID='', ins=None, outs=(), thermo=None, init_with='WasteStream', underflow=2000, wastage=385, surface_area=1500, height=4, upflow_velocity=43.2, N_layer=10, feed_layer=4, X_threshold=3000, v_max=474, v_max_practical=250, - rh=5.76e-4, rp=2.86e-3, fns=2.28e-3, isdynamic=True, **kwargs): + rh=5.76e-4, rp=2.86e-3, fns=2.28e-3, F_BM_default=default_F_BM, isdynamic=True, **kwargs): - SanUnit.__init__(self, ID, ins, outs, thermo, init_with, isdynamic=isdynamic) + SanUnit.__init__(self, ID, ins, outs, thermo, init_with, isdynamic=isdynamic, F_BM_default=1) self._h = height self.upflow_velocity = upflow_velocity # in m/hr (converted from 12 mm/sec) self._Qras = underflow @@ -841,10 +842,10 @@ class PrimaryClarifier(SanUnit): def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, init_with='WasteStream', Hydraulic_Retention_Time=0.04268, ratio_uf=0.007, f_corr=0.65, cylindrical_depth = 5, upflow_velocity = 43.2, - design_flow = 3155, F_BM_default=None, **kwargs): + design_flow = 3155, F_BM_default=default_F_BM, **kwargs): SanUnit.__init__(self, ID, ins, outs, thermo, isdynamic=isdynamic, - init_with=init_with, F_BM_default=F_BM_default) + init_with=init_with, F_BM_default=1) self.Hydraulic_Retention_Time = Hydraulic_Retention_Time #in days self.ratio_uf = ratio_uf self.f_corr = f_corr From 5701c043dc158c07d1776c265283ce901902572f Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Wed, 26 Jul 2023 08:39:07 +0530 Subject: [PATCH 169/424] Added equipment (V notch weir) Added V notch to both PC and SC. Added scraper to SC as well. --- qsdsan/sanunits/_clarifier.py | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index 7764181e..233deeba 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -595,6 +595,25 @@ def _cost(self): # Construction of concrete and stainless steel walls C['Wall concrete'] = D['Number of clarifiers']*D['Volume of concrete wall']*self.wall_concrete_unit_cost C['Wall stainless steel'] = D['Number of clarifiers']*D['Stainless steel']*self.stainless_steel_unit_cost + + # Cost of equipment + + # Source of scaling exponents: Process Design and Economics for Biochemical Conversion of Lignocellulosic Biomass to Ethanol by NREL. + + # Scraper + # Source: https://www.alibaba.com/product-detail/Peripheral-driving-clarifier-mud-scraper-waste_1600891102019.html?spm=a2700.details.0.0.47ab45a4TP0DLb + base_cost_scraper = 2500 + base_flow_scraper = 1 # in m3/hr (!!! Need to know whether this is for solids or influent !!!) + clarifier_flow = self._mixed.get_total_flow('m3/hr')/D['Number of clarifiers'] + C['Scraper'] = D['Number of clarifiers']*base_cost_scraper*(clarifier_flow/base_flow_scraper)**0.6 + base_power_scraper = 2.75 # in kW + scraper_power = D['Number of clarifiers']*base_power_scraper*(clarifier_flow/base_flow_scraper)**0.6 + + # v notch weir + # Source: https://www.alibaba.com/product-detail/50mm-Tube-Settler-Media-Modules-Inclined_1600835845218.html?spm=a2700.galleryofferlist.normal_offer.d_title.69135ff6o4kFPb + base_cost_v_notch_weir = 6888 + base_flow_v_notch_weir = 10 # in m3/hr + C['v notch weir'] = D['Number of clarifiers']*base_cost_v_notch_weir*(clarifier_flow/base_flow_v_notch_weir)**0.6 # Pump (construction and maintainance) pumps = self.pumps @@ -625,7 +644,9 @@ def _cost(self): if p is None: continue pumping += p.power_utility.rate - self.power_utility.rate = pumping + + self.power_utility.rate += pumping + self.power_utility.consumption += scraper_power class IdealClarifier(SanUnit): @@ -1124,6 +1145,12 @@ def _cost(self): base_power_scraper = 2.75 # in kW scraper_power = D['Number of clarifiers']*base_power_scraper*(clarifier_flow/base_flow_scraper)**0.6 + # v notch weir + # Source: https://www.alibaba.com/product-detail/50mm-Tube-Settler-Media-Modules-Inclined_1600835845218.html?spm=a2700.galleryofferlist.normal_offer.d_title.69135ff6o4kFPb + base_cost_v_notch_weir = 6888 + base_flow_v_notch_weir = 10 # in m3/hr + C['v notch weir'] = D['Number of clarifiers']*base_cost_v_notch_weir*(clarifier_flow/base_flow_v_notch_weir)**0.6 + # Pump (construction and maintainance) pumps = self.pumps add_OPEX = self.add_OPEX @@ -1153,7 +1180,6 @@ def _cost(self): if p is None: continue pumping += p.power_utility.rate - self.power_utility.consumption += pumping - + self.power_utility.consumption += pumping self.power_utility.consumption += scraper_power \ No newline at end of file From 5f798924a92b1407d0e53c1aa6917487e6793cb6 Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Wed, 26 Jul 2023 12:33:31 +0530 Subject: [PATCH 170/424] Finalized pump settings in SC --- qsdsan/sanunits/_clarifier.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index 233deeba..30413ba0 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -106,7 +106,7 @@ class FlatBottomCircularClarifier(SanUnit): wall_concrete_unit_cost = 650 / 0.765 # $/m3, 0.765 is to convert from $/yd3 stainless_steel_unit_cost=1.8 # $/kg (Taken from Joy's METAB code) https://www.alibaba.com/product-detail/brushed-stainless-steel-plate-304l-stainless_1600391656401.html?spm=a2700.details.0.0.230e67e6IKwwFd - pumps = ('ras', 'was') + pumps = ('ras', 'was',) def __init__(self, ID='', ins=None, outs=(), thermo=None, init_with='WasteStream', underflow=2000, wastage=385, @@ -501,8 +501,8 @@ def _design_pump(self): 'was': self._was, } - type_dct = dict.fromkeys(pumps, 'sludge', 'sludge') - inputs_dct = dict.fromkeys(pumps, (1,), (1,)) + type_dct = dict.fromkeys(pumps, 'sludge') + inputs_dct = dict.fromkeys(pumps, (1,)) for i in pumps: if hasattr(self, f'{i}_pump'): @@ -516,7 +516,7 @@ def _design_pump(self): Q_mgd=None, add_inputs=inputs_dct[i], capacity_factor=capacity_factor, include_pump_cost=True, - include_building_cost=True, + include_building_cost=False, include_OM_cost=True, ) setattr(self, f'{i}_pump', pump) @@ -1037,7 +1037,6 @@ def _design_pump(self): else: ID = f'{ID}_{i}' capacity_factor=1 - # No. of pumps = No. of influents pump = WWTpump( ID=ID, ins=ins_dct[i], pump_type=type_dct[i], Q_mgd=None, add_inputs=inputs_dct[i], From 942d58e86b5b2c05d33840ab9be64952b71281fc Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Wed, 26 Jul 2023 13:14:02 +0530 Subject: [PATCH 171/424] Made several necessary changes --- qsdsan/sanunits/_sludge_treatment.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py index 268028da..444a000d 100644 --- a/qsdsan/sanunits/_sludge_treatment.py +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -132,6 +132,7 @@ def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, init_with='WasteStream', F_BM_default=None, thickener_perc=7, TSS_removal_perc=98, solids_loading_rate = 75, h_cylindrical=2, upflow_velocity=43.2, **kwargs): + SanUnit.__init__(self, ID, ins, outs, thermo, isdynamic=isdynamic, init_with=init_with, F_BM_default=F_BM_default) @@ -405,7 +406,7 @@ def _design_pump(self): 'inf': self._inf, } - type_dct = dict.fromkeys(pumps, '') + type_dct = dict.fromkeys(pumps, 'sludge') inputs_dct = dict.fromkeys(pumps, (1,)) for i in pumps: @@ -468,9 +469,11 @@ def _design(self): D['Center feed diameter'] = ((4*Center_feed_area)/3.14)**(1/2) # Sanity check: Diameter of the center feed lies between 15-25% of tank diameter # Amount of concrete required - thickness_concrete_wall = 3 # in m (!! NEED A RELIABLE SOURCE !!) + D_tank = D['Cylindrical depth']*39.37 # m to inches + # Thickness of the wall concrete, [m]. Default to be minimum of 1 ft with 1 in added for every ft of depth over 12 ft. + thickness_concrete_wall = (1 + max(D_tank-12, 0)/12)*0.3048 # from feet to m inner_diameter = D['Cylindrical diameter'] - outer_diameter = inner_diameter + thickness_concrete_wall + outer_diameter = inner_diameter + 2*thickness_concrete_wall volume_cylindercal_wall = (3.14*D['Cylindrical depth']/4)*(outer_diameter**2 - inner_diameter**2) volume_conical_wall = (3.14/3)*(D['Conical depth']/4)*(outer_diameter**2 - inner_diameter**2) @@ -478,7 +481,7 @@ def _design(self): # Amount of metal required for center feed thickness_metal_wall = 0.5 # in m (!! NEED A RELIABLE SOURCE !!) inner_diameter_center_feed = D['Center feed diameter'] - outer_diameter_center_feed = inner_diameter_center_feed + thickness_metal_wall + outer_diameter_center_feed = inner_diameter_center_feed + 2*thickness_metal_wall volume_center_feed = (3.14*D['Center feed depth']/4)*(outer_diameter_center_feed**2 - inner_diameter_center_feed **2) density_ss = 7930 # kg/m3, 18/8 Chromium D['Stainless steel'] = volume_center_feed*density_ss # in kg @@ -590,6 +593,7 @@ class DewateringUnit(Thickener): # Costs stainless_steel_unit_cost=1.8 # $/kg (Taken from Joy's METAB code) https://www.alibaba.com/product-detail/brushed-stainless-steel-plate-304l-stainless_1600391656401.html?spm=a2700.details.0.0.230e67e6IKwwFd screw_conveyor_unit_cost_by_weight = 1800/800 # $/kg (Source: https://www.alibaba.com/product-detail/Engineers-Available-Service-Stainless-Steel-U_60541536633.html?spm=a2700.galleryofferlist.normal_offer.d_title.1fea1f65v6R3OQ&s=p) + polymer_cost_by_weight = 5 # !!!Placeholder value!!! def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, init_with='WasteStream', F_BM_default=None, thickener_perc=28, TSS_removal_perc=98, @@ -657,8 +661,8 @@ def _design_pump(self): Q_mgd=None, add_inputs=inputs_dct[i], capacity_factor=capacity_factor, include_pump_cost=True, - include_building_cost=False, - include_OM_cost=False, + include_building_cost=True, + include_OM_cost=True, ) setattr(self, f'{i}_pump', pump) @@ -737,9 +741,9 @@ def _cost(self): C = self.baseline_purchase_costs # Construction of concrete and stainless steel walls - C['Bowl stainless steel'] = D['Stainless steel for bowl']*self.stainless_steel_unit_cost - C['Conveyor stainless steel'] = D['Stainless steel for conveyor']*self.screw_conveyor_unit_cost_by_weight - + C['Bowl stainless steel'] = D['Number of centrifuges']*D['Stainless steel for bowl']*self.stainless_steel_unit_cost + C['Conveyor stainless steel'] = D['Number of centrifuges']*D['Stainless steel for conveyor']*self.screw_conveyor_unit_cost_by_weight + C['Polymer'] = D['Number of centrifuges']*D['Polymer feed rate']*self.polymer_cost_by_weight # Pump (construction and maintainance) pumps = self.pumps add_OPEX = self.add_OPEX From 6d6eaf8937e9cd39a73aaaa2d137279d03aaadbe Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Wed, 26 Jul 2023 13:23:12 +0530 Subject: [PATCH 172/424] Updated pump setting in thickener --- qsdsan/sanunits/_sludge_treatment.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py index 444a000d..177c8b76 100644 --- a/qsdsan/sanunits/_sludge_treatment.py +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -126,7 +126,7 @@ class Thickener(SanUnit): wall_concrete_unit_cost = 650 / 0.765 # $/m3, 0.765 is to convert from $/yd3 stainless_steel_unit_cost=1.8 # $/kg (Taken from Joy's METAB code) https://www.alibaba.com/product-detail/brushed-stainless-steel-plate-304l-stainless_1600391656401.html?spm=a2700.details.0.0.230e67e6IKwwFd - pumps = ('inf',) + pumps = ('sludge',) def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, init_with='WasteStream', F_BM_default=None, thickener_perc=7, @@ -143,6 +143,8 @@ def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, self.upflow_velocity = upflow_velocity self._mixed = WasteStream(thermo=thermo) + self._sludge = self.outs[0].copy(f'{ID}_sludge') + @property def thickener_perc(self): '''tp is the percentage of Suspended Sludge in the underflow of the thickener''' @@ -400,10 +402,10 @@ def yt(t, QC_ins, dQC_ins): def _design_pump(self): ID, pumps = self.ID, self.pumps - self._inf.copy_like(self._mixed) + self._sludge.copy_like(self.outs[0]) ins_dct = { - 'inf': self._inf, + 'sludge': self._sludge, } type_dct = dict.fromkeys(pumps, 'sludge') From 4c4b9c4f648e8b7a29d815df62366a23ff7adea4 Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Wed, 26 Jul 2023 17:59:55 +0530 Subject: [PATCH 173/424] Updated cost of mechanical thickener Allowing multiple thickener units, and added cost of equipment (scraper). --- qsdsan/sanunits/_sludge_treatment.py | 29 +++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py index 177c8b76..4665e551 100644 --- a/qsdsan/sanunits/_sludge_treatment.py +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -131,7 +131,7 @@ class Thickener(SanUnit): def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, init_with='WasteStream', F_BM_default=None, thickener_perc=7, TSS_removal_perc=98, solids_loading_rate = 75, h_cylindrical=2, - upflow_velocity=43.2, **kwargs): + upflow_velocity=43.2, design_flow = 40, **kwargs): SanUnit.__init__(self, ID, ins, outs, thermo, isdynamic=isdynamic, init_with=init_with, F_BM_default=F_BM_default) @@ -141,6 +141,7 @@ def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, self.solids_loading_rate = solids_loading_rate self.h_cylindrical = h_cylindrical self.upflow_velocity = upflow_velocity + self.design_flow = design_flow # 0.25 MGD = 40 m3/hr self._mixed = WasteStream(thermo=thermo) self._sludge = self.outs[0].copy(f'{ID}_sludge') @@ -443,8 +444,9 @@ def _design(self): self._mixed.mix_from(self.ins) D = self.design_results + D['Number of thickeners'] = np.ceil(self._mixed.get_total_flow('m3/hr')/self.design_flow) D['slr'] = self.solids_loading_rate # in (kg/day)/m2 - D['Daily mass of solids handled'] = (self._mixed.get_TSS()/1000)*self._mixed.get_total_flow('m3/hr')*24 # (mg/L)*[1/1000(kg*L)/(mg*m3)](m3/hr)*(24hr/day) = (kg/day) + D['Daily mass of solids handled'] = ((self._mixed.get_TSS()/1000)*self._mixed.get_total_flow('m3/hr')*24)/D['Number of thickeners'] # (mg/L)*[1/1000(kg*L)/(mg*m3)](m3/hr)*(24hr/day) = (kg/day) D['Surface area'] = D['Daily mass of solids handled']/D['slr'] # in m2 # design['Hydraulic_Loading'] = (self.ins[0].F_vol*24)/design['Area'] #in m3/(m2*day) @@ -494,13 +496,28 @@ def _design(self): D['Pump stainless steel'] = pumps def _cost(self): + + self._mixed.mix_from(self.ins) D = self.design_results C = self.baseline_purchase_costs # Construction of concrete and stainless steel walls - C['Wall concrete'] = D['Volume of concrete wall']*self.wall_concrete_unit_cost - C['Wall stainless steel'] = D['Stainless steel']*self.stainless_steel_unit_cost + C['Wall concrete'] = D['Number of thickeners']*D['Volume of concrete wall']*self.wall_concrete_unit_cost + C['Wall stainless steel'] = D['Number of thickeners']*D['Stainless steel']*self.stainless_steel_unit_cost + + # Cost of equipment + + # Source of scaling exponents: Process Design and Economics for Biochemical Conversion of Lignocellulosic Biomass to Ethanol by NREL. + + # Scraper + # Source: https://www.alibaba.com/product-detail/Peripheral-driving-clarifier-mud-scraper-waste_1600891102019.html?spm=a2700.details.0.0.47ab45a4TP0DLb + base_cost_scraper = 2500 + base_flow_scraper = 1 # in m3/hr (!!! Need to know whether this is for solids or influent !!!) + clarifier_flow = self._mixed.get_total_flow('m3/hr')/D['Number of thickeners'] + C['Scraper'] = D['Number of clarifiers']*base_cost_scraper*(clarifier_flow/base_flow_scraper)**0.6 + base_power_scraper = 2.75 # in kW + scraper_power = D['Number of clarifiers']*base_power_scraper*(clarifier_flow/base_flow_scraper)**0.6 # Pump (construction and maintainance) pumps = self.pumps @@ -531,7 +548,9 @@ def _cost(self): if p is None: continue pumping += p.power_utility.rate - self.power_utility.rate = pumping + + self.power_utility.rate += pumping + self.power_utility.rate += scraper_power class DewateringUnit(Thickener): From b58f78ce24a1e2edbb50c9055585b06117e13382 Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Sun, 6 Aug 2023 11:52:04 +0530 Subject: [PATCH 174/424] Updated number of clarifiers Updated number of clarifiers based on design flow which in turn is based on total flow --- qsdsan/sanunits/_clarifier.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index 30413ba0..a7a60b74 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -1064,10 +1064,19 @@ def _design(self): self._mixed.mix_from(self.ins) D = self.design_results + total_flow = self._mixed.get_total_flow('m3/hr') # Assuming the capacity of one clarifier is 20 MGD = (20*3785.4118) m3/day = 3155 m3/hr - design_flow = self.design_flow # m3/hr - D['Number of clarifiers'] = np.ceil(self._mixed.get_total_flow('m3/hr')/design_flow) + if total_flow <= 1580: + design_flow = 790 + elif total_flow >1580 and total_flow <= 4730: + design_flow = 2365 + elif total_flow > 4730 and total_flow <= 15770: + design_flow = 3940 + else: + design_flow = 5520 + + D['Number of clarifiers'] = np.ceil(total_flow/design_flow) total_volume = 24*self._HRT*design_flow #in m3 working_volume = total_volume/0.8 # Assume 80% working volume From 956398bd8c8d9b40936c3fda1c78b78b7c59e710 Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Sun, 6 Aug 2023 12:25:01 +0530 Subject: [PATCH 175/424] # clarifiers (SC) Made changes related to number of clarifiers for secondary sedimentation too --- qsdsan/sanunits/_clarifier.py | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index a7a60b74..1c627db5 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -531,9 +531,21 @@ def _design_pump(self): return pipe_ss, pump_ss def _design(self): - + self._mixed.mix_from(self.ins) + D = self.design_results + total_flow = self._mixed.get_total_flow('m3/hr') + + if total_flow <= 1580: # 10 MGD + design_flow = 790 # 5 MGD + elif total_flow >1580 and total_flow <= 4730: # Between 10 and 30 MGD + design_flow = 2365 # 15 MGD + elif total_flow > 4730 and total_flow <= 15770: # Between 30 and 100 MGD + design_flow = 3940 # 25 MGD + else: + design_flow = 5520 # 35 MGD + D = self.design_results # Assuming the capacity of one clarifier is 20 MGD = (20*3785.4118) m3/day = 3155 m3/hr design_flow = 3155 # m3/hr @@ -863,7 +875,7 @@ class PrimaryClarifier(SanUnit): def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, init_with='WasteStream', Hydraulic_Retention_Time=0.04268, ratio_uf=0.007, f_corr=0.65, cylindrical_depth = 5, upflow_velocity = 43.2, - design_flow = 3155, F_BM_default=default_F_BM, **kwargs): + F_BM_default=default_F_BM, **kwargs): SanUnit.__init__(self, ID, ins, outs, thermo, isdynamic=isdynamic, init_with=init_with, F_BM_default=1) @@ -872,7 +884,6 @@ def __init__(self, ID='', ins=None, outs=(), thermo=None, self.f_corr = f_corr self.cylindrical_depth = cylindrical_depth # in m self.upflow_velocity = upflow_velocity # in m/hr (converted from 12 mm/sec) - self.design_flow = design_flow # 20 MGD = 3155 m3/hr self._mixed = self.ins[0].copy(f'{ID}_mixed') self._sludge = self.outs[1].copy(f'{ID}_sludge') @@ -1066,15 +1077,14 @@ def _design(self): D = self.design_results total_flow = self._mixed.get_total_flow('m3/hr') - # Assuming the capacity of one clarifier is 20 MGD = (20*3785.4118) m3/day = 3155 m3/hr - if total_flow <= 1580: - design_flow = 790 - elif total_flow >1580 and total_flow <= 4730: - design_flow = 2365 - elif total_flow > 4730 and total_flow <= 15770: - design_flow = 3940 + if total_flow <= 1580: # 10 MGD + design_flow = 790 # 5 MGD + elif total_flow >1580 and total_flow <= 4730: # Between 10 and 30 MGD + design_flow = 2365 # 15 MGD + elif total_flow > 4730 and total_flow <= 15770: # Between 30 and 100 MGD + design_flow = 3940 # 25 MGD else: - design_flow = 5520 + design_flow = 5520 # 35 MGD D['Number of clarifiers'] = np.ceil(total_flow/design_flow) From c0c6f06843ced7f7c8d4b79b3f9072a29624be5b Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Mon, 7 Aug 2023 07:36:48 +0530 Subject: [PATCH 176/424] # pumps updated in PC Cost of multiple pumps included based on number of clarifiers. The 'Q_mgd' argument in the WWTpump is changed. --- qsdsan/sanunits/_clarifier.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index 1c627db5..13328206 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -1011,6 +1011,7 @@ def yt(t, QC_ins, dQC_ins): _units = { + 'Number of clarifiers': '?', 'Cylindrical volume': 'm3', 'Cylindrical depth': 'm', 'Cylindrical diameter': 'm', @@ -1026,7 +1027,8 @@ def yt(t, QC_ins, dQC_ins): 'Volume of concrete wall': 'm3', 'Stainless steel': 'kg', 'Pump pipe stainless steel' : 'kg', - 'Pump stainless steel': 'kg' + 'Pump stainless steel': 'kg', + 'Number of pumps': '?' } @@ -1040,6 +1042,10 @@ def _design_pump(self): type_dct = dict.fromkeys(pumps, 'sludge') inputs_dct = dict.fromkeys(pumps, (1,),) + + D = self.design_results + influent_Q = self._sludge.get_total_flow('m3/hr')/D['Number of clarifiers'] + influent_Q_mgd = influent_Q*0.00634 # m3/hr to MGD for i in pumps: if hasattr(self, f'{i}_pump'): @@ -1050,7 +1056,7 @@ def _design_pump(self): capacity_factor=1 pump = WWTpump( ID=ID, ins=ins_dct[i], pump_type=type_dct[i], - Q_mgd=None, add_inputs=inputs_dct[i], + Q_mgd=influent_Q_mgd, add_inputs=inputs_dct[i], capacity_factor=capacity_factor, include_pump_cost=True, include_building_cost=False, @@ -1062,8 +1068,6 @@ def _design_pump(self): for i in pumps: p = getattr(self, f'{i}_pump') p.simulate() - # try: p.simulate() - # except: breakpoint() p_design = p.design_results pipe_ss += p_design['Pump pipe stainless steel'] pump_ss += p_design['Pump stainless steel'] @@ -1139,6 +1143,8 @@ def _design(self): pipe, pumps = self._design_pump() D['Pump pipe stainless steel'] = pipe D['Pump stainless steel'] = pumps + # For primary clarifier + D['Number of pumps'] = D['Number of clarifiers'] def _cost(self): @@ -1186,10 +1192,10 @@ def _cost(self): opex_o += p_add_opex['Pump operating'] opex_m += p_add_opex['Pump maintenance'] - C['Pumps'] = pump_cost - C['Pump building'] = building_cost - add_OPEX['Pump operating'] = opex_o - add_OPEX['Pump maintenance'] = opex_m + C['Pumps'] = pump_cost*D['Number of pumps'] + C['Pump building'] = building_cost*D['Number of pumps'] + add_OPEX['Pump operating'] = opex_o*D['Number of pumps'] + add_OPEX['Pump maintenance'] = opex_m*D['Number of pumps'] # Power pumping = 0. @@ -1198,6 +1204,8 @@ def _cost(self): if p is None: continue pumping += p.power_utility.rate + + pumping = pumping*D['Number of pumps'] self.power_utility.consumption += pumping self.power_utility.consumption += scraper_power \ No newline at end of file From 9818f4c9e83ca466b87ed4e471ee7241b54bb097 Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Mon, 7 Aug 2023 12:52:34 +0530 Subject: [PATCH 177/424] Changes in thickener Made changes related to number of pumps in thickener, also added weir in equipments, and changed the default design flow based on metroWWTP. --- qsdsan/sanunits/_sludge_treatment.py | 39 +++++++++++++++++++--------- 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py index 4665e551..bec266b2 100644 --- a/qsdsan/sanunits/_sludge_treatment.py +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -131,7 +131,7 @@ class Thickener(SanUnit): def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, init_with='WasteStream', F_BM_default=None, thickener_perc=7, TSS_removal_perc=98, solids_loading_rate = 75, h_cylindrical=2, - upflow_velocity=43.2, design_flow = 40, **kwargs): + upflow_velocity=43.2, design_flow = 113, **kwargs): SanUnit.__init__(self, ID, ins, outs, thermo, isdynamic=isdynamic, init_with=init_with, F_BM_default=F_BM_default) @@ -141,7 +141,7 @@ def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, self.solids_loading_rate = solids_loading_rate self.h_cylindrical = h_cylindrical self.upflow_velocity = upflow_velocity - self.design_flow = design_flow # 0.25 MGD = 40 m3/hr + self.design_flow = design_flow # 0.60 MGD = 113 m3/hr self._mixed = WasteStream(thermo=thermo) self._sludge = self.outs[0].copy(f'{ID}_sludge') @@ -411,6 +411,10 @@ def _design_pump(self): type_dct = dict.fromkeys(pumps, 'sludge') inputs_dct = dict.fromkeys(pumps, (1,)) + + D = self.design_results + influent_Q = self._sludge.get_total_flow('m3/hr')/D['Number of thickeners'] + influent_Q_mgd = influent_Q*0.00634 # m3/hr to MGD for i in pumps: if hasattr(self, f'{i}_pump'): @@ -422,11 +426,11 @@ def _design_pump(self): # No. of pumps = No. of influents pump = WWTpump( ID=ID, ins= ins_dct[i], pump_type=type_dct[i], - Q_mgd=None, add_inputs=inputs_dct[i], + Q_mgd=influent_Q_mgd, add_inputs=inputs_dct[i], capacity_factor=capacity_factor, include_pump_cost=True, include_building_cost=False, - include_OM_cost=False, + include_OM_cost=True, ) setattr(self, f'{i}_pump', pump) @@ -452,7 +456,7 @@ def _design(self): # design['Hydraulic_Loading'] = (self.ins[0].F_vol*24)/design['Area'] #in m3/(m2*day) D['Cylindrical diameter'] = np.sqrt(4*D['Surface area']/np.pi) #in m D['Cylindrical depth'] = self.h_cylindrical #in m - D['Cylindrical volume'] = np.pi*np.square(D['Diameter']/2)*D['Cylindrical depth'] #in m3 + D['Cylindrical volume'] = np.pi*np.square(D['Cylindrical diameter']/2)*D['Cylindrical depth'] #in m3 D['Conical radius'] = D['Cylindrical diameter']/2 # The slope of the bottom conical floor lies between 1:10 to 1:12 @@ -495,6 +499,9 @@ def _design(self): D['Pump pipe stainless steel'] = pipe D['Pump stainless steel'] = pumps + #For thickeners + D['Number of pumps'] = D['Number of thickeners'] + def _cost(self): self._mixed.mix_from(self.ins) @@ -514,10 +521,16 @@ def _cost(self): # Source: https://www.alibaba.com/product-detail/Peripheral-driving-clarifier-mud-scraper-waste_1600891102019.html?spm=a2700.details.0.0.47ab45a4TP0DLb base_cost_scraper = 2500 base_flow_scraper = 1 # in m3/hr (!!! Need to know whether this is for solids or influent !!!) - clarifier_flow = self._mixed.get_total_flow('m3/hr')/D['Number of thickeners'] - C['Scraper'] = D['Number of clarifiers']*base_cost_scraper*(clarifier_flow/base_flow_scraper)**0.6 + thickener_flow = self._mixed.get_total_flow('m3/hr')/D['Number of thickeners'] + C['Scraper'] = D['Number of thickeners']*base_cost_scraper*(thickener_flow/base_flow_scraper)**0.6 base_power_scraper = 2.75 # in kW - scraper_power = D['Number of clarifiers']*base_power_scraper*(clarifier_flow/base_flow_scraper)**0.6 + scraper_power = D['Number of thickeners']*base_power_scraper*(thickener_flow/base_flow_scraper)**0.6 + + # v notch weir + # Source: https://www.alibaba.com/product-detail/50mm-Tube-Settler-Media-Modules-Inclined_1600835845218.html?spm=a2700.galleryofferlist.normal_offer.d_title.69135ff6o4kFPb + base_cost_v_notch_weir = 6888 + base_flow_v_notch_weir = 10 # in m3/hr + C['v notch weir'] = D['Number of thickeners']*base_cost_v_notch_weir*(thickener_flow/base_flow_v_notch_weir)**0.6 # Pump (construction and maintainance) pumps = self.pumps @@ -536,10 +549,10 @@ def _cost(self): opex_o += p_add_opex['Pump operating'] opex_m += p_add_opex['Pump maintenance'] - C['Pumps'] = pump_cost - C['Pump building'] = building_cost - add_OPEX['Pump operating'] = opex_o - add_OPEX['Pump maintenance'] = opex_m + C['Pumps'] = pump_cost*D['Number of thickeners'] + C['Pump building'] = building_cost*D['Number of thickeners'] + add_OPEX['Pump operating'] = opex_o*D['Number of thickeners'] + add_OPEX['Pump maintenance'] = opex_m*D['Number of thickeners'] # Power pumping = 0. @@ -549,6 +562,8 @@ def _cost(self): continue pumping += p.power_utility.rate + pumping = pumping*D['Number of thickeners'] + self.power_utility.rate += pumping self.power_utility.rate += scraper_power From 7582a09e3bf05a226dc977b25af5f884eb5c81be Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Mon, 7 Aug 2023 13:02:33 +0530 Subject: [PATCH 178/424] BM factor for thickeners Set bare module factors for thickener and dewatering unit --- qsdsan/sanunits/_sludge_treatment.py | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py index bec266b2..560a897b 100644 --- a/qsdsan/sanunits/_sludge_treatment.py +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -20,6 +20,20 @@ __all__ = ('Thickener', 'DewateringUnit', 'Incinerator') + +F_BM_pump = 1.18*(1 + 0.007/100) # 0.007 is for miscellaneous costs + +default_F_BM = { + 'Pumps': F_BM_pump, + 'Pump building': F_BM_pump, + } + +default_equipment_lifetime = { + 'Pumps': 15, + 'Pump pipe stainless steel': 15, + 'Pump stainless steel': 15, + } + class Thickener(SanUnit): """ @@ -129,12 +143,12 @@ class Thickener(SanUnit): pumps = ('sludge',) def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, - init_with='WasteStream', F_BM_default=None, thickener_perc=7, + init_with='WasteStream', F_BM_default=default_F_BM, thickener_perc=7, TSS_removal_perc=98, solids_loading_rate = 75, h_cylindrical=2, upflow_velocity=43.2, design_flow = 113, **kwargs): SanUnit.__init__(self, ID, ins, outs, thermo, isdynamic=isdynamic, - init_with=init_with, F_BM_default=F_BM_default) + init_with=init_with, F_BM_default=1) self.thickener_perc = thickener_perc self.TSS_removal_perc = TSS_removal_perc @@ -632,7 +646,7 @@ class DewateringUnit(Thickener): polymer_cost_by_weight = 5 # !!!Placeholder value!!! def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, - init_with='WasteStream', F_BM_default=None, thickener_perc=28, TSS_removal_perc=98, + init_with='WasteStream', F_BM_default=default_F_BM, thickener_perc=28, TSS_removal_perc=98, solids_feed_rate = 70, # specific_gravity_sludge=1.03, cake_density=965, @@ -640,7 +654,7 @@ def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, polymer_dosage = 0.0075, h_cylindrical=2, h_conical=1, **kwargs): Thickener.__init__(self, ID=ID, ins=ins, outs=outs, thermo=thermo, isdynamic=isdynamic, - init_with=init_with, F_BM_default=F_BM_default, thickener_perc=thickener_perc, + init_with=init_with, F_BM_default=1, thickener_perc=thickener_perc, TSS_removal_perc=TSS_removal_perc, **kwargs) # self._mixed = self.ins[0].copy(f'{ID}_mixed') From d3ab52860c6ab665c84b9014a8aeb7da145bc401 Mon Sep 17 00:00:00 2001 From: Yalin Li Date: Mon, 7 Aug 2023 10:08:38 -0400 Subject: [PATCH 179/424] fix doc bugs and minor style-related updates --- qsdsan/processes/_adm1_p_extension.py | 6 +- qsdsan/sanunits/_clarifier.py | 147 +++++++++--------- qsdsan/sanunits/_sludge_treatment.py | 211 +++++++++++++------------- 3 files changed, 188 insertions(+), 176 deletions(-) diff --git a/qsdsan/processes/_adm1_p_extension.py b/qsdsan/processes/_adm1_p_extension.py index 7d752f1b..217fc744 100644 --- a/qsdsan/processes/_adm1_p_extension.py +++ b/qsdsan/processes/_adm1_p_extension.py @@ -578,9 +578,9 @@ class ADM1_p_extension(CompiledProcesses): -------- >>> from qsdsan import processes as pc >>> cmps = pc.create_adm1_p_extension_cmps() - >>> adm1 = pc.ADM1_p_extension() - >>> adm1.show() - ADM1([hydrolysis_carbs, hydrolysis_proteins, hydrolysis_lipids, uptake_sugars, uptake_amino_acids, uptake_LCFA, uptake_valerate, uptake_butyrate, uptake_propionate, uptake_acetate, uptake_h2, decay_Xsu, decay_Xaa, decay_Xfa, decay_Xc4, decay_Xpro, decay_Xac, decay_Xh2, storage_Sva_in_XPHA, storage_Sbu_in_XPHA, storage_Spro_in_XPHA, storage_Sac_in_XPHA, lysis_XPAO, lysis_XPP, lysis_XPHA, h2_transfer, ch4_transfer, IC_transfer]) + >>> adm1_p = pc.ADM1_p_extension() + >>> adm1_p.show() + ADM1_p_extension([hydrolysis_carbs, hydrolysis_proteins, hydrolysis_lipids, uptake_sugars, uptake_amino_acids, uptake_LCFA, uptake_valerate, uptake_butyrate, uptake_propionate, uptake_acetate, uptake_h2, decay_Xsu, decay_Xaa, decay_Xfa, decay_Xc4, decay_Xpro, decay_Xac, decay_Xh2, storage_Sva_in_XPHA, storage_Sbu_in_XPHA, storage_Spro_in_XPHA, storage_Sac_in_XPHA, lysis_XPAO, lysis_XPP, lysis_XPHA, h2_transfer, ch4_transfer, IC_transfer]) References ---------- diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index 5fef3be4..3a452e2a 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -16,6 +16,7 @@ from .. import SanUnit, WasteStream import numpy as np from ..sanunits import WWTpump +from ..sanunits._pumping import default_F_BM as default_WWTpump_F_BM __all__ = ('FlatBottomCircularClarifier', 'IdealClarifier', @@ -134,6 +135,7 @@ def __init__(self, ID='', ins=None, outs=(), thermo=None, self._solubles = None self._X_comp = np.zeros(len(self.components)) self._dX_comp = self._X_comp.copy() + self._mixed = WasteStream(f'{ID}_mixed') header = self._state_header self._state_header = list(header) + [f'TSS{i+1} [mg/L]' for i in range(N_layer)] for attr, value in kwargs.items(): @@ -489,7 +491,7 @@ def _design_pump(self): ID, pumps = self.ID, self.pumps ins_dct = { - 'inf': self._inf, + 'inf': self._mixed, } type_dct = dict.fromkeys(pumps, '') @@ -614,6 +616,9 @@ def _cost(self): pumping += p.power_utility.rate self.power_utility.rate = pumping + +# %% + class IdealClarifier(SanUnit): _N_ins = 1 @@ -713,6 +718,16 @@ def _run(self): def _design(self): pass + +# %% + +# Asign a bare module of 1 to all +default_F_BM = { + 'Wall concrete': 1., + 'Wall stainless steel': 1., + } +default_F_BM.update(default_WWTpump_F_BM) + class PrimaryClarifier(SanUnit): """ @@ -736,6 +751,8 @@ class PrimaryClarifier(SanUnit): The depth of the cylindrical portion of clarifier [in m]. upflow_velocity : float, optional Speed with which influent enters the center feed of the clarifier [m/hr]. The default is 43.2. + F_BM : dict + Equipment bare modules. Examples -------- @@ -745,68 +762,60 @@ class PrimaryClarifier(SanUnit): >>> set_thermo(cmps_test) >>> ws = WasteStream('ws', S_F = 10, S_NH4 = 20, X_OHO = 15, H2O=1000) >>> from qsdsan.sanunits import PrimaryClarifier - >>> ps = PrimaryClarifier(ID='PC', ins= (ws), outs=()) - >>> ps._run() - >>> uf, of = ps.outs - >>> uf.imass['X_OHO']/ws.imass['X_OHO'] # doctest: +ELLIPSIS + >>> PC = PrimaryClarifier(ID='PC', ins= (ws,), outs=('eff', 'sludge')) + >>> PC.simulate() + >>> eff, sludge = PC.outs + >>> eff.imass['X_OHO']/ws.imass['X_OHO'] # doctest: +ELLIPSIS 0.280... - >>> ps + >>> PC.show() PrimaryClarifier: PC ins... [0] ws - phase: 'l', T: 298.15 K, P: 101325 Pa - flow (g/hr): S_F 1e+04 - S_NH4 2e+04 - X_OHO 1.5e+04 - H2O 1e+06 + phase: 'l', T: 298.15 K, P: 101325 Pa + flow (g/hr): S_F 1e+04 + S_NH4 2e+04 + X_OHO 1.5e+04 + H2O 1e+06 WasteStream-specific properties: pH : 7.0 - COD : 23643.1 mg/L - BOD : 14819.1 mg/L - TC : 8218.3 mg/L - TOC : 8218.3 mg/L - TN : 20167.1 mg/L - TP : 364.1 mg/L - TK : 67.6 mg/L - [1] ws3 - phase: 'l', T: 298.15 K, P: 101325 Pa - flow: 0 - WasteStream-specific properties: None for empty waste streams - [2] ws4 - phase: 'l', T: 298.15 K, P: 101325 Pa - flow: 0 - WasteStream-specific properties: None for empty waste streams + COD : 23873.0 mg/L + BOD : 14963.2 mg/L + TC : 8298.3 mg/L + TOC : 8298.3 mg/L + TN : 20363.2 mg/L + TP : 367.6 mg/L + TK : 68.3 mg/L outs... - [0] ws1 - phase: 'l', T: 298.15 K, P: 101325 Pa - flow (g/hr): S_F 70 - S_NH4 140 - X_OHO 4.2e+03 - H2O 7e+03 + [0] eff + phase: 'l', T: 298.15 K, P: 101325 Pa + flow (g/hr): S_F 70 + S_NH4 140 + X_OHO 4.2e+03 + H2O 7e+03 WasteStream-specific properties: pH : 7.0 - COD : 425826.3 mg/L - BOD : 242338.7 mg/L - TC : 155531.6 mg/L - TOC : 155531.6 mg/L - TN : 42767.0 mg/L - TP : 8027.9 mg/L - TK : 1997.1 mg/L - [1] ws2 - phase: 'l', T: 298.15 K, P: 101325 Pa - flow (g/hr): S_F 9.93e+03 - S_NH4 1.99e+04 - X_OHO 1.08e+04 - H2O 9.93e+05 + COD : 428873.3 mg/L + BOD : 244072.7 mg/L + TC : 156644.5 mg/L + TOC : 156644.5 mg/L + TN : 43073.0 mg/L + TP : 8085.4 mg/L + TK : 2011.4 mg/L + [1] sludge + phase: 'l', T: 298.15 K, P: 101325 Pa + flow (g/hr): S_F 9.93e+03 + S_NH4 1.99e+04 + X_OHO 1.08e+04 + H2O 9.93e+05 WasteStream-specific properties: pH : 7.0 - COD : 19789.4 mg/L - BOD : 12639.0 mg/L - TC : 6806.8 mg/L - TOC : 6806.8 mg/L - TN : 19950.5 mg/L - TP : 290.7 mg/L - TK : 49.2 mg/L + COD : 19982.3 mg/L + BOD : 12762.2 mg/L + TC : 6873.2 mg/L + TOC : 6873.2 mg/L + TN : 20145.0 mg/L + TP : 293.5 mg/L + TK : 49.6 mg/L References ---------- @@ -829,16 +838,15 @@ class PrimaryClarifier(SanUnit): def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, init_with='WasteStream', Hydraulic_Retention_Time=0.04268, ratio_uf=0.007, f_corr=0.65, cylindrical_depth = 3, upflow_velocity = 43.2, - F_BM_default=None, **kwargs): - + F_BM=default_F_BM, **kwargs): SanUnit.__init__(self, ID, ins, outs, thermo, isdynamic=isdynamic, - init_with=init_with, F_BM_default=F_BM_default) + init_with=init_with) self.Hydraulic_Retention_Time = Hydraulic_Retention_Time #in days self.ratio_uf = ratio_uf self.f_corr = f_corr self.cylindrical_depth = cylindrical_depth # in m self.upflow_velocity = upflow_velocity # in m/hr (converted from 12 mm/sec) - + self.F_BM.update(default_F_BM) self._mixed = self.ins[0].copy(f'{ID}_mixed') self._inf = self.ins[0].copy(f'{ID}_inf') @@ -890,23 +898,24 @@ def _f_i(self): return f_i def _run(self): - uf, of = self.outs + eff, sludge = self.outs cmps = self.components - self._mixed.mix_from(self.ins) + mixed = self._mixed + mixed.mix_from(self.ins) r = self._r f_i = self._f_i() - Xs = (1 - f_i)*self._mixed.mass*cmps.x - Xe = (f_i)*self._mixed.mass*cmps.x + Xs = (1 - f_i)*mixed.mass*cmps.x + Xe = (f_i)*mixed.mass*cmps.x - Zs = r*self._mixed.mass*cmps.s - Ze = (1-r)*self._mixed.mass*cmps.s + Zs = r*mixed.mass*cmps.s + Ze = (1-r)*mixed.mass*cmps.s Ce = Ze + Xe Cs = Zs + Xs - of.set_flow(Ce,'kg/hr') - uf.set_flow(Cs,'kg/hr') + sludge.set_flow(Ce,'kg/hr') + eff.set_flow(Cs,'kg/hr') def _init_state(self): # if multiple wastestreams exist then concentration and total inlow @@ -916,11 +925,11 @@ def _init_state(self): self._state = np.append(Qs @ Cs / Qs.sum(), Qs.sum()) self._dstate = self._state * 0. - uf, of = self.outs - s_flow = uf.F_vol/(uf.F_vol+of.F_vol) - denominator = uf.mass + of.mass + eff, sludge = self.outs + s_flow = eff.F_vol/(eff.F_vol+sludge.F_vol) + denominator = eff.mass + sludge.mass denominator += (denominator == 0) - s = uf.mass/denominator + s = eff.mass/denominator self._sludge = np.append(s/s_flow, s_flow) self._effluent = np.append((1-s)/(1-s_flow), 1-s_flow) @@ -1037,7 +1046,7 @@ def _design(self): # Sidewater depth of a cylindrical clarifier lies between 2.5-5m D['Cylindrical depth'] = self.cylindrical_depth # in m # The tank diameter can lie anywhere between 3 m to 100 m - D['Cylindrical diameter'] = (4*D['Cylindrical volume']/(3.14*D['Cylindrical depth']))**(1/2) # in m + D['Cylindrical diameter'] = (4*working_volume/(3.14*D['Cylindrical depth']))**(1/2) # in m D['Conical radius'] = D['Cylindrical diameter']/2 # The slope of the bottom conical floor lies between 1:10 to 1:12 diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py index 56ce2afa..5e66cbd6 100644 --- a/qsdsan/sanunits/_sludge_treatment.py +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -16,15 +16,22 @@ from .. import SanUnit, WasteStream import numpy as np from ..sanunits import WWTpump +from ..sanunits._pumping import default_F_BM as default_WWTpump_F_BM __all__ = ('Thickener', 'DewateringUnit', 'Incinerator') +# Asign a bare module of 1 to all +default_F_BM = { + 'Wall concrete': 1., + 'Wall stainless steel': 1., + } +default_F_BM.update(default_WWTpump_F_BM) + class Thickener(SanUnit): """ Thickener based on BSM2 Layout. [1] - ---------- ID : str ID for the Thickener. The default is ''. @@ -44,70 +51,71 @@ class Thickener(SanUnit): Height of cylinder forming the thickener.[2] upflow_velocity : float, optional Speed with which influent enters the center feed of the clarifier [m/hr]. The default is 43.2. + F_BM : dict + Equipment bare modules. Examples -------- - >>> from qsdsan import set_thermo, Components, WasteStream >>> cmps = Components.load_default() >>> cmps_test = cmps.subgroup(['S_F', 'S_NH4', 'X_OHO', 'H2O']) >>> set_thermo(cmps_test) >>> ws = WasteStream('ws', S_F = 10, S_NH4 = 20, X_OHO = 15, H2O=1000) >>> from qsdsan.sanunits import Thickener - >>> ps = Thickener(ID='TC', ins= (ws), outs=('Sludge', 'Effluent')) - >>> ps._run() - >>> uf, of = ps.outs - >>> uf.imass['X_OHO']/ws.imass['X_OHO'] # doctest: +ELLIPSIS + >>> TC = Thickener(ID='TC', ins= (ws), outs=('sludge', 'effluent')) + >>> TC.simulate() + >>> sludge, effluent = TC.outs + >>> sludge.imass['X_OHO']/ws.imass['X_OHO'] 0.98 - >>> ps + >>> TC.show() # doctest: +ELLIPSIS Thickener: TC ins... [0] ws - phase: 'l', T: 298.15 K, P: 101325 Pa - flow (g/hr): S_F 1e+04 - S_NH4 2e+04 - X_OHO 1.5e+04 - H2O 1e+06 + phase: 'l', T: 298.15 K, P: 101325 Pa + flow (g/hr): S_F 1e+04 + S_NH4 2e+04 + X_OHO 1.5e+04 + H2O 1e+06 WasteStream-specific properties: pH : 7.0 - COD : 23643.1 mg/L - BOD : 14819.1 mg/L - TC : 8218.3 mg/L - TOC : 8218.3 mg/L - TN : 20167.1 mg/L - TP : 364.1 mg/L - TK : 67.6 mg/L + COD : 23873.0 mg/L + BOD : 14963.2 mg/L + TC : 8298.3 mg/L + TOC : 8298.3 mg/L + TN : 20363.2 mg/L + TP : 367.6 mg/L + TK : 68.3 mg/L outs... - [0] Sludge - phase: 'l', T: 298.15 K, P: 101325 Pa - flow (g/hr): S_F 8.46e+03 - S_NH4 1.69e+04 - X_OHO 1.47e+04 - H2O 8.46e+05 + [0] sludge + phase: 'l', T: 298.15 K, P: 101325 Pa + flow (g/hr): S_F 1.56e+03 + S_NH4 3.11e+03 + X_OHO 1.47e+04 + H2O 1.56e+05 WasteStream-specific properties: pH : 7.0 - COD : 25857.3 mg/L - BOD : 16071.7 mg/L - TC : 9029.4 mg/L - TOC : 9029.4 mg/L - TN : 20291.5 mg/L - TP : 406.3 mg/L - TK : 78.3 mg/L - [1] Effluent - phase: 'l', T: 298.15 K, P: 101325 Pa - flow (g/hr): S_F 1.54e+03 - S_NH4 3.08e+03 - X_OHO 300 - H2O 1.54e+05 + COD : 95050.4 mg/L + BOD : 55228.4 mg/L + TC : 34369.6 mg/L + TOC : 34369.6 mg/L + TN : 24354.4 mg/L + TP : 1724.0 mg/L + TK : 409.8 mg/L + [1] effluent + phase: 'l', T: 298.15 K, P: 101325 Pa + flow (g/hr): S_F 8.44e+03 + S_NH4 1.69e+04 + X_OHO 300 + H2O 8.44e+05 WasteStream-specific properties: pH : 7.0 - COD : 11387.0 mg/L - BOD : 7885.7 mg/L - TC : 3729.1 mg/L - TOC : 3729.1 mg/L - TN : 19478.3 mg/L - TP : 130.6 mg/L - TK : 8.8 mg/L + COD : 9978.2 mg/L + BOD : 7102.9 mg/L + TC : 3208.8 mg/L + TOC : 3208.8 mg/L + TN : 19584.1 mg/L + TP : 102.9 mg/L + TK : 1.6 mg/L References ---------- @@ -131,16 +139,16 @@ class Thickener(SanUnit): def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, init_with='WasteStream', F_BM_default=None, thickener_perc=7, TSS_removal_perc=98, solids_loading_rate = 75, h_cylindrical=2, - upflow_velocity=43.2, **kwargs): + upflow_velocity=43.2, F_BM=default_F_BM, **kwargs): SanUnit.__init__(self, ID, ins, outs, thermo, isdynamic=isdynamic, - init_with=init_with, F_BM_default=F_BM_default) - + init_with=init_with) self.thickener_perc = thickener_perc self.TSS_removal_perc = TSS_removal_perc self.solids_loading_rate = solids_loading_rate self.h_cylindrical = h_cylindrical self.upflow_velocity = upflow_velocity - self.mixed = WasteStream(thermo=thermo) + self.F_BM.update(F_BM) + self._mixed = WasteStream(f'{ID}_mixed') @property def thickener_perc(self): @@ -184,8 +192,8 @@ def TSS_removal_perc(self, TSS_rmv): @property def thickener_factor(self): - self.mixed.mix_from(self.ins) - inf = self.mixed + self._mixed.mix_from(self.ins) + inf = self._mixed _cal_thickener_factor = self._cal_thickener_factor if not self.ins: return elif inf.isempty(): return @@ -196,8 +204,8 @@ def thickener_factor(self): @property def thinning_factor(self): - self.mixed.mix_from(self.ins) - inf = self.mixed + self._mixed.mix_from(self.ins) + inf = self._mixed TSS_in = inf.get_TSS() _cal_thickener_factor = self._cal_thickener_factor thickener_factor = _cal_thickener_factor(TSS_in) @@ -238,10 +246,9 @@ def _update_parameters(self): self.updated_Qu_factor, self.updated_thinning_factor = _cal_parameters(updated_thickener_factor) def _run(self): - - self.mixed.mix_from(self.ins) - inf = self.mixed - uf, of = self.outs + self._mixed.mix_from(self.ins) + inf = self._mixed + sludge, eff = self.outs cmps = self.components TSS_rmv = self._TSS_rmv @@ -261,8 +268,8 @@ def _run(self): Ce = Ze + Xe Cs = Zs + Xs - of.set_flow(Ce,'kg/hr') - uf.set_flow(Cs,'kg/hr') + eff.set_flow(Ce,'kg/hr') + sludge.set_flow(Cs,'kg/hr') def _init_state(self): @@ -397,17 +404,11 @@ def yt(t, QC_ins, dQC_ins): } def _design_pump(self): - ID, pumps = self.ID, self.pumps - - self.mixed.mix_from(self.ins) - - inf = self.mixed - + inf = self._mixed ins_dct = { 'inf': inf, } - type_dct = dict.fromkeys(pumps, '') inputs_dct = dict.fromkeys(pumps, (1,)) @@ -439,18 +440,16 @@ def _design_pump(self): return pipe_ss, pump_ss def _design(self): - - self.mixed.mix_from(self.ins) - D = self.design_results D['slr'] = self.solids_loading_rate # in (kg/day)/m2 - D['Daily mass of solids handled'] = (self.mixed.get_TSS()/1000)*self.mixed.get_total_flow('m3/hr')*24 # (mg/L)*[1/1000(kg*L)/(mg*m3)](m3/hr)*(24hr/day) = (kg/day) + mixed = self._mixed + D['Daily mass of solids handled'] = (mixed.get_TSS()/1000)*mixed.get_total_flow('m3/hr')*24 # (mg/L)*[1/1000(kg*L)/(mg*m3)](m3/hr)*(24hr/day) = (kg/day) D['Surface area'] = D['Daily mass of solids handled']/D['slr'] # in m2 # design['Hydraulic_Loading'] = (self.ins[0].F_vol*24)/design['Area'] #in m3/(m2*day) D['Cylindrical diameter'] = np.sqrt(4*D['Surface area']/np.pi) #in m D['Cylindrical depth'] = self.h_cylindrical #in m - D['Cylindrical volume'] = np.pi*np.square(D['Diameter']/2)*D['Cylindrical depth'] #in m3 + D['Cylindrical volume'] = np.pi*np.square(D['Cylindrical diameter']/2)*D['Cylindrical depth'] #in m3 D['Conical radius'] = D['Cylindrical diameter']/2 # The slope of the bottom conical floor lies between 1:10 to 1:12 @@ -467,7 +466,7 @@ def _design(self): # of 10-13 mm/s and maximum velocity of 25-30 mm/s peak_flow_safety_factor = 2.5 # assumed based on average and maximum velocities D['Upflow velocity'] = self.upflow_velocity*peak_flow_safety_factor # in m/hr - Center_feed_area = self.mixed.get_total_flow('m3/hr')/D['Upflow velocity'] # in m2 + Center_feed_area = mixed.get_total_flow('m3/hr')/D['Upflow velocity'] # in m2 D['Center feed diameter'] = ((4*Center_feed_area)/3.14)**(1/2) # Sanity check: Diameter of the center feed lies between 15-25% of tank diameter # Amount of concrete required @@ -531,6 +530,9 @@ def _cost(self): pumping += p.power_utility.rate self.power_utility.rate = pumping + +# %% + class DewateringUnit(Thickener): """ @@ -615,6 +617,9 @@ def _design(self): design['Volume'] = np.pi*np.square(design['Diameter']/2)*(self.h_cylinderical + (self.h_conical/3)) #in m3 design['Curved Surface Area'] = np.pi*design['Diameter']/2*(2*self.h_cylinderical + np.sqrt(np.square(design['Diameter']/2) + np.square(self.h_conical))) #in m2 + +# %% + class Incinerator(SanUnit): """ @@ -640,8 +645,7 @@ class Incinerator(SanUnit): The default fuel is natural gas with calofific value of 50000 KJ/kg. Examples - -------- - + -------- >>> import qsdsan as qs >>> cmps = qs.Components.load_default() >>> CO2 = qs.Component.from_chemical('S_CO2', search_ID='CO2', particle_size='Soluble', degradability='Undegradable', organic=False) @@ -652,48 +656,47 @@ class Incinerator(SanUnit): >>> natural_gas = qs.WasteStream('nat_gas', phase='g', S_CH4=1000) >>> air = qs.WasteStream('air', phase='g', S_O2=210, S_N2=780, S_H2=10) >>> from qsdsan.sanunits import Incinerator - >>> ps = Incinerator(ID='PC', ins= (ws, air, natural_gas), outs=('flu_gas', 'ash'), - isdynamic=True) - >>> ps._run() - >>> ps - - Incinerator: PC + >>> Inc = Incinerator(ID='Inc', ins= (ws, air, natural_gas), outs=('flu_gas', 'ash'), + ... isdynamic=False) + >>> Inc.simulate() + >>> Inc.show() + Incinerator: Inc ins... [0] ws - phase: 'l', T: 298.15 K, P: 101325 Pa - flow (g/hr): S_F 1e+04 - S_NH4 2e+04 - X_OHO 1.5e+04 - H2O 1e+06 + phase: 'l', T: 298.15 K, P: 101325 Pa + flow (g/hr): S_F 1e+04 + S_NH4 2e+04 + X_OHO 1.5e+04 + H2O 1e+06 WasteStream-specific properties: pH : 7.0 - COD : 23643.1 mg/L - BOD : 14819.1 mg/L - TC : 8218.3 mg/L - TOC : 8218.3 mg/L - TN : 20167.1 mg/L - TP : 364.1 mg/L - TK : 67.6 mg/L + COD : 23873.0 mg/L + BOD : 14963.2 mg/L + TC : 8298.3 mg/L + TOC : 8298.3 mg/L + TN : 20363.2 mg/L + TP : 367.6 mg/L + TK : 68.3 mg/L [1] air - phase: 'g', T: 298.15 K, P: 101325 Pa - flow (g/hr): S_O2 2.1e+05 - S_N2 7.8e+05 - S_H2 1e+04 + phase: 'g', T: 298.15 K, P: 101325 Pa + flow (g/hr): S_O2 2.1e+05 + S_N2 7.8e+05 + S_H2 1e+04 WasteStream-specific properties: None for non-liquid waste streams [2] nat_gas - phase: 'g', T: 298.15 K, P: 101325 Pa - flow (g/hr): S_CH4 1e+06 + phase: 'g', T: 298.15 K, P: 101325 Pa + flow (g/hr): S_CH4 1e+06 WasteStream-specific properties: None for non-liquid waste streams outs... [0] flu_gas - phase: 'g', T: 298.15 K, P: 101325 Pa - flow (g/hr): H2O 1e+06 - S_N2 7.8e+05 - S_CO2 4.77e+05 + phase: 'g', T: 298.15 K, P: 101325 Pa + flow (g/hr): H2O 1e+06 + S_N2 7.8e+05 + S_CO2 2.67e+05 WasteStream-specific properties: None for non-liquid waste streams [1] ash - phase: 's', T: 298.15 K, P: 101325 Pa - flow (g/hr): X_Ig_ISS 2.58e+04 + phase: 's', T: 298.15 K, P: 101325 Pa + flow (g/hr): X_Ig_ISS 2.37e+05 WasteStream-specific properties: None for non-liquid waste streams References: From 1c8426833ce85b29a6d8d35f68fdf6239a571c9f Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Mon, 7 Aug 2023 20:59:34 +0530 Subject: [PATCH 180/424] Updated name of DU Changed name of 'DewateringUnit' to 'Centrifuge' --- qsdsan/sanunits/_sludge_treatment.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py index 99058af4..1f19faf8 100644 --- a/qsdsan/sanunits/_sludge_treatment.py +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -575,10 +575,10 @@ def _cost(self): # %% -class DewateringUnit(Thickener): - +class Centrifuge(Thickener): + """ - Dewatering Unit based on BSM2 Layout. [1] + Centrifuge based on BSM2 Layout. [1] Parameters ---------- @@ -641,7 +641,6 @@ class DewateringUnit(Thickener): def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, init_with='WasteStream', F_BM_default=default_F_BM, thickener_perc=28, TSS_removal_perc=98, - solids_feed_rate = 70, # specific_gravity_sludge=1.03, cake_density=965, g_factor=2500, rotational_speed = 40, LtoD = 4, @@ -835,8 +834,6 @@ class Incinerator(SanUnit): Please remember the order of influents as {wastestream, air, fuel} outs : class:`WasteStream` Flue gas and ash. - thickener_perc : float - The percentage of Suspended Sludge in the underflow of the dewatering unit. process_efficiency : float The process efficiency of the incinerator unit. Expected value between 0 and 1. calorific_value_sludge : float From 02afcf3b09519a34862d0882829fcf0073ac5860 Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Tue, 8 Aug 2023 16:27:04 +0530 Subject: [PATCH 181/424] Changed name of SanUnit --- qsdsan/sanunits/_sludge_treatment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py index 1f19faf8..d97a042b 100644 --- a/qsdsan/sanunits/_sludge_treatment.py +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -22,7 +22,7 @@ ) -__all__ = ('Thickener', 'DewateringUnit', 'Incinerator') +__all__ = ('Thickener', 'Centrifuge', 'Incinerator') # Asign a bare module of 1 to all default_F_BM = { From 3558014f0622ea371c823aad5c89ef22c4e740c1 Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Tue, 8 Aug 2023 16:27:53 +0530 Subject: [PATCH 182/424] Redid changes for SC pumps --- qsdsan/sanunits/_clarifier.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index 217d9e41..f7ad1dd3 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -495,14 +495,15 @@ def dy_dt(t, QC_ins, QC, dQC_ins): def _design_pump(self): ID, pumps = self.ID, self.pumps + self._ras.copy_like(self.outs[1]) self._was.copy_like(self.outs[2]) ins_dct = { - 'inf': self._mixed, 'ras': self._ras, 'was': self._was, } + type_dct = dict.fromkeys(pumps, 'sludge') inputs_dct = dict.fromkeys(pumps, (1,)) @@ -549,8 +550,7 @@ def _design(self): design_flow = 5520 # 35 MGD D = self.design_results - # Assuming the capacity of one clarifier is 20 MGD = (20*3785.4118) m3/day = 3155 m3/hr - design_flow = 3155 # m3/hr + D['Number of clarifiers'] = np.ceil(self._mixed.get_total_flow('m3/hr')/design_flow) D['Cylindrical volume'] = self._V # in m3 @@ -600,6 +600,8 @@ def _design(self): pipe, pumps = self._design_pump() D['Pump pipe stainless steel'] = pipe D['Pump stainless steel'] = pumps + # For secondary clarifier + D['Number of pumps'] = 2*D['Number of clarifiers'] def _cost(self): From c980302e5e9d0a0606c12ca7da4c7dee6e2f2667 Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Thu, 10 Aug 2023 17:37:42 +0530 Subject: [PATCH 183/424] Made necessary changes to centrifuge Changed pump settings and using exponent scaling for equipments in dewatering unit. --- qsdsan/sanunits/_sludge_treatment.py | 82 ++++++++++++++-------------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py index d97a042b..e7fabbe0 100644 --- a/qsdsan/sanunits/_sludge_treatment.py +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -672,24 +672,26 @@ def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, 'Volume of bowl': 'm3', 'Stainless steel for bowl': 'kg', - 'Stainless steel for conveyor': 'kg', 'Polymer feed rate': 'kg/hr', 'Pump pipe stainless steel' : 'kg', - 'Pump stainless steel': 'kg' + 'Pump stainless steel': 'kg', + 'Number of pumps': '?' } - def _design_pump(self): ID, pumps = self.ID, self.pumps - self._inf.copy_like(self._mixed) - + self._sludge.copy_like(self.outs[0]) + sludge = self._sludge ins_dct = { - 'inf': self._inf, + 'sludge': sludge, } - - type_dct = dict.fromkeys(pumps, '') + type_dct = dict.fromkeys(pumps, 'sludge') inputs_dct = dict.fromkeys(pumps, (1,)) + + D = self.design_results + influent_Q = sludge.get_total_flow('m3/hr')/D['Number of centrifuges'] + influent_Q_mgd = influent_Q*0.00634 # m3/hr to MGD for i in pumps: if hasattr(self, f'{i}_pump'): @@ -700,11 +702,11 @@ def _design_pump(self): capacity_factor=1 # No. of pumps = No. of influents pump = WWTpump( - ID=ID, ins=ins_dct[i], pump_type=type_dct[i], - Q_mgd=None, add_inputs=inputs_dct[i], + ID=ID, ins= ins_dct[i], pump_type=type_dct[i], + Q_mgd=influent_Q_mgd, add_inputs=inputs_dct[i], capacity_factor=capacity_factor, include_pump_cost=True, - include_building_cost=True, + include_building_cost=False, include_OM_cost=True, ) setattr(self, f'{i}_pump', pump) @@ -713,30 +715,28 @@ def _design_pump(self): for i in pumps: p = getattr(self, f'{i}_pump') p.simulate() - # try: p.simulate() - # except: breakpoint() p_design = p.design_results pipe_ss += p_design['Pump pipe stainless steel'] pump_ss += p_design['Pump stainless steel'] return pipe_ss, pump_ss - def _design(self): self._mixed.mix_from(self.ins) + mixed = self._mixed D = self.design_results TSS_rmv = self._TSS_rmv solids_feed_rate = 44.66*self.solids_feed_rate # 44.66 is factor to convert tonne/day to kg/hr # Cake's total solids and TSS are essentially the same (pg. 24-6 [3]) # If TSS_rmv = 98, then total_mass_dry_solids_removed = (0.98)*(influent TSS mass) - total_mass_dry_solids_removed = (TSS_rmv/100)*((self._mixed.get_TSS()*self.ins[0].F_vol)/1000) # in kg/hr + total_mass_dry_solids_removed = (TSS_rmv/100)*((mixed.get_TSS()*self.ins[0].F_vol)/1000) # in kg/hr D['Number of centrifuges'] = np.ceil(total_mass_dry_solids_removed/solids_feed_rate) k = 0.00000056 # Based on emprical formula (pg. 24-23 of [3]) g = 9.81 # m/s2 # The inner diameterof the bowl is calculated based on an empirical formula. 1000 is used to convert mm to m. D['Diameter of bowl'] = (self.g_factor*g)/(k*np.square(self.rotational_speed)*1000) # in m - D['Total length of bowl'] = self.LtoD*D['Diameter'] + D['Total length of bowl'] = self.LtoD*D['Diameter of bowl'] # Sanity check: L should be between 1-7 m, diameter should be around 0.25-0.8 m (Source: [4]) fraction_cylindrical_portion = 0.8 @@ -753,20 +753,6 @@ def _design(self): density_ss = 7930 # kg/m3, 18/8 Chromium D['Stainless steel for bowl'] = D['Volume of bowl']*density_ss # in kg - Inner_diameter_conveyor = 0.108 # in m (based on Alibaba's product) - Length_screw_conveyor = D['Total length of bowl'] # in m - thickness_conveyor_wall = 0.01 # in m (!!! NEED A RELIABLE SOURCE !!!) - Outer_diameter_conveyor = Inner_diameter_conveyor + 2*thickness_conveyor_wall - Cylinder_conveyor_volume = np.pi*(np.square(Outer_diameter_conveyor/2) - np.square(Inner_diameter_conveyor/2))*Length_screw_conveyor # in m2 - - outer_projection_screw = 0.05 # in m (!!! NEED A RELIABLE SOURCE !!!) - Outer_diameter_screw = Outer_diameter_conveyor + 2*outer_projection_screw - thickness_screw = 0.01 # in m (!!! NEED A RELIABLE SOURCE !!!) - number_of_screws_per_unit_length = 10 # in m-1 (!!! NEED A RELIABLE SOURCE !!!) - Number_of_circles_in_screw = D['Total length of bowl']*number_of_screws_per_unit_length # !!! NEED A RELIABLE SOURCE !!! - Screw_volume = np.pi*(np.square(Outer_diameter_screw/2) - np.square(Outer_diameter_conveyor/2))*thickness_screw*Number_of_circles_in_screw # in m3 - D['Stainless steel for conveyor'] = (Cylinder_conveyor_volume + Screw_volume)*density_ss # in kg - polymer_dosage_rate = 0.000453592*self.polymer_dosage # convert from (lbs, polymer/tonne, solids) to (kg, polymer/kg, solids) D['Polymer feed rate'] = (polymer_dosage_rate*solids_feed_rate) # in kg, polymer/hr @@ -774,19 +760,31 @@ def _design(self): pipe, pumps = self._design_pump() D['Pump pipe stainless steel'] = pipe D['Pump stainless steel'] = pumps - - # design['Projected Area at Inlet'] = np.pi*np.square(design['Diameter']/2) #in m2 - # design['Hydraulic_Loading'] = (self.ins[0].F_vol*24)/design['Projected Area at Inlet'] #in m3/(m2*day) + # For centrifuges + D['Number of pumps'] = D['Number of centrifuges'] def _cost(self): D = self.design_results C = self.baseline_purchase_costs + + self._mixed.mix_from(self.ins) + mixed = self._mixed # Construction of concrete and stainless steel walls C['Bowl stainless steel'] = D['Number of centrifuges']*D['Stainless steel for bowl']*self.stainless_steel_unit_cost - C['Conveyor stainless steel'] = D['Number of centrifuges']*D['Stainless steel for conveyor']*self.screw_conveyor_unit_cost_by_weight + # C['Conveyor stainless steel'] = D['Number of centrifuges']*D['Stainless steel for conveyor']*self.screw_conveyor_unit_cost_by_weight C['Polymer'] = D['Number of centrifuges']*D['Polymer feed rate']*self.polymer_cost_by_weight + + # Conveyor + # Source: https://www.alibaba.com/product-detail/Engineers-Available-Service-Stainless-Steel-U_60541536633.html?spm=a2700.galleryofferlist.normal_offer.d_title.1fea1f65v6R3OQ&s=p + base_cost_conveyor = 1800 + base_flow_conveyor = 10 # in m3/hr + thickener_flow = mixed.get_total_flow('m3/hr')/D['Number of centrifuges'] + C['Conveyor'] = D['Number of centrifuges']*base_cost_conveyor*(thickener_flow/base_flow_conveyor)**0.6 + base_power_conveyor = 2.2 # in kW + conveyor_power = D['Number of centrifuges']*base_power_conveyor*(thickener_flow/base_flow_conveyor)**0.6 + # Pump (construction and maintainance) pumps = self.pumps add_OPEX = self.add_OPEX @@ -804,10 +802,10 @@ def _cost(self): opex_o += p_add_opex['Pump operating'] opex_m += p_add_opex['Pump maintenance'] - C['Pumps'] = pump_cost - C['Pump building'] = building_cost - add_OPEX['Pump operating'] = opex_o - add_OPEX['Pump maintenance'] = opex_m + C['Pumps'] = pump_cost*D['Number of pumps'] + C['Pump building'] = building_cost*D['Number of pumps'] + add_OPEX['Pump operating'] = opex_o*D['Number of pumps'] + add_OPEX['Pump maintenance'] = opex_m*D['Number of pumps'] # Power pumping = 0. @@ -816,8 +814,10 @@ def _cost(self): if p is None: continue pumping += p.power_utility.rate - self.power_utility.rate = pumping - + + pumping = pumping*D['Number of pumps'] + self.power_utility.rate += pumping + self.power_utility.rate += conveyor_power # %% class Incinerator(SanUnit): @@ -840,7 +840,7 @@ class Incinerator(SanUnit): The calorific value of influent sludge in KJ/kg. The default value used is 12000 KJ/kg. calorific_value_fuel : float The calorific value of fuel employed for combustion in KJ/kg. - The default fuel is natural gas with calofific value of 50000 KJ/kg. + The default fuel is natural gas with calorific value of 50000 KJ/kg. Examples -------- From ec73111b6de8851dd76f20eade909dbea9cfcfe8 Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Fri, 11 Aug 2023 16:13:13 +0530 Subject: [PATCH 184/424] Changed pump settings in SC and undo some changes made by Yalin 1. Changed WWTpump settings in secondary clarifier based on Joy's suggestion. 2. Undo changes made by Yalin w.r.t. uf, of. uf is underflow (sludge), and of is overflow (treated effluent). --- qsdsan/sanunits/_clarifier.py | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index f7ad1dd3..8a63d024 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -504,6 +504,13 @@ def _design_pump(self): 'was': self._was, } + D = self.design_results + + Q_mgd = { + 'ras': self._ras.get_total_flow('m3/hr')/D['Number of clarifiers']*0.00634, + 'was': self._was.get_total_flow('m3/hr')/D['Number of clarifiers']*0.00634, + } + type_dct = dict.fromkeys(pumps, 'sludge') inputs_dct = dict.fromkeys(pumps, (1,)) @@ -516,7 +523,7 @@ def _design_pump(self): capacity_factor=1 pump = WWTpump( ID=ID, ins=ins_dct[i], pump_type=type_dct[i], - Q_mgd=None, add_inputs=inputs_dct[i], + Q_mgd=Q_mgd, add_inputs=inputs_dct[i], capacity_factor=capacity_factor, include_pump_cost=True, include_building_cost=False, @@ -788,7 +795,7 @@ class PrimaryClarifier(SanUnit): ins : class:`WasteStream` Influent to the clarifier. Expected number of influent is 3. outs : class:`WasteStream` - Treated effluent and sludge. + Sludge (uf) and treated effluent (of). Hydraulic Retention time : float Hydraulic Retention Time in days. The default is 0.04268 days, based on IWA report.[1] ratio_uf : float @@ -812,8 +819,8 @@ class PrimaryClarifier(SanUnit): >>> from qsdsan.sanunits import PrimaryClarifier >>> PC = PrimaryClarifier(ID='PC', ins= (ws,), outs=('eff', 'sludge')) >>> PC.simulate() - >>> eff, sludge = PC.outs - >>> eff.imass['X_OHO']/ws.imass['X_OHO'] # doctest: +ELLIPSIS + >>> uf, of = PC.outs + >>> uf.imass['X_OHO']/ws.imass['X_OHO'] # doctest: +ELLIPSIS 0.280... >>> PC.show() PrimaryClarifier: PC @@ -834,7 +841,7 @@ class PrimaryClarifier(SanUnit): TP : 367.6 mg/L TK : 68.3 mg/L outs... - [0] eff + [0] uf phase: 'l', T: 298.15 K, P: 101325 Pa flow (g/hr): S_F 70 S_NH4 140 @@ -849,7 +856,7 @@ class PrimaryClarifier(SanUnit): TN : 43073.0 mg/L TP : 8085.4 mg/L TK : 2011.4 mg/L - [1] sludge + [1] of phase: 'l', T: 298.15 K, P: 101325 Pa flow (g/hr): S_F 9.93e+03 S_NH4 1.99e+04 @@ -946,7 +953,7 @@ def _f_i(self): return f_i def _run(self): - eff, sludge = self.outs + uf, of = self.outs cmps = self.components mixed = self._mixed mixed.mix_from(self.ins) @@ -962,8 +969,8 @@ def _run(self): Ce = Ze + Xe Cs = Zs + Xs - sludge.set_flow(Ce,'kg/hr') - eff.set_flow(Cs,'kg/hr') + of.set_flow(Ce,'kg/hr') + uf.set_flow(Cs,'kg/hr') def _init_state(self): # if multiple wastestreams exist then concentration and total inlow @@ -973,11 +980,11 @@ def _init_state(self): self._state = np.append(Qs @ Cs / Qs.sum(), Qs.sum()) self._dstate = self._state * 0. - eff, sludge = self.outs - s_flow = eff.F_vol/(eff.F_vol+sludge.F_vol) - denominator = eff.mass + sludge.mass + uf, of = self.outs + s_flow = uf.F_vol/(uf.F_vol+of.F_vol) + denominator = uf.mass + of.mass denominator += (denominator == 0) - s = eff.mass/denominator + s = uf.mass/denominator self._sludge = np.append(s/s_flow, s_flow) self._effluent = np.append((1-s)/(1-s_flow), 1-s_flow) From bf555fc5b61de3dda7fe5fbafac28cfba543b591 Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Mon, 11 Sep 2023 09:27:37 -0500 Subject: [PATCH 185/424] Added functions for estimation of aeration energy Added functions to calculate oxygen demand for heterotrophs, autotrophs, and required airflow. --- qsdsan/utils/wwt_design.py | 130 ++++++++++++++++++++++++++++++++++++- 1 file changed, 128 insertions(+), 2 deletions(-) diff --git a/qsdsan/utils/wwt_design.py b/qsdsan/utils/wwt_design.py index 6b38e87b..91560285 100644 --- a/qsdsan/utils/wwt_design.py +++ b/qsdsan/utils/wwt_design.py @@ -11,7 +11,9 @@ for license details. ''' -__all__ = ('get_SRT',) +import numpy as np + +__all__ = ('get_SRT', 'get_oxygen_heterotrophs', 'get_oxygen_autotrophs', 'get_airflow') def get_SRT(system, biomass_IDs, wastage=None, active_unit_IDs=None): """ @@ -51,4 +53,128 @@ def get_SRT(system, biomass_IDs, wastage=None, active_unit_IDs=None): units = system.units if active_unit_IDs is None \ else [u for u in system.units if u.ID in active_unit_IDs] retain = sum([u.get_retained_mass(biomass_IDs) for u in units if u.isdynamic]) - return retain/waste \ No newline at end of file + return retain/waste + +def get_oxygen_heterotrophs(system, influent=None, eff_COD_soluble = None, f_d = 0.1, b_H = 0.4, SRT = 10, Y_H = 0.625): + """ + Parameters + ---------- + system : obj + The system whose airflow will be calculated. + influent : iterable[:class:`WasteStream`] + Streams incoming to the process for which required oxygen needs to be calculated. The default is None. + eff_COD_soluble : TYPE, optional + DESCRIPTION. The default is None. + f_d : float + fraction of biomass that remains as cell debris. Default value is 0.1 gCOD/gCOD based on ASM2d 'f_XI_H'. + b_H : float + Decay of heterotrophs [d^-1]. The default is 0.4 based on ASM2d. + SRT : float + Estimated sludge retention time of the system. Default is 10 days. + Y_H : float + Yield of heterotrophs [gCOD/gCOD]. The default is 0.625. + + Returns + ------- + float + Oxygen requirement for heterotrophs in kg/day. + + """ + if influent is None: + influent = [inf for inf in system.feeds if inf.phase in ('l')] + + influent_flow = np.array([inf.F_vol*24 for inf in influent]) # in m3/day + influent_COD = np.array([inf.COD for inf in influent]) # in mg/L + + if eff_COD_soluble is None: + eff_COD_soluble = np.array([eff.composite('COD', particle_size='s', unit = 'mg/L') for eff in system.products if eff.phase in 'l']) + + mass_COD_treated = np.sum(influent_flow*(influent_COD - eff_COD_soluble)/1000) # kg/day + aeration_factor = 1 - (1 + f_d*b_H*SRT)*Y_H/(1 + b_H*SRT) + + return mass_COD_treated*aeration_factor + + +def get_oxygen_autotrophs(system, influent=None, eff_COD_soluble = None, f_d = 0.1, b_H = 0.4, b_AUT = 0.15, SRT = 10, + Y_H = 0.625, Y_AUT = 0.24, K_NH = 1, U_AUT = 1, SF_DO = 1.375, ammonia_component_ID = 'S_NH4'): + """ + + Parameters + ---------- + system : TYPE + DESCRIPTION. + influent : TYPE, optional + Streams incoming to activated sludge process. Default is None. + eff_COD_soluble : TYPE, optional + Maximum effluent soluble COD concentration [mg/L]. Default value is None. + f_d : float + fraction of biomass that remains as cell debris. Default value is 0.1 gCOD/gCOD based on ASM2d 'f_XI_A'. + b_H : float + Decay of heterotrophs [d^-1]. The default is 0.4 based on ASM2d. + b_AUT : float + Decay of autotrophs [d^-1]. The default is 0.15 based on ASM2d. + SRT : float + Estimated sludge retention time of the system. Default is 10 days. + Y_H : float + Yield of heterotrophs [gCOD/gCOD]. The default is 0.625. + Y_AUT : float + Yield of autotrophs [g COD/g N]. The default is 0.24. + K_NH: float + Substrate (Ammonium (nutrient)) half saturation coefficient for autotrophs, in [g N/m^3]. + The default is 1.0. + U_AUT: float + Autotrophic maximum specific growth rate, in [d^(-1)]. The default is 1.0. + SF_DO: float + Safety factor for dissolved oxygen. The default is 1.375. + ammonia_component_ID : tuple + Component ID for ammonia.. The default is 'S_NH4'. + + Returns + ------- + float + Oxygen requirement for heterotrophs in kg/day. + + """ + + + if influent is None: + influent = [inf for inf in system.feeds if inf.phase in ('l')] + + influent_flow = np.array([inf.F_vol*24 for inf in influent]) # in m3/day + influent_COD = np.array([inf.COD for inf in influent]) # in mg/L + + if eff_COD_soluble is None: + eff_COD_soluble = np.array([eff.composite('COD', particle_size='s', unit = 'mg/L') for eff in system.products if eff.phase in 'l']) + + NR = 0.087*(1 + f_d*b_H*SRT)*Y_H/(1 + b_H*SRT) + S_NHO = np.array([inf.iconc[ammonia_component_ID] for inf in influent]) + S_N_a = S_NHO - NR*(influent_COD - eff_COD_soluble) + S_NH = K_NH*(1/SRT + b_AUT)/(U_AUT/SF_DO - (1 + b_AUT/SRT)) + aeration_factor = 4.57 - (1 + f_d*b_AUT*SRT)*Y_AUT/(1 + b_AUT*SRT) + mass_N_removed = influent_flow*(S_N_a - S_NH)/1000 # kg/day + + return mass_N_removed*aeration_factor + +def get_airflow(oxygen_heterotrophs, oxygen_autotrophs, oxygen_transfer_efficiency = 15): + """ + + Parameters + ---------- + oxygen_heterotrophs : float + In kg/day. + oxygen_autotrophs : float + In kg/day. + oxygen_transfer_efficiency : float + Field oxygen transfer efficiency is percentage. The default is 15. + + Returns + ------- + Airflow in m3/min. + + """ + + required_oxygen = (oxygen_heterotrophs + oxygen_autotrophs)/24 # in kg/hr + Q_air = 6*required_oxygen/oxygen_transfer_efficiency + + return Q_air + \ No newline at end of file From cfd299fd71e0d10617a0b8ee0051745e1004e3fc Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Mon, 11 Sep 2023 09:28:16 -0500 Subject: [PATCH 186/424] Fixed error in secondary clarifier --- qsdsan/sanunits/_clarifier.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index 8a63d024..1d4f19aa 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -506,9 +506,15 @@ def _design_pump(self): D = self.design_results + ras_flow = self._ras.get_total_flow('m3/hr') + was_flow = self._was.get_total_flow('m3/hr') + + ras_flow_u = ras_flow/D['Number of clarifiers']*0.00634 + was_flow_u = was_flow/D['Number of clarifiers']*0.00634 + Q_mgd = { - 'ras': self._ras.get_total_flow('m3/hr')/D['Number of clarifiers']*0.00634, - 'was': self._was.get_total_flow('m3/hr')/D['Number of clarifiers']*0.00634, + 'ras': ras_flow_u, + 'was': was_flow_u, } type_dct = dict.fromkeys(pumps, 'sludge') @@ -523,7 +529,7 @@ def _design_pump(self): capacity_factor=1 pump = WWTpump( ID=ID, ins=ins_dct[i], pump_type=type_dct[i], - Q_mgd=Q_mgd, add_inputs=inputs_dct[i], + Q_mgd=Q_mgd[i], add_inputs=inputs_dct[i], capacity_factor=capacity_factor, include_pump_cost=True, include_building_cost=False, From 1c00564931a051d36b344a69445df8eec40fdc4e Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Mon, 11 Sep 2023 23:39:49 -0500 Subject: [PATCH 187/424] Updated calculation of O2 demand Added TKN as the correct parameter while calculating oxygen demand in autotrophs --- qsdsan/utils/wwt_design.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/qsdsan/utils/wwt_design.py b/qsdsan/utils/wwt_design.py index 91560285..ce0582b7 100644 --- a/qsdsan/utils/wwt_design.py +++ b/qsdsan/utils/wwt_design.py @@ -89,7 +89,10 @@ def get_oxygen_heterotrophs(system, influent=None, eff_COD_soluble = None, f_d = if eff_COD_soluble is None: eff_COD_soluble = np.array([eff.composite('COD', particle_size='s', unit = 'mg/L') for eff in system.products if eff.phase in 'l']) - mass_COD_treated = np.sum(influent_flow*(influent_COD - eff_COD_soluble)/1000) # kg/day + mass_influent_COD = np.sum(influent_flow*influent_COD/1000) # in kg/day + mass_effluent_COD = np.sum(influent_flow*eff_COD_soluble/1000) # in kg/day + + mass_COD_treated = mass_influent_COD - mass_effluent_COD # kg/day aeration_factor = 1 - (1 + f_d*b_H*SRT)*Y_H/(1 + b_H*SRT) return mass_COD_treated*aeration_factor @@ -135,8 +138,7 @@ def get_oxygen_autotrophs(system, influent=None, eff_COD_soluble = None, f_d = 0 Oxygen requirement for heterotrophs in kg/day. """ - - + if influent is None: influent = [inf for inf in system.feeds if inf.phase in ('l')] @@ -147,11 +149,11 @@ def get_oxygen_autotrophs(system, influent=None, eff_COD_soluble = None, f_d = 0 eff_COD_soluble = np.array([eff.composite('COD', particle_size='s', unit = 'mg/L') for eff in system.products if eff.phase in 'l']) NR = 0.087*(1 + f_d*b_H*SRT)*Y_H/(1 + b_H*SRT) - S_NHO = np.array([inf.iconc[ammonia_component_ID] for inf in influent]) - S_N_a = S_NHO - NR*(influent_COD - eff_COD_soluble) + TKN = np.array([inf.TKN for inf in influent]) + S_N_a = np.array([TKN]) - np.array([NR*(influent_COD - eff_COD_soluble)]) S_NH = K_NH*(1/SRT + b_AUT)/(U_AUT/SF_DO - (1 + b_AUT/SRT)) aeration_factor = 4.57 - (1 + f_d*b_AUT*SRT)*Y_AUT/(1 + b_AUT*SRT) - mass_N_removed = influent_flow*(S_N_a - S_NH)/1000 # kg/day + mass_N_removed = np.sum(influent_flow*(S_N_a - S_NH)/1000) # kg/day return mass_N_removed*aeration_factor From f343d3629ad3ca382586a713553ab91ba1405dd5 Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Mon, 18 Sep 2023 10:52:45 -0500 Subject: [PATCH 188/424] Added required power for blower Added function to calculate required power for aeration. --- qsdsan/utils/wwt_design.py | 40 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/qsdsan/utils/wwt_design.py b/qsdsan/utils/wwt_design.py index ce0582b7..00066157 100644 --- a/qsdsan/utils/wwt_design.py +++ b/qsdsan/utils/wwt_design.py @@ -13,7 +13,7 @@ import numpy as np -__all__ = ('get_SRT', 'get_oxygen_heterotrophs', 'get_oxygen_autotrophs', 'get_airflow') +__all__ = ('get_SRT', 'get_oxygen_heterotrophs', 'get_oxygen_autotrophs', 'get_airflow', 'get_P_blower') def get_SRT(system, biomass_IDs, wastage=None, active_unit_IDs=None): """ @@ -179,4 +179,40 @@ def get_airflow(oxygen_heterotrophs, oxygen_autotrophs, oxygen_transfer_efficien Q_air = 6*required_oxygen/oxygen_transfer_efficiency return Q_air - \ No newline at end of file + + +def get_P_blower(T=20, p_atm=101.325, q_air=None, P_inlet_loss=1, P_diffuser_loss=7, h_submergance=5.18, efficiency=0.8): + """ + + Parameters + ---------- + T : float + Air temperature. (Degree celsius) + p_atm : float + Atmostpheric pressure. (atm) + q_air : m3/min + Air flow. + P_inlet_loss : float + Head loss at inlet. kPa. + P_diffuser_loss : float + Head loss due to diffuser. kPa. + h_submergance : float + Height of submergance in m. The default is 17 feet (5.18 m) + efficiency : TYPE + Blower efficiency. Default is 0.8. + + Returns + ------- + Power of blower (kW). + + """ + + p_in = p_atm - P_inlet_loss + + p_out = p_atm + 9.81*h_submergance + P_diffuser_loss + + Q_air = q_air*(24*60) # m3/min to m3/day + + P_blower = 1.4161*np.power(1/10, 5)*(T + 273.15)*Q_air*(np.power(p_out/p_in, 0.283) - 1)/efficiency + + return P_blower \ No newline at end of file From 0e7253d26d80a1a72e97990444db929741ff08e5 Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Tue, 19 Sep 2023 17:34:51 -0500 Subject: [PATCH 189/424] Added references to functions Added references --- qsdsan/utils/wwt_design.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/qsdsan/utils/wwt_design.py b/qsdsan/utils/wwt_design.py index 00066157..a022caa3 100644 --- a/qsdsan/utils/wwt_design.py +++ b/qsdsan/utils/wwt_design.py @@ -78,6 +78,11 @@ def get_oxygen_heterotrophs(system, influent=None, eff_COD_soluble = None, f_d = ------- float Oxygen requirement for heterotrophs in kg/day. + + References + ---------- + [1] Adapted from Equation 10.10 in GDLF [Grady, Jr., C.P.L.; Daigger, G.T.; Love, N.G.; Filipe, C.D.M. Biological + Wastewater Treatment. 3rd ed. Boca Raton, FL: CRC Press 2011.] """ if influent is None: @@ -136,6 +141,11 @@ def get_oxygen_autotrophs(system, influent=None, eff_COD_soluble = None, f_d = 0 ------- float Oxygen requirement for heterotrophs in kg/day. + + References + ---------- + [1] Adapted from Equation 11.16-11.19 in GDLF [Grady, Jr., C.P.L.; Daigger, G.T.; Love, N.G.; Filipe, C.D.M. + Biological Wastewater Treatment. 3rd ed. Boca Raton, FL: CRC Press 2011.] """ @@ -172,6 +182,11 @@ def get_airflow(oxygen_heterotrophs, oxygen_autotrophs, oxygen_transfer_efficien Returns ------- Airflow in m3/min. + + References + ---------- + [1] Adapted from Equation 11.2 in GDLF [Grady, Jr., C.P.L.; Daigger, G.T.; Love, N.G.; Filipe, C.D.M. + Biological Wastewater Treatment. 3rd ed. Boca Raton, FL: CRC Press 2011.] """ @@ -204,6 +219,10 @@ def get_P_blower(T=20, p_atm=101.325, q_air=None, P_inlet_loss=1, P_diffuser_los Returns ------- Power of blower (kW). + + References + ---------- + [1] Equation 13.1 from GPS-X user manual. """ From 992e8fef4756981be4258abf9acde44986eef069 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Wed, 20 Sep 2023 12:22:56 -0700 Subject: [PATCH 190/424] remove redundancy --- qsdsan/sanunits/_clarifier.py | 1 - 1 file changed, 1 deletion(-) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index 1d4f19aa..f04ac36c 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -142,7 +142,6 @@ def __init__(self, ID='', ins=None, outs=(), thermo=None, for attr, value in kwargs.items(): setattr(self, attr, value) - self._mixed = self.ins[0].copy(f'{ID}_mixed') self._inf = self.ins[0].copy(f'{ID}_inf') self._ras = self.outs[1].copy(f'{ID}_ras') self._was = self.outs[2].copy(f'{ID}_was') From 222971c91dee66dfb2c3867e9bea03c9b6476473 Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Fri, 22 Sep 2023 13:46:13 -0500 Subject: [PATCH 191/424] Updated Primary Clarifier In process of changing primary clarifier --- qsdsan/sanunits/_clarifier.py | 1067 +++++++++++++++++++++++++-------- 1 file changed, 830 insertions(+), 237 deletions(-) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index 1d4f19aa..314623a4 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -13,6 +13,7 @@ ''' from numpy import maximum as npmax, minimum as npmin, exp as npexp +from warnings import warn from .. import SanUnit, WasteStream import numpy as np from ..sanunits import WWTpump @@ -780,41 +781,490 @@ def _design(self): pass -# %% +# %% +# class PrimaryClarifierBSM2(SanUnit): + +# """ +# A Primary clarifier based on BSM2 Layout. [1] + +# Parameters +# ---------- +# ID : str +# ID for the clarifier. The default is ''. +# ins : class:`WasteStream` +# Influent to the clarifier. Expected number of influent is 3. +# outs : class:`WasteStream` +# Sludge (uf) and treated effluent (of). +# Hydraulic Retention time : float +# Hydraulic Retention Time in days. The default is 0.04268 days, based on IWA report.[1] +# ratio_uf : float +# The ratio of sludge to primary influent. The default is 0.007, based on IWA report.[1] +# f_corr : float +# Dimensionless correction factor for removal efficiency in the primary clarifier.[1] +# cylindrical_depth : float, optional +# The depth of the cylindrical portion of clarifier [in m]. +# upflow_velocity : float, optional +# Speed with which influent enters the center feed of the clarifier [m/hr]. The default is 43.2. +# F_BM : dict +# Equipment bare modules. + +# Examples +# -------- +# >>> from qsdsan import set_thermo, Components, WasteStream +# >>> cmps = Components.load_default() +# >>> cmps_test = cmps.subgroup(['S_F', 'S_NH4', 'X_OHO', 'H2O']) +# >>> set_thermo(cmps_test) +# >>> ws = WasteStream('ws', S_F = 10, S_NH4 = 20, X_OHO = 15, H2O=1000) +# >>> from qsdsan.sanunits import PrimaryClarifier +# >>> PC = PrimaryClarifier(ID='PC', ins= (ws,), outs=('eff', 'sludge')) +# >>> PC.simulate() +# >>> uf, of = PC.outs +# >>> uf.imass['X_OHO']/ws.imass['X_OHO'] # doctest: +ELLIPSIS +# 0.280... +# >>> PC.show() +# PrimaryClarifier: PC +# ins... +# [0] ws +# phase: 'l', T: 298.15 K, P: 101325 Pa +# flow (g/hr): S_F 1e+04 +# S_NH4 2e+04 +# X_OHO 1.5e+04 +# H2O 1e+06 +# WasteStream-specific properties: +# pH : 7.0 +# COD : 23873.0 mg/L +# BOD : 14963.2 mg/L +# TC : 8298.3 mg/L +# TOC : 8298.3 mg/L +# TN : 20363.2 mg/L +# TP : 367.6 mg/L +# TK : 68.3 mg/L +# outs... +# [0] uf +# phase: 'l', T: 298.15 K, P: 101325 Pa +# flow (g/hr): S_F 70 +# S_NH4 140 +# X_OHO 4.2e+03 +# H2O 7e+03 +# WasteStream-specific properties: +# pH : 7.0 +# COD : 428873.3 mg/L +# BOD : 244072.7 mg/L +# TC : 156644.5 mg/L +# TOC : 156644.5 mg/L +# TN : 43073.0 mg/L +# TP : 8085.4 mg/L +# TK : 2011.4 mg/L +# [1] of +# phase: 'l', T: 298.15 K, P: 101325 Pa +# flow (g/hr): S_F 9.93e+03 +# S_NH4 1.99e+04 +# X_OHO 1.08e+04 +# H2O 9.93e+05 +# WasteStream-specific properties: +# pH : 7.0 +# COD : 19982.3 mg/L +# BOD : 12762.2 mg/L +# TC : 6873.2 mg/L +# TOC : 6873.2 mg/L +# TN : 20145.0 mg/L +# TP : 293.5 mg/L +# TK : 49.6 mg/L + +# References +# ---------- +# .. [1] Gernaey, Krist V., Ulf Jeppsson, Peter A. Vanrolleghem, and John B. Copp. +# Benchmarking of control strategies for wastewater treatment plants. IWA publishing, 2014. +# [2] Metcalf, Leonard, Harrison P. Eddy, and Georg Tchobanoglous. Wastewater +# engineering: treatment, disposal, and reuse. Vol. 4. New York: McGraw-Hill, 1991. +# """ + +# _N_ins = 3 +# _N_outs = 2 +# _ins_size_is_fixed = False + +# # Costs +# wall_concrete_unit_cost = 650 / 0.765 # $/m3, 0.765 is to convert from $/yd3 +# stainless_steel_unit_cost=1.8 # $/kg (Taken from Joy's METAB code) https://www.alibaba.com/product-detail/brushed-stainless-steel-plate-304l-stainless_1600391656401.html?spm=a2700.details.0.0.230e67e6IKwwFd + +# pumps = ('sludge',) + +# def __init__(self, ID='', ins=None, outs=(), thermo=None, +# isdynamic=False, init_with='WasteStream', Hydraulic_Retention_Time=0.04268, +# ratio_uf=0.007, f_corr=0.65, cylindrical_depth = 5, upflow_velocity = 43.2, +# F_BM=default_F_BM, **kwargs): +# SanUnit.__init__(self, ID, ins, outs, thermo, isdynamic=isdynamic, +# init_with=init_with) +# self.Hydraulic_Retention_Time = Hydraulic_Retention_Time #in days +# self.ratio_uf = ratio_uf +# self.f_corr = f_corr +# self.cylindrical_depth = cylindrical_depth # in m +# self.upflow_velocity = upflow_velocity # in m/hr (converted from 12 mm/sec) +# self.F_BM.update(default_F_BM) +# self._mixed = self.ins[0].copy(f'{ID}_mixed') +# self._sludge = self.outs[1].copy(f'{ID}_sludge') + +# @property +# def Hydraulic_Retention_Time(self): +# '''The Hydraulic Retention time in days.''' +# return self._HRT + +# @Hydraulic_Retention_Time.setter +# def Hydraulic_Retention_Time(self, HRT): +# if HRT is not None: +# self._HRT = HRT +# else: +# raise ValueError('HRT expected from user') + +# @property +# def ratio_uf(self): +# return self._r + +# @ratio_uf.setter +# def ratio_uf(self, r): +# if r is not None: +# if r > 1 or r < 0: +# raise ValueError(f'Sludge to influent ratio must be within [0, 1], not {r}') +# self._r = r +# else: +# raise ValueError('Sludge to influent ratio expected from user') + +# @property +# def f_corr(self): +# return self._corr + +# @f_corr.setter +# def f_corr(self, corr): +# if corr is not None: +# # if corr > 1 or corr < 0: +# # raise ValueError(f'correction factor must be within [0, 1], not {corr}') +# self._corr = corr +# else: +# raise ValueError('correction factor expected from user') + +# def _f_i(self): +# xcod = self._mixed.composite('COD', particle_size='x') +# fx = xcod/self._mixed.COD +# corr = self._corr +# HRT = self._HRT +# n_COD = corr*(2.88*fx - 0.118)*(1.45 + 6.15*np.log(HRT*24*60)) +# f_i = 1 - (n_COD/100) +# return f_i + +# def _run(self): +# uf, of = self.outs +# cmps = self.components +# mixed = self._mixed +# mixed.mix_from(self.ins) + +# r = self._r +# f_i = self._f_i() + +# Xs = (1 - f_i)*mixed.mass*cmps.x +# Xe = (f_i)*mixed.mass*cmps.x + +# Zs = r*mixed.mass*cmps.s +# Ze = (1-r)*mixed.mass*cmps.s + +# Ce = Ze + Xe +# Cs = Zs + Xs +# of.set_flow(Ce,'kg/hr') +# uf.set_flow(Cs,'kg/hr') + +# def _init_state(self): +# # if multiple wastestreams exist then concentration and total inlow +# # would be calculated assumping perfect mixing +# Qs = self._ins_QC[:,-1] +# Cs = self._ins_QC[:,:-1] +# self._state = np.append(Qs @ Cs / Qs.sum(), Qs.sum()) +# self._dstate = self._state * 0. + +# uf, of = self.outs +# s_flow = uf.F_vol/(uf.F_vol+of.F_vol) +# denominator = uf.mass + of.mass +# denominator += (denominator == 0) +# s = uf.mass/denominator +# self._sludge = np.append(s/s_flow, s_flow) +# self._effluent = np.append((1-s)/(1-s_flow), 1-s_flow) + +# def _update_state(self): +# '''updates conditions of output stream based on conditions of the Primary Clarifier''' +# self._outs[0].state = self._sludge * self._state +# self._outs[1].state = self._effluent * self._state + +# def _update_dstate(self): +# '''updates rates of change of output stream from rates of change of the Primary Clarifier''' +# self._outs[0].dstate = self._sludge * self._dstate +# self._outs[1].dstate = self._effluent * self._dstate + +# @property +# def AE(self): +# if self._AE is None: +# self._compile_AE() +# return self._AE + +# def _compile_AE(self): +# _state = self._state +# _dstate = self._dstate +# _update_state = self._update_state +# _update_dstate = self._update_dstate +# def yt(t, QC_ins, dQC_ins): +# #Because there are multiple inlets +# Q_ins = QC_ins[:, -1] +# C_ins = QC_ins[:, :-1] +# dQ_ins = dQC_ins[:, -1] +# dC_ins = dQC_ins[:, :-1] +# Q = Q_ins.sum() +# C = Q_ins @ C_ins / Q +# _state[-1] = Q +# _state[:-1] = C +# Q_dot = dQ_ins.sum() +# C_dot = (dQ_ins @ C_ins + Q_ins @ dC_ins - Q_dot * C)/Q +# _dstate[-1] = Q_dot +# _dstate[:-1] = C_dot +# _update_state() +# _update_dstate() +# self._AE = yt + +# _units = { +# 'Number of clarifiers': '?', +# 'Cylindrical volume': 'm3', +# 'Cylindrical depth': 'm', +# 'Cylindrical diameter': 'm', + +# 'Conical radius': 'm', +# 'Conical depth': 'm', +# 'Conical volume': 'm3', + +# 'Volume': 'm3', +# 'Center feed depth': 'm', +# 'Upflow velocity': 'm/hr', +# 'Center feed diameter': 'm', +# 'Volume of concrete wall': 'm3', +# 'Stainless steel': 'kg', +# 'Pump pipe stainless steel' : 'kg', +# 'Pump stainless steel': 'kg', +# 'Number of pumps': '?' +# } + + +# def _design_pump(self): +# ID, pumps = self.ID, self.pumps +# self._sludge.copy_like(self.outs[1]) + +# ins_dct = { +# 'sludge': self._sludge, +# } + +# type_dct = dict.fromkeys(pumps, 'sludge') +# inputs_dct = dict.fromkeys(pumps, (1,),) + +# D = self.design_results +# influent_Q = self._sludge.get_total_flow('m3/hr')/D['Number of clarifiers'] +# influent_Q_mgd = influent_Q*0.00634 # m3/hr to MGD + +# for i in pumps: +# if hasattr(self, f'{i}_pump'): +# p = getattr(self, f'{i}_pump') +# setattr(p, 'add_inputs', inputs_dct[i]) +# else: +# ID = f'{ID}_{i}' +# capacity_factor=1 +# pump = WWTpump( +# ID=ID, ins=ins_dct[i], pump_type=type_dct[i], +# Q_mgd=influent_Q_mgd, add_inputs=inputs_dct[i], +# capacity_factor=capacity_factor, +# include_pump_cost=True, +# include_building_cost=False, +# include_OM_cost=True, +# ) +# setattr(self, f'{i}_pump', pump) + +# pipe_ss, pump_ss = 0., 0. +# for i in pumps: +# p = getattr(self, f'{i}_pump') +# p.simulate() +# p_design = p.design_results +# pipe_ss += p_design['Pump pipe stainless steel'] +# pump_ss += p_design['Pump stainless steel'] +# return pipe_ss, pump_ss + + +# def _design(self): + +# self._mixed.mix_from(self.ins) + +# D = self.design_results +# total_flow = self._mixed.get_total_flow('m3/hr') + +# if total_flow <= 1580: # 10 MGD +# design_flow = 790 # 5 MGD +# elif total_flow >1580 and total_flow <= 4730: # Between 10 and 30 MGD +# design_flow = 2365 # 15 MGD +# elif total_flow > 4730 and total_flow <= 15770: # Between 30 and 100 MGD +# design_flow = 3940 # 25 MGD +# else: +# design_flow = 5520 # 35 MGD + +# D['Number of clarifiers'] = np.ceil(total_flow/design_flow) + +# total_volume = 24*self._HRT*design_flow #in m3 +# working_volume = total_volume/0.8 # Assume 80% working volume + +# D['Cylindrical volume'] = working_volume +# # Sidewater depth of a cylindrical clarifier lies between 2.5-5m +# D['Cylindrical depth'] = self.cylindrical_depth # in m +# # The tank diameter can lie anywhere between 3 m to 100 m +# D['Cylindrical diameter'] = (4*working_volume/(3.14*D['Cylindrical depth']))**(1/2) # in m + +# D['Conical radius'] = D['Cylindrical diameter']/2 +# # The slope of the bottom conical floor lies between 1:10 to 1:12 +# D['Conical depth'] = D['Conical radius']/10 +# D['Conical volume'] = (3.14/3)*(D['Conical radius']**2)*D['Conical depth'] + +# D['Volume'] = D['Cylindrical volume'] + D['Conical volume'] + +# # Primary clarifiers can be center feed or peripheral feed. The design here is for the more +# # commonly deployed center feed. + +# # Depth of the center feed lies between 30-75% of sidewater depth +# D['Center feed depth'] = 0.5*D['Cylindrical depth'] +# # Typical conventional feed wells are designed for an average downflow velocity +# # of 10-13 mm/s and maximum velocity of 25-30 mm/s +# peak_flow_safety_factor = 2.5 # assumed based on average and maximum velocities +# upflow_velocity = self.upflow_velocity # in m/hr (converted from 12 mm/sec) +# D['Upflow velocity'] = upflow_velocity*peak_flow_safety_factor # in m/hr +# Center_feed_area = design_flow/D['Upflow velocity'] # in m2 +# D['Center feed diameter'] = ((4*Center_feed_area)/3.14)**(1/2) # Sanity check: Diameter of the center feed lies between 15-25% of tank diameter + +# # Amount of concrete required +# D_tank = D['Cylindrical depth']*39.37 # m to inches +# # Thickness of the wall concrete, [m]. Default to be minimum of 1 ft with 1 in added for every ft of depth over 12 ft. +# thickness_concrete_wall = (1 + max(D_tank-12, 0)/12)*0.3048 # from feet to m +# inner_diameter = D['Cylindrical diameter'] +# outer_diameter = inner_diameter + 2*thickness_concrete_wall +# volume_cylindrical_wall = (3.14*D['Cylindrical depth']/4)*(outer_diameter**2 - inner_diameter**2) +# volume_conical_wall = (3.14/3)*(D['Conical depth']/4)*(outer_diameter**2 - inner_diameter**2) +# D['Volume of concrete wall'] = volume_cylindrical_wall + volume_conical_wall # in m3 + +# # Amount of metal required for center feed +# thickness_metal_wall = 0.5 # in m (!! NEED A RELIABLE SOURCE !!) +# inner_diameter_center_feed = D['Center feed diameter'] +# outer_diameter_center_feed = inner_diameter_center_feed + 2*thickness_metal_wall +# volume_center_feed = (3.14*D['Center feed depth']/4)*(outer_diameter_center_feed**2 - inner_diameter_center_feed **2) +# density_ss = 7930 # kg/m3, 18/8 Chromium +# D['Stainless steel'] = volume_center_feed*density_ss # in kg + +# # Pumps +# pipe, pumps = self._design_pump() +# D['Pump pipe stainless steel'] = pipe +# D['Pump stainless steel'] = pumps +# # For primary clarifier +# D['Number of pumps'] = D['Number of clarifiers'] + +# def _cost(self): + +# self._mixed.mix_from(self.ins) +# D = self.design_results +# C = self.baseline_purchase_costs + +# # Construction of concrete and stainless steel walls +# C['Wall concrete'] = D['Number of clarifiers']*D['Volume of concrete wall']*self.wall_concrete_unit_cost +# C['Wall stainless steel'] = D['Number of clarifiers']*D['Stainless steel']*self.stainless_steel_unit_cost + +# # Cost of equipment + +# # Source of scaling exponents: Process Design and Economics for Biochemical Conversion of Lignocellulosic Biomass to Ethanol by NREL. + +# # Scraper +# # Source: https://www.alibaba.com/product-detail/Peripheral-driving-clarifier-mud-scraper-waste_1600891102019.html?spm=a2700.details.0.0.47ab45a4TP0DLb +# base_cost_scraper = 2500 +# base_flow_scraper = 1 # in m3/hr (!!! Need to know whether this is for solids or influent !!!) +# clarifier_flow = self._mixed.get_total_flow('m3/hr')/D['Number of clarifiers'] +# C['Scraper'] = D['Number of clarifiers']*base_cost_scraper*(clarifier_flow/base_flow_scraper)**0.6 +# base_power_scraper = 2.75 # in kW +# scraper_power = D['Number of clarifiers']*base_power_scraper*(clarifier_flow/base_flow_scraper)**0.6 + +# # v notch weir +# # Source: https://www.alibaba.com/product-detail/50mm-Tube-Settler-Media-Modules-Inclined_1600835845218.html?spm=a2700.galleryofferlist.normal_offer.d_title.69135ff6o4kFPb +# base_cost_v_notch_weir = 6888 +# base_flow_v_notch_weir = 10 # in m3/hr +# C['v notch weir'] = D['Number of clarifiers']*base_cost_v_notch_weir*(clarifier_flow/base_flow_v_notch_weir)**0.6 + +# # Pump (construction and maintainance) +# pumps = self.pumps +# add_OPEX = self.add_OPEX +# pump_cost = 0. +# building_cost = 0. +# opex_o = 0. +# opex_m = 0. + +# for i in pumps: +# p = getattr(self, f'{i}_pump') +# p_cost = p.baseline_purchase_costs +# p_add_opex = p.add_OPEX +# pump_cost += p_cost['Pump'] +# building_cost += p_cost['Pump building'] +# opex_o += p_add_opex['Pump operating'] +# opex_m += p_add_opex['Pump maintenance'] + +# C['Pumps'] = pump_cost*D['Number of pumps'] +# C['Pump building'] = building_cost*D['Number of pumps'] +# add_OPEX['Pump operating'] = opex_o*D['Number of pumps'] +# add_OPEX['Pump maintenance'] = opex_m*D['Number of pumps'] + +# # Power +# pumping = 0. +# for ID in self.pumps: +# p = getattr(self, f'{ID}_pump') +# if p is None: +# continue +# pumping += p.power_utility.rate + +# pumping = pumping*D['Number of pumps'] + +# self.power_utility.consumption += pumping +# self.power_utility.consumption += scraper_power + + # Asign a bare module of 1 to all default_F_BM = { 'Wall concrete': 1., 'Wall stainless steel': 1., } default_F_BM.update(default_WWTpump_F_BM) - + class PrimaryClarifier(SanUnit): - + """ - A Primary clarifier based on BSM2 Layout. [1] - - Parameters + Primary clarifier adapted from the design of thickener as defined in BSM-2. [1] ---------- ID : str - ID for the clarifier. The default is ''. + ID for the Primary Clarifier. The default is ''. ins : class:`WasteStream` - Influent to the clarifier. Expected number of influent is 3. + Influent to the clarifier. Expected number of influent is 1. outs : class:`WasteStream` - Sludge (uf) and treated effluent (of). - Hydraulic Retention time : float - Hydraulic Retention Time in days. The default is 0.04268 days, based on IWA report.[1] - ratio_uf : float - The ratio of sludge to primary influent. The default is 0.007, based on IWA report.[1] - f_corr : float - Dimensionless correction factor for removal efficiency in the primary clarifier.[1] - cylindrical_depth : float, optional - The depth of the cylindrical portion of clarifier [in m]. + Sludge and treated effluent. + thickener_perc : float + The percentage of solids in the underflow of the clarifier.[1] + TSS_removal_perc : float + The percentage of suspended solids removed in the clarifier.[1] + surface_overflow_rate : float + Surface overflow rate in the clarifier in [(m3/day)/m2]. [3] + Design SOR value for clarifier is 41 (m3/day)/m2 if it does not receive WAS. + Design SOR value for clarifier is 29 (m3/day)/m2 if it receives WAS. + Typically SOR lies between 30-50 (m3/day)/m2. + Here default value of 41 (m3/day)/m2 is used. + depth_clarifier : float + Depth of clarifier. Typical depths range from 3 m to 4.9 m [2, 3]. + Default value of 4.5 m would be used here. upflow_velocity : float, optional - Speed with which influent enters the center feed of the clarifier [m/hr]. The default is 43.2. + Speed with which influent enters the center feed of the clarifier [m/hr]. [4] + The default is 43.2 m/hr. (12 mm/sec) F_BM : dict Equipment bare modules. - + Examples -------- >>> from qsdsan import set_thermo, Components, WasteStream @@ -822,14 +1272,14 @@ class PrimaryClarifier(SanUnit): >>> cmps_test = cmps.subgroup(['S_F', 'S_NH4', 'X_OHO', 'H2O']) >>> set_thermo(cmps_test) >>> ws = WasteStream('ws', S_F = 10, S_NH4 = 20, X_OHO = 15, H2O=1000) - >>> from qsdsan.sanunits import PrimaryClarifier - >>> PC = PrimaryClarifier(ID='PC', ins= (ws,), outs=('eff', 'sludge')) - >>> PC.simulate() - >>> uf, of = PC.outs - >>> uf.imass['X_OHO']/ws.imass['X_OHO'] # doctest: +ELLIPSIS - 0.280... - >>> PC.show() - PrimaryClarifier: PC + >>> from qsdsan.sanunits import Thickener + >>> TC = Thickener(ID='TC', ins= (ws), outs=('sludge', 'effluent')) + >>> TC.simulate() + >>> sludge, effluent = TC.outs + >>> sludge.imass['X_OHO']/ws.imass['X_OHO'] + 0.98 + >>> TC.show() # doctest: +ELLIPSIS + Thickener: TC ins... [0] ws phase: 'l', T: 298.15 K, P: 101325 Pa @@ -847,162 +1297,269 @@ class PrimaryClarifier(SanUnit): TP : 367.6 mg/L TK : 68.3 mg/L outs... - [0] uf + [0] sludge phase: 'l', T: 298.15 K, P: 101325 Pa - flow (g/hr): S_F 70 - S_NH4 140 - X_OHO 4.2e+03 - H2O 7e+03 + flow (g/hr): S_F 1.56e+03 + S_NH4 3.11e+03 + X_OHO 1.47e+04 + H2O 1.56e+05 WasteStream-specific properties: pH : 7.0 - COD : 428873.3 mg/L - BOD : 244072.7 mg/L - TC : 156644.5 mg/L - TOC : 156644.5 mg/L - TN : 43073.0 mg/L - TP : 8085.4 mg/L - TK : 2011.4 mg/L - [1] of + COD : 95050.4 mg/L + BOD : 55228.4 mg/L + TC : 34369.6 mg/L + TOC : 34369.6 mg/L + TN : 24354.4 mg/L + TP : 1724.0 mg/L + TK : 409.8 mg/L + [1] effluent phase: 'l', T: 298.15 K, P: 101325 Pa - flow (g/hr): S_F 9.93e+03 - S_NH4 1.99e+04 - X_OHO 1.08e+04 - H2O 9.93e+05 + flow (g/hr): S_F 8.44e+03 + S_NH4 1.69e+04 + X_OHO 300 + H2O 8.44e+05 WasteStream-specific properties: pH : 7.0 - COD : 19982.3 mg/L - BOD : 12762.2 mg/L - TC : 6873.2 mg/L - TOC : 6873.2 mg/L - TN : 20145.0 mg/L - TP : 293.5 mg/L - TK : 49.6 mg/L - + COD : 9978.2 mg/L + BOD : 7102.9 mg/L + TC : 3208.8 mg/L + TOC : 3208.8 mg/L + TN : 19584.1 mg/L + TP : 102.9 mg/L + TK : 1.6 mg/L + References ---------- .. [1] Gernaey, Krist V., Ulf Jeppsson, Peter A. Vanrolleghem, and John B. Copp. Benchmarking of control strategies for wastewater treatment plants. IWA publishing, 2014. - [2] Metcalf, Leonard, Harrison P. Eddy, and Georg Tchobanoglous. Wastewater + [2] Metcalf, Leonard, Harrison P. Eddy, and Georg Tchobanoglous. Wastewater engineering: treatment, disposal, and reuse. Vol. 4. New York: McGraw-Hill, 1991. + [3] Chapter-10: Primary Treatment. Design of water resource recovery facilities. + WEF Manual of Practice No. 8. 6th Edition. Virginia: McGraw-Hill, 2018. + [4] Introduction to Wastewater Clarifier Design by Nikolay Voutchkov, PE, BCEE. """ - _N_ins = 3 + _N_ins = 1 _N_outs = 2 _ins_size_is_fixed = False - + _outs_size_is_fixed = False + # Costs wall_concrete_unit_cost = 650 / 0.765 # $/m3, 0.765 is to convert from $/yd3 stainless_steel_unit_cost=1.8 # $/kg (Taken from Joy's METAB code) https://www.alibaba.com/product-detail/brushed-stainless-steel-plate-304l-stainless_1600391656401.html?spm=a2700.details.0.0.230e67e6IKwwFd - + pumps = ('sludge',) - def __init__(self, ID='', ins=None, outs=(), thermo=None, - isdynamic=False, init_with='WasteStream', Hydraulic_Retention_Time=0.04268, - ratio_uf=0.007, f_corr=0.65, cylindrical_depth = 5, upflow_velocity = 43.2, - F_BM=default_F_BM, **kwargs): - SanUnit.__init__(self, ID, ins, outs, thermo, isdynamic=isdynamic, + def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, + init_with='WasteStream', thickener_perc=7, + TSS_removal_perc=98, surface_overflow_rate = 41, depth_clarifier=4.5, + upflow_velocity=43.2, F_BM=default_F_BM, **kwargs): + SanUnit.__init__(self, ID, ins, outs, thermo, isdynamic=isdynamic, init_with=init_with) - self.Hydraulic_Retention_Time = Hydraulic_Retention_Time #in days - self.ratio_uf = ratio_uf - self.f_corr = f_corr - self.cylindrical_depth = cylindrical_depth # in m - self.upflow_velocity = upflow_velocity # in m/hr (converted from 12 mm/sec) - self.F_BM.update(default_F_BM) - self._mixed = self.ins[0].copy(f'{ID}_mixed') - self._sludge = self.outs[1].copy(f'{ID}_sludge') - + self.thickener_perc = thickener_perc + self.TSS_removal_perc = TSS_removal_perc + self.surface_overflow_rate = surface_overflow_rate + self.depth_clarifier = depth_clarifier + self.upflow_velocity = upflow_velocity + self.F_BM.update(F_BM) + self._mixed = WasteStream(f'{ID}_mixed') + self._sludge = self.outs[0].copy(f'{ID}_sludge') + @property - def Hydraulic_Retention_Time(self): - '''The Hydraulic Retention time in days.''' - return self._HRT - - @Hydraulic_Retention_Time.setter - def Hydraulic_Retention_Time(self, HRT): - if HRT is not None: - self._HRT = HRT - else: - raise ValueError('HRT expected from user') - + def thickener_perc(self): + '''tp is the percentage of Suspended Sludge in the underflow of the clarifier''' + return self._tp + + @thickener_perc.setter + def thickener_perc(self, tp): + if tp is not None: + if tp>=100 or tp<=0: + raise ValueError(f'should be between 0 and 100 not {tp}') + self._tp = tp + else: + raise ValueError('percentage of SS in the underflow of the thickener expected from user') + @property - def ratio_uf(self): - return self._r - - @ratio_uf.setter - def ratio_uf(self, r): - if r is not None: - if r > 1 or r < 0: - raise ValueError(f'Sludge to influent ratio must be within [0, 1], not {r}') - self._r = r - else: - raise ValueError('Sludge to influent ratio expected from user') - + def solids_loading_rate(self): + '''solids_loading_rate is the loading in the clarifier''' + return self._slr + + @solids_loading_rate.setter + def solids_loading_rate(self, slr): + if slr is not None: + self._slr = slr + else: + raise ValueError('solids_loading_rate of the clarifier expected from user') + + @property + def TSS_removal_perc(self): + '''The percentage of suspended solids removed in the clarifier''' + return self._TSS_rmv + + @TSS_removal_perc.setter + def TSS_removal_perc(self, TSS_rmv): + if TSS_rmv is not None: + if TSS_rmv>=100 or TSS_rmv<=0: + raise ValueError(f'should be between 0 and 100 not {TSS_rmv}') + self._TSS_rmv = TSS_rmv + else: + raise ValueError('percentage of suspended solids removed in the clarifier expected from user') + + @property + def thickener_factor(self): + self._mixed.mix_from(self.ins) + inf = self._mixed + _cal_thickener_factor = self._cal_thickener_factor + if not self.ins: return + elif inf.isempty(): return + else: + TSS_in = inf.get_TSS() + thickener_factor = _cal_thickener_factor(TSS_in) + return thickener_factor + @property - def f_corr(self): - return self._corr - - @f_corr.setter - def f_corr(self, corr): - if corr is not None: - # if corr > 1 or corr < 0: - # raise ValueError(f'correction factor must be within [0, 1], not {corr}') - self._corr = corr + def thinning_factor(self): + self._mixed.mix_from(self.ins) + inf = self._mixed + TSS_in = inf.get_TSS() + _cal_thickener_factor = self._cal_thickener_factor + thickener_factor = _cal_thickener_factor(TSS_in) + _cal_parameters = self._cal_parameters + Qu_factor, thinning_factor = _cal_parameters(thickener_factor) + return thinning_factor + + def _cal_thickener_factor(self, TSS_in): + if TSS_in > 0: + thickener_factor = self._tp*10000/TSS_in + if thickener_factor<1: + thickener_factor=1 + return thickener_factor + else: return None + + def _cal_parameters(self, thickener_factor): + if thickener_factor<1: + Qu_factor = 1 + thinning_factor=0 else: - raise ValueError('correction factor expected from user') - - def _f_i(self): - xcod = self._mixed.composite('COD', particle_size='x') - fx = xcod/self._mixed.COD - corr = self._corr - HRT = self._HRT - n_COD = corr*(2.88*fx - 0.118)*(1.45 + 6.15*np.log(HRT*24*60)) - f_i = 1 - (n_COD/100) - return f_i - + Qu_factor = self._TSS_rmv/(100*thickener_factor) + thinning_factor = (1 - (self._TSS_rmv/100))/(1 - Qu_factor) + return Qu_factor, thinning_factor + + def _update_parameters(self): + + # Thickener_factor, Thinning_factor, and Qu_factor need to be + # updated again and again. while dynamic simulations + + cmps = self.components + + TSS_in = np.sum(self._state[:-1]*cmps.i_mass*cmps.x) + _cal_thickener_factor = self._cal_thickener_factor + self.updated_thickener_factor = _cal_thickener_factor(TSS_in) + _cal_parameters = self._cal_parameters + + updated_thickener_factor = self.updated_thickener_factor + self.updated_Qu_factor, self.updated_thinning_factor = _cal_parameters(updated_thickener_factor) + def _run(self): - uf, of = self.outs + self._mixed.mix_from(self.ins) + inf = self._mixed + sludge, eff = self.outs cmps = self.components - mixed = self._mixed - mixed.mix_from(self.ins) - - r = self._r - f_i = self._f_i() - - Xs = (1 - f_i)*mixed.mass*cmps.x - Xe = (f_i)*mixed.mass*cmps.x - - Zs = r*mixed.mass*cmps.s - Ze = (1-r)*mixed.mass*cmps.s - - Ce = Ze + Xe + + TSS_rmv = self._TSS_rmv + thinning_factor = self.thinning_factor + thickener_factor = self.thickener_factor + + # The following are splits by mass of particulates and solubles + + # Note: (1 - thinning_factor)/(thickener_factor - thinning_factor) = Qu_factor + Zs = (1 - thinning_factor)/(thickener_factor - thinning_factor)*inf.mass*cmps.s + Ze = (thickener_factor - 1)/(thickener_factor - thinning_factor)*inf.mass*cmps.s + + Xe = (1 - TSS_rmv/100)*inf.mass*cmps.x + Xs = (TSS_rmv/100)*inf.mass*cmps.x + + # e stands for effluent, s stands for sludge + Ce = Ze + Xe Cs = Zs + Xs - of.set_flow(Ce,'kg/hr') - uf.set_flow(Cs,'kg/hr') + + eff.set_flow(Ce,'kg/hr') + sludge.set_flow(Cs,'kg/hr') def _init_state(self): - # if multiple wastestreams exist then concentration and total inlow - # would be calculated assumping perfect mixing + + # This function is run only once during dynamic simulations + + # Since there could be multiple influents, the state of the unit is + # obtained assuming perfect mixing Qs = self._ins_QC[:,-1] Cs = self._ins_QC[:,:-1] self._state = np.append(Qs @ Cs / Qs.sum(), Qs.sum()) self._dstate = self._state * 0. - - uf, of = self.outs - s_flow = uf.F_vol/(uf.F_vol+of.F_vol) - denominator = uf.mass + of.mass - denominator += (denominator == 0) - s = uf.mass/denominator - self._sludge = np.append(s/s_flow, s_flow) - self._effluent = np.append((1-s)/(1-s_flow), 1-s_flow) - + + # To initialize the updated_thickener_factor, updated_thinning_factor + # and updated_Qu_factor for dynamic simulation + self._update_parameters() + def _update_state(self): - '''updates conditions of output stream based on conditions of the Primary Clarifier''' - self._outs[0].state = self._sludge * self._state - self._outs[1].state = self._effluent * self._state + '''updates conditions of output stream based on conditions of the Thickener''' + + # This function is run multiple times during dynamic simulation + + # Remember that here we are updating the state array of size n, which is made up + # of component concentrations in the first (n-1) cells and the last cell is flowrate. + + # So, while in the run function the effluent and sludge are split by mass, + # here they are split by concentration. Therefore, the split factors are different. + + # Updated intrinsic modelling parameters are used for dynamic simulation + thickener_factor = self.updated_thickener_factor + thinning_factor = self.updated_thinning_factor + Qu_factor = self.updated_Qu_factor + cmps = self.components + + # For sludge, the particulate concentrations are multipled by thickener factor, and + # flowrate is multiplied by Qu_factor. The soluble concentrations remains same. + uf, of = self.outs + if uf.state is None: uf.state = np.zeros(len(cmps)+1) + uf.state[:-1] = self._state[:-1]*cmps.s*1 + self._state[:-1]*cmps.x*thickener_factor + uf.state[-1] = self._state[-1]*Qu_factor + + # For effluent, the particulate concentrations are multipled by thinning factor, and + # flowrate is multiplied by Qu_factor. The soluble concentrations remains same. + if of.state is None: of.state = np.zeros(len(cmps)+1) + of.state[:-1] = self._state[:-1]*cmps.s*1 + self._state[:-1]*cmps.x*thinning_factor + of.state[-1] = self._state[-1]*(1 - Qu_factor) def _update_dstate(self): - '''updates rates of change of output stream from rates of change of the Primary Clarifier''' - self._outs[0].dstate = self._sludge * self._dstate - self._outs[1].dstate = self._effluent * self._dstate + '''updates rates of change of output stream from rates of change of the Thickener''' + + # This function is run multiple times during dynamic simulation + + # Remember that here we are updating the state array of size n, which is made up + # of component concentrations in the first (n-1) cells and the last cell is flowrate. + + # So, while in the run function the effluent and sludge are split by mass, + # here they are split by concentration. Therefore, the split factors are different. + + # Updated intrinsic modelling parameters are used for dynamic simulation + thickener_factor = self.updated_thickener_factor + thinning_factor = self.updated_thinning_factor + Qu_factor = self.updated_Qu_factor + cmps = self.components + + # For sludge, the particulate concentrations are multipled by thickener factor, and + # flowrate is multiplied by Qu_factor. The soluble concentrations remains same. + uf, of = self.outs + if uf.dstate is None: uf.dstate = np.zeros(len(cmps)+1) + uf.dstate[:-1] = self._dstate[:-1]*cmps.s*1 + self._dstate[:-1]*cmps.x*thickener_factor + uf.dstate[-1] = self._dstate[-1]*Qu_factor + + # For effluent, the particulate concentrations are multipled by thinning factor, and + # flowrate is multiplied by Qu_factor. The soluble concentrations remains same. + if of.dstate is None: of.dstate = np.zeros(len(cmps)+1) + of.dstate[:-1] = self._dstate[:-1]*cmps.s*1 + self._dstate[:-1]*cmps.x*thinning_factor + of.dstate[-1] = self._dstate[-1]*(1 - Qu_factor) @property def AE(self): @@ -1011,12 +1568,15 @@ def AE(self): return self._AE def _compile_AE(self): + + # This function is run multiple times during dynamic simulation + _state = self._state _dstate = self._dstate _update_state = self._update_state _update_dstate = self._update_dstate + _update_parameters = self._update_parameters def yt(t, QC_ins, dQC_ins): - #Because there are multiple inlets Q_ins = QC_ins[:, -1] C_ins = QC_ins[:, :-1] dQ_ins = dQC_ins[:, -1] @@ -1029,47 +1589,27 @@ def yt(t, QC_ins, dQC_ins): C_dot = (dQ_ins @ C_ins + Q_ins @ dC_ins - Q_dot * C)/Q _dstate[-1] = Q_dot _dstate[:-1] = C_dot + + _update_parameters() _update_state() _update_dstate() self._AE = yt - - - _units = { - 'Number of clarifiers': '?', - 'Cylindrical volume': 'm3', - 'Cylindrical depth': 'm', - 'Cylindrical diameter': 'm', - - 'Conical radius': 'm', - 'Conical depth': 'm', - 'Conical volume': 'm3', - - 'Volume': 'm3', - 'Center feed depth': 'm', - 'Upflow velocity': 'm/hr', - 'Center feed diameter': 'm', - 'Volume of concrete wall': 'm3', - 'Stainless steel': 'kg', - 'Pump pipe stainless steel' : 'kg', - 'Pump stainless steel': 'kg', - 'Number of pumps': '?' - } - - + def _design_pump(self): ID, pumps = self.ID, self.pumps - self._sludge.copy_like(self.outs[1]) + self._sludge.copy_like(self.outs[0]) + sludge = self._sludge ins_dct = { - 'sludge': self._sludge, + 'sludge': sludge, } - + type_dct = dict.fromkeys(pumps, 'sludge') - inputs_dct = dict.fromkeys(pumps, (1,),) + inputs_dct = dict.fromkeys(pumps, (1,)) D = self.design_results - influent_Q = self._sludge.get_total_flow('m3/hr')/D['Number of clarifiers'] - influent_Q_mgd = influent_Q*0.00634 # m3/hr to MGD + influent_Q = sludge.get_total_flow('m3/hr')/D['Number of clarifiers'] + influent_Q_mgd = influent_Q*0.00634 # m3/hr to MGD for i in pumps: if hasattr(self, f'{i}_pump'): @@ -1078,8 +1618,9 @@ def _design_pump(self): else: ID = f'{ID}_{i}' capacity_factor=1 + # No. of pumps = No. of influents pump = WWTpump( - ID=ID, ins=ins_dct[i], pump_type=type_dct[i], + ID=ID, ins= ins_dct[i], pump_type=type_dct[i], Q_mgd=influent_Q_mgd, add_inputs=inputs_dct[i], capacity_factor=capacity_factor, include_pump_cost=True, @@ -1096,70 +1637,120 @@ def _design_pump(self): pipe_ss += p_design['Pump pipe stainless steel'] pump_ss += p_design['Pump stainless steel'] return pipe_ss, pump_ss - - + + _units = { + 'Number of clarifiers': 'Unitless', + 'SOR': 'm3/day/m2', + 'Volumetric flow': 'm3/day', + 'Surface area': 'm2', + 'Cylindrical diameter': 'm', + 'Conical radius': 'm', + 'Conical depth': 'm', + 'Clarifier depth': 'm', + 'Cylindrical depth': 'm', + 'Cylindrical volume': 'm3', + 'Conical volume': 'm3', + 'Volume': 'm3', + 'Hydraulic Retention Time': 'hr', + 'Center feed depth': 'm', + 'Upflow velocity': 'm/hr', + 'Center feed diameter': 'm', + 'Volume of concrete wall': 'm3', + 'Stainless steel': 'kg', + 'Pump pipe stainless steel' : 'kg', + 'Pump stainless steel': 'kg', + 'Number of pumps': 'Unitless' + } + def _design(self): - + self._mixed.mix_from(self.ins) - + mixed = self._mixed D = self.design_results - total_flow = self._mixed.get_total_flow('m3/hr') - if total_flow <= 1580: # 10 MGD - design_flow = 790 # 5 MGD - elif total_flow >1580 and total_flow <= 4730: # Between 10 and 30 MGD - design_flow = 2365 # 15 MGD - elif total_flow > 4730 and total_flow <= 15770: # Between 30 and 100 MGD - design_flow = 3940 # 25 MGD + # Number of clarifiers based on tentative suggestions by Jeremy (would be verified through collaboration with industry) + total_flow = (mixed.get_total_flow('m3/hr')*24)/3785 # in MGD + if total_flow <= 3: + D['Number of clarifiers'] = 2 + elif total_flow > 3 and total_flow <= 8: + D['Number of clarifiers'] = 3 + elif total_flow > 8 and total_flow <=20: + D['Number of clarifiers'] = 4 else: - design_flow = 5520 # 35 MGD + D['Number of clarifiers'] = 4 + total_flow -= 20 + D['Number of clarifiers'] += np.ceil(total_flow/20) + + D['SOR'] = self.surface_overflow_rate # in (m3/day)/m2 + D['Volumetric flow'] = (mixed.get_total_flow('m3/hr')*24)/D['Number of clarifiers'] # m3/day + D['Surface area'] = D['Volumetric flow']/D['SOR'] # in m2 + D['Cylindrical diameter'] = np.sqrt(4*D['Surface area']/np.pi) #in m + + #Check on cylinderical diameter [2, 3] + if D['Cylindrical diameter'] < 3 or D['Cylindrical diameter'] > 60: + Cylindrical_dia = D['Cylindrical diameter'] + warn(f'Cylindrical diameter = {Cylindrical_dia} is not between 3 m and 60 m') - D['Number of clarifiers'] = np.ceil(total_flow/design_flow) - - total_volume = 24*self._HRT*design_flow #in m3 - working_volume = total_volume/0.8 # Assume 80% working volume - - D['Cylindrical volume'] = working_volume - # Sidewater depth of a cylindrical clarifier lies between 2.5-5m - D['Cylindrical depth'] = self.cylindrical_depth # in m - # The tank diameter can lie anywhere between 3 m to 100 m - D['Cylindrical diameter'] = (4*working_volume/(3.14*D['Cylindrical depth']))**(1/2) # in m - D['Conical radius'] = D['Cylindrical diameter']/2 - # The slope of the bottom conical floor lies between 1:10 to 1:12 - D['Conical depth'] = D['Conical radius']/10 - D['Conical volume'] = (3.14/3)*(D['Conical radius']**2)*D['Conical depth'] - - D['Volume'] = D['Cylindrical volume'] + D['Conical volume'] - - # Primary clarifiers can be center feed or peripheral feed. The design here is for the more - # commonly deployed center feed. - - # Depth of the center feed lies between 30-75% of sidewater depth + # The slope of the bottom conical floor lies between 1:10 to 1:12 [3, 4] + D['Conical depth'] = D['Conical radius']/12 + D['Clarifier depth'] = self.depth_clarifier #in m + D['Cylindrical depth'] = D['Clarifier depth'] - D['Conical depth'] + + # Check on cylinderical and conical depths + if D['Cylindrical depth'] < D['Conical depth']: + Cylindrical_depth = D['Cylindrical depth'] + Conical_depth = D['Conical depth'] + warn(f'Cylindrical depth = {Cylindrical_depth} is lower than Conical depth = {Conical_depth}') + + D['Cylindrical volume'] = np.pi*np.square(D['Cylindrical diameter']/2)*D['Cylindrical depth'] #in m3 + D['Conical volume'] = (3.14/3)*(D['Conical radius']**2)*D['Conical depth'] #in m3 + D['Volume'] = D['Cylindrical volume'] + D['Conical volume'] #in m3 + + D['Hydraulic Retention Time'] = D['Volume']/(D['Volumetric flow']/24) #in hrs + + # Check on cylinderical HRT [3] + if D['Hydraulic Retention Time'] < 1.5 or D['Hydraulic Retention Time'] > 2.5: + HRT = D['Hydraulic Retention Time'] + warn(f'HRT = {HRT} is not between 1.5 and 2.5 hrs') + + # The design here is for center feed of clarifier. + + # Depth of the center feed lies between 30-75% of sidewater depth. [3, 4] D['Center feed depth'] = 0.5*D['Cylindrical depth'] # Typical conventional feed wells are designed for an average downflow velocity - # of 10-13 mm/s and maximum velocity of 25-30 mm/s + # of 10-13 mm/s and maximum velocity of 25-30 mm/s. [4] peak_flow_safety_factor = 2.5 # assumed based on average and maximum velocities - upflow_velocity = self.upflow_velocity # in m/hr (converted from 12 mm/sec) - D['Upflow velocity'] = upflow_velocity*peak_flow_safety_factor # in m/hr - Center_feed_area = design_flow/D['Upflow velocity'] # in m2 - D['Center feed diameter'] = ((4*Center_feed_area)/3.14)**(1/2) # Sanity check: Diameter of the center feed lies between 15-25% of tank diameter + D['Upflow velocity'] = self.upflow_velocity*peak_flow_safety_factor # in m/hr + + + Center_feed_area = (D['Volumetric flow']/24)/D['Upflow velocity'] # in m2 + + + D['Center feed diameter'] = np.sqrt(4*Center_feed_area/np.pi) + + #Sanity check: Diameter of the center feed lies between 15-25% of tank diameter [4] + if D['Center feed diameter'] < 0.15*D['Cylindrical diameter'] or D['Center feed diameter'] > 0.25*D['Cylindrical diameter']: + cf_dia = D['Center feed diameter'] + tank_dia = D['Cylindrical diameter'] + warn(f'Diameter of the center feed does not lie between 15-25% of tank diameter. It is {cf_dia*100/tank_dia}% of tank diameter') # Amount of concrete required D_tank = D['Cylindrical depth']*39.37 # m to inches - # Thickness of the wall concrete, [m]. Default to be minimum of 1 ft with 1 in added for every ft of depth over 12 ft. + # Thickness of the wall concrete [m]. Default to be minimum of 1 feet with 1 inch added for every feet of depth over 12 feet. thickness_concrete_wall = (1 + max(D_tank-12, 0)/12)*0.3048 # from feet to m inner_diameter = D['Cylindrical diameter'] outer_diameter = inner_diameter + 2*thickness_concrete_wall - volume_cylindrical_wall = (3.14*D['Cylindrical depth']/4)*(outer_diameter**2 - inner_diameter**2) - volume_conical_wall = (3.14/3)*(D['Conical depth']/4)*(outer_diameter**2 - inner_diameter**2) - D['Volume of concrete wall'] = volume_cylindrical_wall + volume_conical_wall # in m3 - + volume_cylindercal_wall = (np.pi*D['Cylindrical depth']/4)*(outer_diameter**2 - inner_diameter**2) + volume_conical_wall = (np.pi/3)*(D['Conical depth']/4)*(outer_diameter**2 - inner_diameter**2) + + D['Volume of concrete wall'] = volume_cylindercal_wall + volume_conical_wall # in m3 + # Amount of metal required for center feed thickness_metal_wall = 0.5 # in m (!! NEED A RELIABLE SOURCE !!) inner_diameter_center_feed = D['Center feed diameter'] outer_diameter_center_feed = inner_diameter_center_feed + 2*thickness_metal_wall - volume_center_feed = (3.14*D['Center feed depth']/4)*(outer_diameter_center_feed**2 - inner_diameter_center_feed **2) + volume_center_feed = (3.14*D['Center feed depth']/4)*(outer_diameter_center_feed**2 - inner_diameter_center_feed**2) density_ss = 7930 # kg/m3, 18/8 Chromium D['Stainless steel'] = volume_center_feed*density_ss # in kg @@ -1167,19 +1758,21 @@ def _design(self): pipe, pumps = self._design_pump() D['Pump pipe stainless steel'] = pipe D['Pump stainless steel'] = pumps - # For primary clarifier + + #For primary clarifier D['Number of pumps'] = D['Number of clarifiers'] - + def _cost(self): - + self._mixed.mix_from(self.ins) + D = self.design_results C = self.baseline_purchase_costs # Construction of concrete and stainless steel walls C['Wall concrete'] = D['Number of clarifiers']*D['Volume of concrete wall']*self.wall_concrete_unit_cost C['Wall stainless steel'] = D['Number of clarifiers']*D['Stainless steel']*self.stainless_steel_unit_cost - + # Cost of equipment # Source of scaling exponents: Process Design and Economics for Biochemical Conversion of Lignocellulosic Biomass to Ethanol by NREL. @@ -1188,8 +1781,8 @@ def _cost(self): # Source: https://www.alibaba.com/product-detail/Peripheral-driving-clarifier-mud-scraper-waste_1600891102019.html?spm=a2700.details.0.0.47ab45a4TP0DLb base_cost_scraper = 2500 base_flow_scraper = 1 # in m3/hr (!!! Need to know whether this is for solids or influent !!!) - clarifier_flow = self._mixed.get_total_flow('m3/hr')/D['Number of clarifiers'] - C['Scraper'] = D['Number of clarifiers']*base_cost_scraper*(clarifier_flow/base_flow_scraper)**0.6 + clarifier_flow = self._mixed.get_total_flow('m3/hr')/ D['Number of clarifiers'] + C['Scraper'] = D['Number of clarifiers']*base_cost_scraper*(clarifier_flow/base_flow_scraper)**0.6 base_power_scraper = 2.75 # in kW scraper_power = D['Number of clarifiers']*base_power_scraper*(clarifier_flow/base_flow_scraper)**0.6 @@ -1198,7 +1791,7 @@ def _cost(self): base_cost_v_notch_weir = 6888 base_flow_v_notch_weir = 10 # in m3/hr C['v notch weir'] = D['Number of clarifiers']*base_cost_v_notch_weir*(clarifier_flow/base_flow_v_notch_weir)**0.6 - + # Pump (construction and maintainance) pumps = self.pumps add_OPEX = self.add_OPEX @@ -1216,10 +1809,10 @@ def _cost(self): opex_o += p_add_opex['Pump operating'] opex_m += p_add_opex['Pump maintenance'] - C['Pumps'] = pump_cost*D['Number of pumps'] - C['Pump building'] = building_cost*D['Number of pumps'] - add_OPEX['Pump operating'] = opex_o*D['Number of pumps'] - add_OPEX['Pump maintenance'] = opex_m*D['Number of pumps'] + C['Pumps'] = pump_cost*D['Number of clarifiers'] + C['Pump building'] = building_cost*D['Number of clarifiers'] + add_OPEX['Pump operating'] = opex_o*D['Number of clarifiers'] + add_OPEX['Pump maintenance'] = opex_m*D['Number of clarifiers'] # Power pumping = 0. @@ -1228,8 +1821,8 @@ def _cost(self): if p is None: continue pumping += p.power_utility.rate - - pumping = pumping*D['Number of pumps'] - self.power_utility.consumption += pumping - self.power_utility.consumption += scraper_power \ No newline at end of file + pumping = pumping*D['Number of clarifiers'] + + self.power_utility.rate += pumping + self.power_utility.rate += scraper_power From 9c4270df2601c3393ac626b94117b3177869825c Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Fri, 22 Sep 2023 11:47:50 -0700 Subject: [PATCH 192/424] Update wwt_design.py --- qsdsan/utils/wwt_design.py | 152 ++++++++++++++++++++++++------------- 1 file changed, 98 insertions(+), 54 deletions(-) diff --git a/qsdsan/utils/wwt_design.py b/qsdsan/utils/wwt_design.py index a022caa3..9287b165 100644 --- a/qsdsan/utils/wwt_design.py +++ b/qsdsan/utils/wwt_design.py @@ -5,6 +5,7 @@ This module is developed by: Joy Zhang + Saumitra Rai This module is under the University of Illinois/NCSA Open Source License. Please refer to https://github.com/QSD-Group/QSDsan/blob/main/LICENSE.txt @@ -13,7 +14,13 @@ import numpy as np -__all__ = ('get_SRT', 'get_oxygen_heterotrophs', 'get_oxygen_autotrophs', 'get_airflow', 'get_P_blower') +__all__ = ('get_SRT', + 'get_oxygen_heterotrophs', + 'get_oxygen_autotrophs', + 'get_airflow', + 'get_P_blower') + +#%% def get_SRT(system, biomass_IDs, wastage=None, active_unit_IDs=None): """ @@ -21,7 +28,7 @@ def get_SRT(system, biomass_IDs, wastage=None, active_unit_IDs=None): Parameters ---------- - system : obj + system : :class:`biosteam.System` The system whose SRT will be calculated for. biomass_IDs : tuple[str] Component IDs of active biomass. @@ -56,22 +63,28 @@ def get_SRT(system, biomass_IDs, wastage=None, active_unit_IDs=None): return retain/waste def get_oxygen_heterotrophs(system, influent=None, eff_COD_soluble = None, f_d = 0.1, b_H = 0.4, SRT = 10, Y_H = 0.625): +# def get_oxygen_heterotrophs(flow, influent_COD, eff_COD_soluble, +# f_d=0.1, b_H=0.4, SRT=10, Y_H=0.625): """ Parameters ---------- - system : obj - The system whose airflow will be calculated. - influent : iterable[:class:`WasteStream`] - Streams incoming to the process for which required oxygen needs to be calculated. The default is None. - eff_COD_soluble : TYPE, optional - DESCRIPTION. The default is None. - f_d : float - fraction of biomass that remains as cell debris. Default value is 0.1 gCOD/gCOD based on ASM2d 'f_XI_H'. - b_H : float - Decay of heterotrophs [d^-1]. The default is 0.4 based on ASM2d. - SRT : float + # system : :class:`biosteam.System` + # The system whose aeration airflow will be calculated. + flow : float + Volumetric flow rate through the system [m3/d]. + influent_COD : float + Influent COD concentration [mg/L]. The default is None. + #!!! why soluble only?? + eff_COD_soluble : float + Maximum effluent soluble COD concentration [mg/L]. + f_d : float, optional + Fraction of biomass that remains as cell debris after decay. Default value is + 0.1 gCOD/gCOD based on ASM2d 'f_XI_H'. + b_H : float, optional + Decay rate constant of heterotrophs [d^-1]. The default is 0.4 based on ASM2d. + SRT : float, optional Estimated sludge retention time of the system. Default is 10 days. - Y_H : float + Y_H : float, optional Yield of heterotrophs [gCOD/gCOD]. The default is 0.625. Returns @@ -81,46 +94,58 @@ def get_oxygen_heterotrophs(system, influent=None, eff_COD_soluble = None, f_d = References ---------- - [1] Adapted from Equation 10.10 in GDLF [Grady, Jr., C.P.L.; Daigger, G.T.; Love, N.G.; Filipe, C.D.M. Biological + [1] Adapted from Equation 10.10 in GDLF [Grady Jr, C.P.L.; Daigger, G.T.; Love, N.G.; Filipe, C.D.M. Biological Wastewater Treatment. 3rd ed. Boca Raton, FL: CRC Press 2011.] """ if influent is None: - influent = [inf for inf in system.feeds if inf.phase in ('l')] + influent = [inf for inf in system.feeds if inf.phase == 'l'] influent_flow = np.array([inf.F_vol*24 for inf in influent]) # in m3/day influent_COD = np.array([inf.COD for inf in influent]) # in mg/L if eff_COD_soluble is None: - eff_COD_soluble = np.array([eff.composite('COD', particle_size='s', unit = 'mg/L') for eff in system.products if eff.phase in 'l']) + eff_COD_soluble = np.array([eff.composite('COD', particle_size='s', unit = 'mg/L') \ + for eff in system.products if eff.phase == 'l']) mass_influent_COD = np.sum(influent_flow*influent_COD/1000) # in kg/day mass_effluent_COD = np.sum(influent_flow*eff_COD_soluble/1000) # in kg/day mass_COD_treated = mass_influent_COD - mass_effluent_COD # kg/day + + # mass_COD_treated = flow * (influent_COD - eff_COD_soluble) * 1e-3 # kg/day aeration_factor = 1 - (1 + f_d*b_H*SRT)*Y_H/(1 + b_H*SRT) return mass_COD_treated*aeration_factor - def get_oxygen_autotrophs(system, influent=None, eff_COD_soluble = None, f_d = 0.1, b_H = 0.4, b_AUT = 0.15, SRT = 10, Y_H = 0.625, Y_AUT = 0.24, K_NH = 1, U_AUT = 1, SF_DO = 1.375, ammonia_component_ID = 'S_NH4'): +# def get_oxygen_autotrophs(flow, influent_COD, eff_COD_soluble, influent_TKN, +# f_d=0.1, b_H=0.4, b_AUT=0.15, SRT=10, +# Y_H=0.625, Y_AUT=0.24, K_NH=1, U_AUT=1, SF_DO=1.375): """ Parameters ---------- - system : TYPE - DESCRIPTION. - influent : TYPE, optional - Streams incoming to activated sludge process. Default is None. - eff_COD_soluble : TYPE, optional - Maximum effluent soluble COD concentration [mg/L]. Default value is None. + # system : :class:`biosteam.System` + # The system whose aeration airflow will be calculated. + # influent : iterable[:class:`WasteStream`], optional + # Streams incoming to activated sludge process. Default is None. + flow : float + Volumetric flow rate through the system [m3/d]. + influent_COD : float + Influent COD concentration [mg/L]. + eff_COD_soluble : float + Maximum effluent soluble COD concentration [mg/L]. + influent_TKN : float + Influent TKN concentration [mg-N/L]. The default is None. f_d : float - fraction of biomass that remains as cell debris. Default value is 0.1 gCOD/gCOD based on ASM2d 'f_XI_A'. + fraction of biomass that remains as cell debris. Default value is + 0.1 gCOD/gCOD based on ASM2d 'f_XI_A'. b_H : float - Decay of heterotrophs [d^-1]. The default is 0.4 based on ASM2d. + Decay rate constant of heterotrophs [d^-1]. The default is 0.4 based on ASM2d. b_AUT : float - Decay of autotrophs [d^-1]. The default is 0.15 based on ASM2d. + Decay rate constant of autotrophs [d^-1]. The default is 0.15 based on ASM2d. SRT : float Estimated sludge retention time of the system. Default is 10 days. Y_H : float @@ -134,40 +159,46 @@ def get_oxygen_autotrophs(system, influent=None, eff_COD_soluble = None, f_d = 0 Autotrophic maximum specific growth rate, in [d^(-1)]. The default is 1.0. SF_DO: float Safety factor for dissolved oxygen. The default is 1.375. - ammonia_component_ID : tuple - Component ID for ammonia.. The default is 'S_NH4'. + ammonia_component_ID : str + Component ID for ammonia. The default is 'S_NH4'. Returns ------- float - Oxygen requirement for heterotrophs in kg/day. + Oxygen requirement for autotrophs in kg/day. References ---------- - [1] Adapted from Equation 11.16-11.19 in GDLF [Grady, Jr., C.P.L.; Daigger, G.T.; Love, N.G.; Filipe, C.D.M. + [1] Adapted from Equation 11.16-11.19 in GDLF [Grady Jr, C.P.L.; Daigger, G.T.; Love, N.G.; Filipe, C.D.M. Biological Wastewater Treatment. 3rd ed. Boca Raton, FL: CRC Press 2011.] """ if influent is None: - influent = [inf for inf in system.feeds if inf.phase in ('l')] + influent = [inf for inf in system.feeds if inf.phase == 'l'] influent_flow = np.array([inf.F_vol*24 for inf in influent]) # in m3/day influent_COD = np.array([inf.COD for inf in influent]) # in mg/L if eff_COD_soluble is None: - eff_COD_soluble = np.array([eff.composite('COD', particle_size='s', unit = 'mg/L') for eff in system.products if eff.phase in 'l']) + eff_COD_soluble = np.array([eff.composite('COD', particle_size='s', unit='mg/L') \ + for eff in system.products if eff.phase == 'l']) NR = 0.087*(1 + f_d*b_H*SRT)*Y_H/(1 + b_H*SRT) - TKN = np.array([inf.TKN for inf in influent]) - S_N_a = np.array([TKN]) - np.array([NR*(influent_COD - eff_COD_soluble)]) + TKN = np.array([inf.composite('N', organic=True) + + inf.composite('N', subgroup=(ammonia_component_ID,)) \ + for inf in influent]) + # TKN = np.array([inf.TKN for inf in influent]) + # TKN = influent_TKN + S_N_a = TKN - NR*(influent_COD - eff_COD_soluble) S_NH = K_NH*(1/SRT + b_AUT)/(U_AUT/SF_DO - (1 + b_AUT/SRT)) aeration_factor = 4.57 - (1 + f_d*b_AUT*SRT)*Y_AUT/(1 + b_AUT*SRT) mass_N_removed = np.sum(influent_flow*(S_N_a - S_NH)/1000) # kg/day + # mass_N_removed = flow*(S_N_a - S_NH)/1000 # kg/day return mass_N_removed*aeration_factor -def get_airflow(oxygen_heterotrophs, oxygen_autotrophs, oxygen_transfer_efficiency = 15): +def get_airflow(oxygen_heterotrophs, oxygen_autotrophs, oxygen_transfer_efficiency=12): """ Parameters @@ -177,7 +208,7 @@ def get_airflow(oxygen_heterotrophs, oxygen_autotrophs, oxygen_transfer_efficien oxygen_autotrophs : float In kg/day. oxygen_transfer_efficiency : float - Field oxygen transfer efficiency is percentage. The default is 15. + Field oxygen transfer efficiency in percentage. The default is 12. Returns ------- @@ -185,7 +216,7 @@ def get_airflow(oxygen_heterotrophs, oxygen_autotrophs, oxygen_transfer_efficien References ---------- - [1] Adapted from Equation 11.2 in GDLF [Grady, Jr., C.P.L.; Daigger, G.T.; Love, N.G.; Filipe, C.D.M. + [1] Adapted from Equation 11.2 in GDLF [Grady Jr, C.P.L.; Daigger, G.T.; Love, N.G.; Filipe, C.D.M. Biological Wastewater Treatment. 3rd ed. Boca Raton, FL: CRC Press 2011.] """ @@ -196,42 +227,55 @@ def get_airflow(oxygen_heterotrophs, oxygen_autotrophs, oxygen_transfer_efficien return Q_air -def get_P_blower(T=20, p_atm=101.325, q_air=None, P_inlet_loss=1, P_diffuser_loss=7, h_submergance=5.18, efficiency=0.8): +def get_P_blower(q_air, T=20, p_atm=101.325, P_inlet_loss=1, + P_diffuser_loss=7, h_submergance=5.18, efficiency=0.7, + K=0.283): """ Parameters ---------- + q_air : float + Air volumetric flow rate [m3/min]. T : float - Air temperature. (Degree celsius) + Air temperature [degree Celsius]. p_atm : float - Atmostpheric pressure. (atm) - q_air : m3/min - Air flow. + Atmostpheric pressure [kPa] P_inlet_loss : float - Head loss at inlet. kPa. + Head loss at inlet [kPa]. P_diffuser_loss : float - Head loss due to diffuser. kPa. + Head loss due to piping and diffuser [kPa]. h_submergance : float - Height of submergance in m. The default is 17 feet (5.18 m) - efficiency : TYPE + Diffuser submergance depth in m. The default is 17 feet (5.18 m) + efficiency : float Blower efficiency. Default is 0.8. + K : float, optional + Equal to (kappa - 1)/kappa, where kappa is the adiabatic exponent of air. + Default is 0.283, equivalent to kappa = 1.3947. Returns ------- - Power of blower (kW). + Power of blower [kW]. References ---------- - [1] Equation 13.1 from GPS-X user manual. - + [1] Eq.(5-77) in Metcalf & Eddy, Wastewater Engineering: Treatment and + Resource Recovery. 5th ed. New York: McGraw-Hill Education. 2014. + [2] Eq.(4.27) in Mueller, J.; William C.B.; and Popel, H.J. Aeration: + Principles and Practice, Volume 11. CRC press, 2002. """ - p_in = p_atm - P_inlet_loss + T += 273.15 + air_molar_vol = 22.4e-3 * (T/273.15)/(p_atm/101.325) # m3/mol + R = 8.31446261815324 # J/mol/K, universal gas constant + p_in = p_atm - P_inlet_loss p_out = p_atm + 9.81*h_submergance + P_diffuser_loss - Q_air = q_air*(24*60) # m3/min to m3/day + # Q_air = q_air*(24*60) # m3/min to m3/day + # P_blower = 1.4161e-5*(T + 273.15)*Q_air*((p_out/p_in)**0.283 - 1)/efficiency + # return P_blower - P_blower = 1.4161*np.power(1/10, 5)*(T + 273.15)*Q_air*(np.power(p_out/p_in, 0.283) - 1)/efficiency + Q_air = q_air/60 # m3/min to m3/s + WP = (Q_air / air_molar_vol) * R * T / K * ((p_out/p_in)**K - 1) / efficiency # in W + return WP/1000 - return P_blower \ No newline at end of file From 02db58b8612749233f35288bece232200a588ef5 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Thu, 28 Sep 2023 16:11:15 -0700 Subject: [PATCH 193/424] update RO related calculation --- qsdsan/utils/wwt_design.py | 66 ++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 34 deletions(-) diff --git a/qsdsan/utils/wwt_design.py b/qsdsan/utils/wwt_design.py index 9287b165..16c9957a 100644 --- a/qsdsan/utils/wwt_design.py +++ b/qsdsan/utils/wwt_design.py @@ -62,9 +62,9 @@ def get_SRT(system, biomass_IDs, wastage=None, active_unit_IDs=None): retain = sum([u.get_retained_mass(biomass_IDs) for u in units if u.isdynamic]) return retain/waste -def get_oxygen_heterotrophs(system, influent=None, eff_COD_soluble = None, f_d = 0.1, b_H = 0.4, SRT = 10, Y_H = 0.625): -# def get_oxygen_heterotrophs(flow, influent_COD, eff_COD_soluble, -# f_d=0.1, b_H=0.4, SRT=10, Y_H=0.625): +# def get_oxygen_heterotrophs(system, influent=None, eff_COD_soluble = None, f_d = 0.1, b_H = 0.4, SRT = 10, Y_H = 0.625): +def get_oxygen_heterotrophs(flow, influent_COD, eff_COD_soluble, + f_d=0.1, b_H=0.4, SRT=10, Y_H=0.625): """ Parameters ---------- @@ -74,7 +74,6 @@ def get_oxygen_heterotrophs(system, influent=None, eff_COD_soluble = None, f_d = Volumetric flow rate through the system [m3/d]. influent_COD : float Influent COD concentration [mg/L]. The default is None. - #!!! why soluble only?? eff_COD_soluble : float Maximum effluent soluble COD concentration [mg/L]. f_d : float, optional @@ -98,31 +97,31 @@ def get_oxygen_heterotrophs(system, influent=None, eff_COD_soluble = None, f_d = Wastewater Treatment. 3rd ed. Boca Raton, FL: CRC Press 2011.] """ - if influent is None: - influent = [inf for inf in system.feeds if inf.phase == 'l'] + # if influent is None: + # influent = [inf for inf in system.feeds if inf.phase == 'l'] - influent_flow = np.array([inf.F_vol*24 for inf in influent]) # in m3/day - influent_COD = np.array([inf.COD for inf in influent]) # in mg/L + # influent_flow = np.array([inf.F_vol*24 for inf in influent]) # in m3/day + # influent_COD = np.array([inf.COD for inf in influent]) # in mg/L - if eff_COD_soluble is None: - eff_COD_soluble = np.array([eff.composite('COD', particle_size='s', unit = 'mg/L') \ - for eff in system.products if eff.phase == 'l']) + # if eff_COD_soluble is None: + # eff_COD_soluble = np.array([eff.composite('COD', particle_size='s', unit = 'mg/L') \ + # for eff in system.products if eff.phase == 'l']) - mass_influent_COD = np.sum(influent_flow*influent_COD/1000) # in kg/day - mass_effluent_COD = np.sum(influent_flow*eff_COD_soluble/1000) # in kg/day + # mass_influent_COD = np.sum(influent_flow*influent_COD/1000) # in kg/day + # mass_effluent_COD = np.sum(influent_flow*eff_COD_soluble/1000) # in kg/day - mass_COD_treated = mass_influent_COD - mass_effluent_COD # kg/day + # mass_COD_treated = mass_influent_COD - mass_effluent_COD # kg/day - # mass_COD_treated = flow * (influent_COD - eff_COD_soluble) * 1e-3 # kg/day + mass_COD_treated = flow * (influent_COD - eff_COD_soluble) * 1e-3 # kg/day aeration_factor = 1 - (1 + f_d*b_H*SRT)*Y_H/(1 + b_H*SRT) return mass_COD_treated*aeration_factor -def get_oxygen_autotrophs(system, influent=None, eff_COD_soluble = None, f_d = 0.1, b_H = 0.4, b_AUT = 0.15, SRT = 10, - Y_H = 0.625, Y_AUT = 0.24, K_NH = 1, U_AUT = 1, SF_DO = 1.375, ammonia_component_ID = 'S_NH4'): -# def get_oxygen_autotrophs(flow, influent_COD, eff_COD_soluble, influent_TKN, -# f_d=0.1, b_H=0.4, b_AUT=0.15, SRT=10, -# Y_H=0.625, Y_AUT=0.24, K_NH=1, U_AUT=1, SF_DO=1.375): +# def get_oxygen_autotrophs(system, influent=None, eff_COD_soluble = None, f_d = 0.1, b_H = 0.4, b_AUT = 0.15, SRT = 10, +# Y_H = 0.625, Y_AUT = 0.24, K_NH = 1, U_AUT = 1, SF_DO = 1.375, ammonia_component_ID = 'S_NH4'): +def get_oxygen_autotrophs(flow, influent_COD, eff_COD_soluble, influent_TKN, + f_d=0.1, b_H=0.4, b_AUT=0.15, SRT=10, + Y_H=0.625, Y_AUT=0.24, K_NH=1, U_AUT=1, SF_DO=1.375): """ Parameters @@ -174,27 +173,26 @@ def get_oxygen_autotrophs(system, influent=None, eff_COD_soluble = None, f_d = 0 """ - if influent is None: - influent = [inf for inf in system.feeds if inf.phase == 'l'] + # if influent is None: + # influent = [inf for inf in system.feeds if inf.phase == 'l'] - influent_flow = np.array([inf.F_vol*24 for inf in influent]) # in m3/day - influent_COD = np.array([inf.COD for inf in influent]) # in mg/L + # influent_flow = np.array([inf.F_vol*24 for inf in influent]) # in m3/day + # influent_COD = np.array([inf.COD for inf in influent]) # in mg/L - if eff_COD_soluble is None: - eff_COD_soluble = np.array([eff.composite('COD', particle_size='s', unit='mg/L') \ - for eff in system.products if eff.phase == 'l']) + # if eff_COD_soluble is None: + # eff_COD_soluble = np.array([eff.composite('COD', particle_size='s', unit='mg/L') \ + # for eff in system.products if eff.phase == 'l']) NR = 0.087*(1 + f_d*b_H*SRT)*Y_H/(1 + b_H*SRT) - TKN = np.array([inf.composite('N', organic=True) + - inf.composite('N', subgroup=(ammonia_component_ID,)) \ - for inf in influent]) - # TKN = np.array([inf.TKN for inf in influent]) - # TKN = influent_TKN + # TKN = np.array([inf.composite('N', organic=True) + + # inf.composite('N', subgroup=(ammonia_component_ID,)) \ + # for inf in influent]) + TKN = influent_TKN S_N_a = TKN - NR*(influent_COD - eff_COD_soluble) S_NH = K_NH*(1/SRT + b_AUT)/(U_AUT/SF_DO - (1 + b_AUT/SRT)) aeration_factor = 4.57 - (1 + f_d*b_AUT*SRT)*Y_AUT/(1 + b_AUT*SRT) - mass_N_removed = np.sum(influent_flow*(S_N_a - S_NH)/1000) # kg/day - # mass_N_removed = flow*(S_N_a - S_NH)/1000 # kg/day + # mass_N_removed = np.sum(influent_flow*(S_N_a - S_NH)/1000) # kg/day + mass_N_removed = flow*(S_N_a - S_NH)/1000 # kg/day return mass_N_removed*aeration_factor From 91a042f3c912e3b4ee3a21debe8722ad7dfc9b36 Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Tue, 3 Oct 2023 12:08:57 -0500 Subject: [PATCH 194/424] No change --- qsdsan/utils/wwt_design.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qsdsan/utils/wwt_design.py b/qsdsan/utils/wwt_design.py index 9287b165..9b8ab91e 100644 --- a/qsdsan/utils/wwt_design.py +++ b/qsdsan/utils/wwt_design.py @@ -190,6 +190,7 @@ def get_oxygen_autotrophs(system, influent=None, eff_COD_soluble = None, f_d = 0 for inf in influent]) # TKN = np.array([inf.TKN for inf in influent]) # TKN = influent_TKN + S_N_a = TKN - NR*(influent_COD - eff_COD_soluble) S_NH = K_NH*(1/SRT + b_AUT)/(U_AUT/SF_DO - (1 + b_AUT/SRT)) aeration_factor = 4.57 - (1 + f_d*b_AUT*SRT)*Y_AUT/(1 + b_AUT*SRT) From 712544f2a06bc13c78ade6553858f6b8b6b4cda3 Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Tue, 3 Oct 2023 21:41:24 -0500 Subject: [PATCH 195/424] Added 'PrimaryClarifierBSM2' 'PrimaryClarifierBSM2' is the design of primary clarifier based on BSM2. While 'PrimaryClarifier' is the PC design we use in our centralized WRRFs. --- qsdsan/sanunits/_clarifier.py | 795 +++++++++++++++++----------------- 1 file changed, 398 insertions(+), 397 deletions(-) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index d2fde078..2d85f75a 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -21,6 +21,7 @@ __all__ = ('FlatBottomCircularClarifier', 'IdealClarifier', + 'PrimaryClarifierBSM2', 'PrimaryClarifier') F_BM_pump = 1.18*(1 + 0.007/100) # 0.007 is for miscellaneous costs @@ -781,450 +782,450 @@ def _design(self): # %% -# class PrimaryClarifierBSM2(SanUnit): +class PrimaryClarifierBSM2(SanUnit): -# """ -# A Primary clarifier based on BSM2 Layout. [1] - -# Parameters -# ---------- -# ID : str -# ID for the clarifier. The default is ''. -# ins : class:`WasteStream` -# Influent to the clarifier. Expected number of influent is 3. -# outs : class:`WasteStream` -# Sludge (uf) and treated effluent (of). -# Hydraulic Retention time : float -# Hydraulic Retention Time in days. The default is 0.04268 days, based on IWA report.[1] -# ratio_uf : float -# The ratio of sludge to primary influent. The default is 0.007, based on IWA report.[1] -# f_corr : float -# Dimensionless correction factor for removal efficiency in the primary clarifier.[1] -# cylindrical_depth : float, optional -# The depth of the cylindrical portion of clarifier [in m]. -# upflow_velocity : float, optional -# Speed with which influent enters the center feed of the clarifier [m/hr]. The default is 43.2. -# F_BM : dict -# Equipment bare modules. - -# Examples -# -------- -# >>> from qsdsan import set_thermo, Components, WasteStream -# >>> cmps = Components.load_default() -# >>> cmps_test = cmps.subgroup(['S_F', 'S_NH4', 'X_OHO', 'H2O']) -# >>> set_thermo(cmps_test) -# >>> ws = WasteStream('ws', S_F = 10, S_NH4 = 20, X_OHO = 15, H2O=1000) -# >>> from qsdsan.sanunits import PrimaryClarifier -# >>> PC = PrimaryClarifier(ID='PC', ins= (ws,), outs=('eff', 'sludge')) -# >>> PC.simulate() -# >>> uf, of = PC.outs -# >>> uf.imass['X_OHO']/ws.imass['X_OHO'] # doctest: +ELLIPSIS -# 0.280... -# >>> PC.show() -# PrimaryClarifier: PC -# ins... -# [0] ws -# phase: 'l', T: 298.15 K, P: 101325 Pa -# flow (g/hr): S_F 1e+04 -# S_NH4 2e+04 -# X_OHO 1.5e+04 -# H2O 1e+06 -# WasteStream-specific properties: -# pH : 7.0 -# COD : 23873.0 mg/L -# BOD : 14963.2 mg/L -# TC : 8298.3 mg/L -# TOC : 8298.3 mg/L -# TN : 20363.2 mg/L -# TP : 367.6 mg/L -# TK : 68.3 mg/L -# outs... -# [0] uf -# phase: 'l', T: 298.15 K, P: 101325 Pa -# flow (g/hr): S_F 70 -# S_NH4 140 -# X_OHO 4.2e+03 -# H2O 7e+03 -# WasteStream-specific properties: -# pH : 7.0 -# COD : 428873.3 mg/L -# BOD : 244072.7 mg/L -# TC : 156644.5 mg/L -# TOC : 156644.5 mg/L -# TN : 43073.0 mg/L -# TP : 8085.4 mg/L -# TK : 2011.4 mg/L -# [1] of -# phase: 'l', T: 298.15 K, P: 101325 Pa -# flow (g/hr): S_F 9.93e+03 -# S_NH4 1.99e+04 -# X_OHO 1.08e+04 -# H2O 9.93e+05 -# WasteStream-specific properties: -# pH : 7.0 -# COD : 19982.3 mg/L -# BOD : 12762.2 mg/L -# TC : 6873.2 mg/L -# TOC : 6873.2 mg/L -# TN : 20145.0 mg/L -# TP : 293.5 mg/L -# TK : 49.6 mg/L + """ + A Primary clarifier based on BSM2 Layout. [1] + + Parameters + ---------- + ID : str + ID for the clarifier. The default is ''. + ins : class:`WasteStream` + Influent to the clarifier. Expected number of influent is 3. + outs : class:`WasteStream` + Sludge (uf) and treated effluent (of). + Hydraulic Retention time : float + Hydraulic Retention Time in days. The default is 0.04268 days, based on IWA report.[1] + ratio_uf : float + The ratio of sludge to primary influent. The default is 0.007, based on IWA report.[1] + f_corr : float + Dimensionless correction factor for removal efficiency in the primary clarifier.[1] + cylindrical_depth : float, optional + The depth of the cylindrical portion of clarifier [in m]. + upflow_velocity : float, optional + Speed with which influent enters the center feed of the clarifier [m/hr]. The default is 43.2. + F_BM : dict + Equipment bare modules. + + Examples + -------- + >>> from qsdsan import set_thermo, Components, WasteStream + >>> cmps = Components.load_default() + >>> cmps_test = cmps.subgroup(['S_F', 'S_NH4', 'X_OHO', 'H2O']) + >>> set_thermo(cmps_test) + >>> ws = WasteStream('ws', S_F = 10, S_NH4 = 20, X_OHO = 15, H2O=1000) + >>> from qsdsan.sanunits import PrimaryClarifier + >>> PC = PrimaryClarifier(ID='PC', ins= (ws,), outs=('eff', 'sludge')) + >>> PC.simulate() + >>> uf, of = PC.outs + >>> uf.imass['X_OHO']/ws.imass['X_OHO'] # doctest: +ELLIPSIS + 0.280... + >>> PC.show() + PrimaryClarifier: PC + ins... + [0] ws + phase: 'l', T: 298.15 K, P: 101325 Pa + flow (g/hr): S_F 1e+04 + S_NH4 2e+04 + X_OHO 1.5e+04 + H2O 1e+06 + WasteStream-specific properties: + pH : 7.0 + COD : 23873.0 mg/L + BOD : 14963.2 mg/L + TC : 8298.3 mg/L + TOC : 8298.3 mg/L + TN : 20363.2 mg/L + TP : 367.6 mg/L + TK : 68.3 mg/L + outs... + [0] uf + phase: 'l', T: 298.15 K, P: 101325 Pa + flow (g/hr): S_F 70 + S_NH4 140 + X_OHO 4.2e+03 + H2O 7e+03 + WasteStream-specific properties: + pH : 7.0 + COD : 428873.3 mg/L + BOD : 244072.7 mg/L + TC : 156644.5 mg/L + TOC : 156644.5 mg/L + TN : 43073.0 mg/L + TP : 8085.4 mg/L + TK : 2011.4 mg/L + [1] of + phase: 'l', T: 298.15 K, P: 101325 Pa + flow (g/hr): S_F 9.93e+03 + S_NH4 1.99e+04 + X_OHO 1.08e+04 + H2O 9.93e+05 + WasteStream-specific properties: + pH : 7.0 + COD : 19982.3 mg/L + BOD : 12762.2 mg/L + TC : 6873.2 mg/L + TOC : 6873.2 mg/L + TN : 20145.0 mg/L + TP : 293.5 mg/L + TK : 49.6 mg/L -# References -# ---------- -# .. [1] Gernaey, Krist V., Ulf Jeppsson, Peter A. Vanrolleghem, and John B. Copp. -# Benchmarking of control strategies for wastewater treatment plants. IWA publishing, 2014. -# [2] Metcalf, Leonard, Harrison P. Eddy, and Georg Tchobanoglous. Wastewater -# engineering: treatment, disposal, and reuse. Vol. 4. New York: McGraw-Hill, 1991. -# """ + References + ---------- + .. [1] Gernaey, Krist V., Ulf Jeppsson, Peter A. Vanrolleghem, and John B. Copp. + Benchmarking of control strategies for wastewater treatment plants. IWA publishing, 2014. + [2] Metcalf, Leonard, Harrison P. Eddy, and Georg Tchobanoglous. Wastewater + engineering: treatment, disposal, and reuse. Vol. 4. New York: McGraw-Hill, 1991. + """ -# _N_ins = 3 -# _N_outs = 2 -# _ins_size_is_fixed = False + _N_ins = 3 + _N_outs = 2 + _ins_size_is_fixed = False -# # Costs -# wall_concrete_unit_cost = 650 / 0.765 # $/m3, 0.765 is to convert from $/yd3 -# stainless_steel_unit_cost=1.8 # $/kg (Taken from Joy's METAB code) https://www.alibaba.com/product-detail/brushed-stainless-steel-plate-304l-stainless_1600391656401.html?spm=a2700.details.0.0.230e67e6IKwwFd + # Costs + wall_concrete_unit_cost = 650 / 0.765 # $/m3, 0.765 is to convert from $/yd3 + stainless_steel_unit_cost=1.8 # $/kg (Taken from Joy's METAB code) https://www.alibaba.com/product-detail/brushed-stainless-steel-plate-304l-stainless_1600391656401.html?spm=a2700.details.0.0.230e67e6IKwwFd -# pumps = ('sludge',) + pumps = ('sludge',) -# def __init__(self, ID='', ins=None, outs=(), thermo=None, -# isdynamic=False, init_with='WasteStream', Hydraulic_Retention_Time=0.04268, -# ratio_uf=0.007, f_corr=0.65, cylindrical_depth = 5, upflow_velocity = 43.2, -# F_BM=default_F_BM, **kwargs): -# SanUnit.__init__(self, ID, ins, outs, thermo, isdynamic=isdynamic, -# init_with=init_with) -# self.Hydraulic_Retention_Time = Hydraulic_Retention_Time #in days -# self.ratio_uf = ratio_uf -# self.f_corr = f_corr -# self.cylindrical_depth = cylindrical_depth # in m -# self.upflow_velocity = upflow_velocity # in m/hr (converted from 12 mm/sec) -# self.F_BM.update(default_F_BM) -# self._mixed = self.ins[0].copy(f'{ID}_mixed') -# self._sludge = self.outs[1].copy(f'{ID}_sludge') + def __init__(self, ID='', ins=None, outs=(), thermo=None, + isdynamic=False, init_with='WasteStream', Hydraulic_Retention_Time=0.04268, + ratio_uf=0.007, f_corr=0.65, cylindrical_depth = 5, upflow_velocity = 43.2, + F_BM=default_F_BM, **kwargs): + SanUnit.__init__(self, ID, ins, outs, thermo, isdynamic=isdynamic, + init_with=init_with) + self.Hydraulic_Retention_Time = Hydraulic_Retention_Time #in days + self.ratio_uf = ratio_uf + self.f_corr = f_corr + self.cylindrical_depth = cylindrical_depth # in m + self.upflow_velocity = upflow_velocity # in m/hr (converted from 12 mm/sec) + self.F_BM.update(default_F_BM) + self._mixed = self.ins[0].copy(f'{ID}_mixed') + self._sludge = self.outs[1].copy(f'{ID}_sludge') -# @property -# def Hydraulic_Retention_Time(self): -# '''The Hydraulic Retention time in days.''' -# return self._HRT - -# @Hydraulic_Retention_Time.setter -# def Hydraulic_Retention_Time(self, HRT): -# if HRT is not None: -# self._HRT = HRT -# else: -# raise ValueError('HRT expected from user') - -# @property -# def ratio_uf(self): -# return self._r - -# @ratio_uf.setter -# def ratio_uf(self, r): -# if r is not None: -# if r > 1 or r < 0: -# raise ValueError(f'Sludge to influent ratio must be within [0, 1], not {r}') -# self._r = r -# else: -# raise ValueError('Sludge to influent ratio expected from user') + @property + def Hydraulic_Retention_Time(self): + '''The Hydraulic Retention time in days.''' + return self._HRT + + @Hydraulic_Retention_Time.setter + def Hydraulic_Retention_Time(self, HRT): + if HRT is not None: + self._HRT = HRT + else: + raise ValueError('HRT expected from user') + + @property + def ratio_uf(self): + return self._r + + @ratio_uf.setter + def ratio_uf(self, r): + if r is not None: + if r > 1 or r < 0: + raise ValueError(f'Sludge to influent ratio must be within [0, 1], not {r}') + self._r = r + else: + raise ValueError('Sludge to influent ratio expected from user') -# @property -# def f_corr(self): -# return self._corr - -# @f_corr.setter -# def f_corr(self, corr): -# if corr is not None: -# # if corr > 1 or corr < 0: -# # raise ValueError(f'correction factor must be within [0, 1], not {corr}') -# self._corr = corr -# else: -# raise ValueError('correction factor expected from user') + @property + def f_corr(self): + return self._corr + + @f_corr.setter + def f_corr(self, corr): + if corr is not None: + # if corr > 1 or corr < 0: + # raise ValueError(f'correction factor must be within [0, 1], not {corr}') + self._corr = corr + else: + raise ValueError('correction factor expected from user') -# def _f_i(self): -# xcod = self._mixed.composite('COD', particle_size='x') -# fx = xcod/self._mixed.COD -# corr = self._corr -# HRT = self._HRT -# n_COD = corr*(2.88*fx - 0.118)*(1.45 + 6.15*np.log(HRT*24*60)) -# f_i = 1 - (n_COD/100) -# return f_i + def _f_i(self): + xcod = self._mixed.composite('COD', particle_size='x') + fx = xcod/self._mixed.COD + corr = self._corr + HRT = self._HRT + n_COD = corr*(2.88*fx - 0.118)*(1.45 + 6.15*np.log(HRT*24*60)) + f_i = 1 - (n_COD/100) + return f_i -# def _run(self): -# uf, of = self.outs -# cmps = self.components -# mixed = self._mixed -# mixed.mix_from(self.ins) + def _run(self): + uf, of = self.outs + cmps = self.components + mixed = self._mixed + mixed.mix_from(self.ins) -# r = self._r -# f_i = self._f_i() + r = self._r + f_i = self._f_i() -# Xs = (1 - f_i)*mixed.mass*cmps.x -# Xe = (f_i)*mixed.mass*cmps.x + Xs = (1 - f_i)*mixed.mass*cmps.x + Xe = (f_i)*mixed.mass*cmps.x -# Zs = r*mixed.mass*cmps.s -# Ze = (1-r)*mixed.mass*cmps.s + Zs = r*mixed.mass*cmps.s + Ze = (1-r)*mixed.mass*cmps.s -# Ce = Ze + Xe -# Cs = Zs + Xs -# of.set_flow(Ce,'kg/hr') -# uf.set_flow(Cs,'kg/hr') + Ce = Ze + Xe + Cs = Zs + Xs + of.set_flow(Ce,'kg/hr') + uf.set_flow(Cs,'kg/hr') -# def _init_state(self): -# # if multiple wastestreams exist then concentration and total inlow -# # would be calculated assumping perfect mixing -# Qs = self._ins_QC[:,-1] -# Cs = self._ins_QC[:,:-1] -# self._state = np.append(Qs @ Cs / Qs.sum(), Qs.sum()) -# self._dstate = self._state * 0. + def _init_state(self): + # if multiple wastestreams exist then concentration and total inlow + # would be calculated assumping perfect mixing + Qs = self._ins_QC[:,-1] + Cs = self._ins_QC[:,:-1] + self._state = np.append(Qs @ Cs / Qs.sum(), Qs.sum()) + self._dstate = self._state * 0. -# uf, of = self.outs -# s_flow = uf.F_vol/(uf.F_vol+of.F_vol) -# denominator = uf.mass + of.mass -# denominator += (denominator == 0) -# s = uf.mass/denominator -# self._sludge = np.append(s/s_flow, s_flow) -# self._effluent = np.append((1-s)/(1-s_flow), 1-s_flow) + uf, of = self.outs + s_flow = uf.F_vol/(uf.F_vol+of.F_vol) + denominator = uf.mass + of.mass + denominator += (denominator == 0) + s = uf.mass/denominator + self._sludge = np.append(s/s_flow, s_flow) + self._effluent = np.append((1-s)/(1-s_flow), 1-s_flow) -# def _update_state(self): -# '''updates conditions of output stream based on conditions of the Primary Clarifier''' -# self._outs[0].state = self._sludge * self._state -# self._outs[1].state = self._effluent * self._state - -# def _update_dstate(self): -# '''updates rates of change of output stream from rates of change of the Primary Clarifier''' -# self._outs[0].dstate = self._sludge * self._dstate -# self._outs[1].dstate = self._effluent * self._dstate + def _update_state(self): + '''updates conditions of output stream based on conditions of the Primary Clarifier''' + self._outs[0].state = self._sludge * self._state + self._outs[1].state = self._effluent * self._state + + def _update_dstate(self): + '''updates rates of change of output stream from rates of change of the Primary Clarifier''' + self._outs[0].dstate = self._sludge * self._dstate + self._outs[1].dstate = self._effluent * self._dstate -# @property -# def AE(self): -# if self._AE is None: -# self._compile_AE() -# return self._AE - -# def _compile_AE(self): -# _state = self._state -# _dstate = self._dstate -# _update_state = self._update_state -# _update_dstate = self._update_dstate -# def yt(t, QC_ins, dQC_ins): -# #Because there are multiple inlets -# Q_ins = QC_ins[:, -1] -# C_ins = QC_ins[:, :-1] -# dQ_ins = dQC_ins[:, -1] -# dC_ins = dQC_ins[:, :-1] -# Q = Q_ins.sum() -# C = Q_ins @ C_ins / Q -# _state[-1] = Q -# _state[:-1] = C -# Q_dot = dQ_ins.sum() -# C_dot = (dQ_ins @ C_ins + Q_ins @ dC_ins - Q_dot * C)/Q -# _dstate[-1] = Q_dot -# _dstate[:-1] = C_dot -# _update_state() -# _update_dstate() -# self._AE = yt + @property + def AE(self): + if self._AE is None: + self._compile_AE() + return self._AE + + def _compile_AE(self): + _state = self._state + _dstate = self._dstate + _update_state = self._update_state + _update_dstate = self._update_dstate + def yt(t, QC_ins, dQC_ins): + #Because there are multiple inlets + Q_ins = QC_ins[:, -1] + C_ins = QC_ins[:, :-1] + dQ_ins = dQC_ins[:, -1] + dC_ins = dQC_ins[:, :-1] + Q = Q_ins.sum() + C = Q_ins @ C_ins / Q + _state[-1] = Q + _state[:-1] = C + Q_dot = dQ_ins.sum() + C_dot = (dQ_ins @ C_ins + Q_ins @ dC_ins - Q_dot * C)/Q + _dstate[-1] = Q_dot + _dstate[:-1] = C_dot + _update_state() + _update_dstate() + self._AE = yt -# _units = { -# 'Number of clarifiers': '?', -# 'Cylindrical volume': 'm3', -# 'Cylindrical depth': 'm', -# 'Cylindrical diameter': 'm', + # _units = { + # 'Number of clarifiers': '?', + # 'Cylindrical volume': 'm3', + # 'Cylindrical depth': 'm', + # 'Cylindrical diameter': 'm', -# 'Conical radius': 'm', -# 'Conical depth': 'm', -# 'Conical volume': 'm3', + # 'Conical radius': 'm', + # 'Conical depth': 'm', + # 'Conical volume': 'm3', -# 'Volume': 'm3', -# 'Center feed depth': 'm', -# 'Upflow velocity': 'm/hr', -# 'Center feed diameter': 'm', -# 'Volume of concrete wall': 'm3', -# 'Stainless steel': 'kg', -# 'Pump pipe stainless steel' : 'kg', -# 'Pump stainless steel': 'kg', -# 'Number of pumps': '?' -# } + # 'Volume': 'm3', + # 'Center feed depth': 'm', + # 'Upflow velocity': 'm/hr', + # 'Center feed diameter': 'm', + # 'Volume of concrete wall': 'm3', + # 'Stainless steel': 'kg', + # 'Pump pipe stainless steel' : 'kg', + # 'Pump stainless steel': 'kg', + # 'Number of pumps': '?' + # } -# def _design_pump(self): -# ID, pumps = self.ID, self.pumps -# self._sludge.copy_like(self.outs[1]) + # def _design_pump(self): + # ID, pumps = self.ID, self.pumps + # self._sludge.copy_like(self.outs[1]) -# ins_dct = { -# 'sludge': self._sludge, -# } + # ins_dct = { + # 'sludge': self._sludge, + # } -# type_dct = dict.fromkeys(pumps, 'sludge') -# inputs_dct = dict.fromkeys(pumps, (1,),) + # type_dct = dict.fromkeys(pumps, 'sludge') + # inputs_dct = dict.fromkeys(pumps, (1,),) -# D = self.design_results -# influent_Q = self._sludge.get_total_flow('m3/hr')/D['Number of clarifiers'] -# influent_Q_mgd = influent_Q*0.00634 # m3/hr to MGD + # D = self.design_results + # influent_Q = self._sludge.get_total_flow('m3/hr')/D['Number of clarifiers'] + # influent_Q_mgd = influent_Q*0.00634 # m3/hr to MGD -# for i in pumps: -# if hasattr(self, f'{i}_pump'): -# p = getattr(self, f'{i}_pump') -# setattr(p, 'add_inputs', inputs_dct[i]) -# else: -# ID = f'{ID}_{i}' -# capacity_factor=1 -# pump = WWTpump( -# ID=ID, ins=ins_dct[i], pump_type=type_dct[i], -# Q_mgd=influent_Q_mgd, add_inputs=inputs_dct[i], -# capacity_factor=capacity_factor, -# include_pump_cost=True, -# include_building_cost=False, -# include_OM_cost=True, -# ) -# setattr(self, f'{i}_pump', pump) - -# pipe_ss, pump_ss = 0., 0. -# for i in pumps: -# p = getattr(self, f'{i}_pump') -# p.simulate() -# p_design = p.design_results -# pipe_ss += p_design['Pump pipe stainless steel'] -# pump_ss += p_design['Pump stainless steel'] -# return pipe_ss, pump_ss + # for i in pumps: + # if hasattr(self, f'{i}_pump'): + # p = getattr(self, f'{i}_pump') + # setattr(p, 'add_inputs', inputs_dct[i]) + # else: + # ID = f'{ID}_{i}' + # capacity_factor=1 + # pump = WWTpump( + # ID=ID, ins=ins_dct[i], pump_type=type_dct[i], + # Q_mgd=influent_Q_mgd, add_inputs=inputs_dct[i], + # capacity_factor=capacity_factor, + # include_pump_cost=True, + # include_building_cost=False, + # include_OM_cost=True, + # ) + # setattr(self, f'{i}_pump', pump) + + # pipe_ss, pump_ss = 0., 0. + # for i in pumps: + # p = getattr(self, f'{i}_pump') + # p.simulate() + # p_design = p.design_results + # pipe_ss += p_design['Pump pipe stainless steel'] + # pump_ss += p_design['Pump stainless steel'] + # return pipe_ss, pump_ss -# def _design(self): + # def _design(self): -# self._mixed.mix_from(self.ins) + # self._mixed.mix_from(self.ins) -# D = self.design_results -# total_flow = self._mixed.get_total_flow('m3/hr') - -# if total_flow <= 1580: # 10 MGD -# design_flow = 790 # 5 MGD -# elif total_flow >1580 and total_flow <= 4730: # Between 10 and 30 MGD -# design_flow = 2365 # 15 MGD -# elif total_flow > 4730 and total_flow <= 15770: # Between 30 and 100 MGD -# design_flow = 3940 # 25 MGD -# else: -# design_flow = 5520 # 35 MGD - -# D['Number of clarifiers'] = np.ceil(total_flow/design_flow) + # D = self.design_results + # total_flow = self._mixed.get_total_flow('m3/hr') + + # if total_flow <= 1580: # 10 MGD + # design_flow = 790 # 5 MGD + # elif total_flow >1580 and total_flow <= 4730: # Between 10 and 30 MGD + # design_flow = 2365 # 15 MGD + # elif total_flow > 4730 and total_flow <= 15770: # Between 30 and 100 MGD + # design_flow = 3940 # 25 MGD + # else: + # design_flow = 5520 # 35 MGD + + # D['Number of clarifiers'] = np.ceil(total_flow/design_flow) -# total_volume = 24*self._HRT*design_flow #in m3 -# working_volume = total_volume/0.8 # Assume 80% working volume + # total_volume = 24*self._HRT*design_flow #in m3 + # working_volume = total_volume/0.8 # Assume 80% working volume -# D['Cylindrical volume'] = working_volume -# # Sidewater depth of a cylindrical clarifier lies between 2.5-5m -# D['Cylindrical depth'] = self.cylindrical_depth # in m -# # The tank diameter can lie anywhere between 3 m to 100 m -# D['Cylindrical diameter'] = (4*working_volume/(3.14*D['Cylindrical depth']))**(1/2) # in m + # D['Cylindrical volume'] = working_volume + # # Sidewater depth of a cylindrical clarifier lies between 2.5-5m + # D['Cylindrical depth'] = self.cylindrical_depth # in m + # # The tank diameter can lie anywhere between 3 m to 100 m + # D['Cylindrical diameter'] = (4*working_volume/(3.14*D['Cylindrical depth']))**(1/2) # in m -# D['Conical radius'] = D['Cylindrical diameter']/2 -# # The slope of the bottom conical floor lies between 1:10 to 1:12 -# D['Conical depth'] = D['Conical radius']/10 -# D['Conical volume'] = (3.14/3)*(D['Conical radius']**2)*D['Conical depth'] + # D['Conical radius'] = D['Cylindrical diameter']/2 + # # The slope of the bottom conical floor lies between 1:10 to 1:12 + # D['Conical depth'] = D['Conical radius']/10 + # D['Conical volume'] = (3.14/3)*(D['Conical radius']**2)*D['Conical depth'] -# D['Volume'] = D['Cylindrical volume'] + D['Conical volume'] + # D['Volume'] = D['Cylindrical volume'] + D['Conical volume'] -# # Primary clarifiers can be center feed or peripheral feed. The design here is for the more -# # commonly deployed center feed. + # # Primary clarifiers can be center feed or peripheral feed. The design here is for the more + # # commonly deployed center feed. -# # Depth of the center feed lies between 30-75% of sidewater depth -# D['Center feed depth'] = 0.5*D['Cylindrical depth'] -# # Typical conventional feed wells are designed for an average downflow velocity -# # of 10-13 mm/s and maximum velocity of 25-30 mm/s -# peak_flow_safety_factor = 2.5 # assumed based on average and maximum velocities -# upflow_velocity = self.upflow_velocity # in m/hr (converted from 12 mm/sec) -# D['Upflow velocity'] = upflow_velocity*peak_flow_safety_factor # in m/hr -# Center_feed_area = design_flow/D['Upflow velocity'] # in m2 -# D['Center feed diameter'] = ((4*Center_feed_area)/3.14)**(1/2) # Sanity check: Diameter of the center feed lies between 15-25% of tank diameter - -# # Amount of concrete required -# D_tank = D['Cylindrical depth']*39.37 # m to inches -# # Thickness of the wall concrete, [m]. Default to be minimum of 1 ft with 1 in added for every ft of depth over 12 ft. -# thickness_concrete_wall = (1 + max(D_tank-12, 0)/12)*0.3048 # from feet to m -# inner_diameter = D['Cylindrical diameter'] -# outer_diameter = inner_diameter + 2*thickness_concrete_wall -# volume_cylindrical_wall = (3.14*D['Cylindrical depth']/4)*(outer_diameter**2 - inner_diameter**2) -# volume_conical_wall = (3.14/3)*(D['Conical depth']/4)*(outer_diameter**2 - inner_diameter**2) -# D['Volume of concrete wall'] = volume_cylindrical_wall + volume_conical_wall # in m3 + # # Depth of the center feed lies between 30-75% of sidewater depth + # D['Center feed depth'] = 0.5*D['Cylindrical depth'] + # # Typical conventional feed wells are designed for an average downflow velocity + # # of 10-13 mm/s and maximum velocity of 25-30 mm/s + # peak_flow_safety_factor = 2.5 # assumed based on average and maximum velocities + # upflow_velocity = self.upflow_velocity # in m/hr (converted from 12 mm/sec) + # D['Upflow velocity'] = upflow_velocity*peak_flow_safety_factor # in m/hr + # Center_feed_area = design_flow/D['Upflow velocity'] # in m2 + # D['Center feed diameter'] = ((4*Center_feed_area)/3.14)**(1/2) # Sanity check: Diameter of the center feed lies between 15-25% of tank diameter + + # # Amount of concrete required + # D_tank = D['Cylindrical depth']*39.37 # m to inches + # # Thickness of the wall concrete, [m]. Default to be minimum of 1 ft with 1 in added for every ft of depth over 12 ft. + # thickness_concrete_wall = (1 + max(D_tank-12, 0)/12)*0.3048 # from feet to m + # inner_diameter = D['Cylindrical diameter'] + # outer_diameter = inner_diameter + 2*thickness_concrete_wall + # volume_cylindrical_wall = (3.14*D['Cylindrical depth']/4)*(outer_diameter**2 - inner_diameter**2) + # volume_conical_wall = (3.14/3)*(D['Conical depth']/4)*(outer_diameter**2 - inner_diameter**2) + # D['Volume of concrete wall'] = volume_cylindrical_wall + volume_conical_wall # in m3 -# # Amount of metal required for center feed -# thickness_metal_wall = 0.5 # in m (!! NEED A RELIABLE SOURCE !!) -# inner_diameter_center_feed = D['Center feed diameter'] -# outer_diameter_center_feed = inner_diameter_center_feed + 2*thickness_metal_wall -# volume_center_feed = (3.14*D['Center feed depth']/4)*(outer_diameter_center_feed**2 - inner_diameter_center_feed **2) -# density_ss = 7930 # kg/m3, 18/8 Chromium -# D['Stainless steel'] = volume_center_feed*density_ss # in kg + # # Amount of metal required for center feed + # thickness_metal_wall = 0.5 # in m (!! NEED A RELIABLE SOURCE !!) + # inner_diameter_center_feed = D['Center feed diameter'] + # outer_diameter_center_feed = inner_diameter_center_feed + 2*thickness_metal_wall + # volume_center_feed = (3.14*D['Center feed depth']/4)*(outer_diameter_center_feed**2 - inner_diameter_center_feed **2) + # density_ss = 7930 # kg/m3, 18/8 Chromium + # D['Stainless steel'] = volume_center_feed*density_ss # in kg -# # Pumps -# pipe, pumps = self._design_pump() -# D['Pump pipe stainless steel'] = pipe -# D['Pump stainless steel'] = pumps -# # For primary clarifier -# D['Number of pumps'] = D['Number of clarifiers'] + # # Pumps + # pipe, pumps = self._design_pump() + # D['Pump pipe stainless steel'] = pipe + # D['Pump stainless steel'] = pumps + # # For primary clarifier + # D['Number of pumps'] = D['Number of clarifiers'] -# def _cost(self): + # def _cost(self): -# self._mixed.mix_from(self.ins) -# D = self.design_results -# C = self.baseline_purchase_costs + # self._mixed.mix_from(self.ins) + # D = self.design_results + # C = self.baseline_purchase_costs -# # Construction of concrete and stainless steel walls -# C['Wall concrete'] = D['Number of clarifiers']*D['Volume of concrete wall']*self.wall_concrete_unit_cost -# C['Wall stainless steel'] = D['Number of clarifiers']*D['Stainless steel']*self.stainless_steel_unit_cost - -# # Cost of equipment - -# # Source of scaling exponents: Process Design and Economics for Biochemical Conversion of Lignocellulosic Biomass to Ethanol by NREL. - -# # Scraper -# # Source: https://www.alibaba.com/product-detail/Peripheral-driving-clarifier-mud-scraper-waste_1600891102019.html?spm=a2700.details.0.0.47ab45a4TP0DLb -# base_cost_scraper = 2500 -# base_flow_scraper = 1 # in m3/hr (!!! Need to know whether this is for solids or influent !!!) -# clarifier_flow = self._mixed.get_total_flow('m3/hr')/D['Number of clarifiers'] -# C['Scraper'] = D['Number of clarifiers']*base_cost_scraper*(clarifier_flow/base_flow_scraper)**0.6 -# base_power_scraper = 2.75 # in kW -# scraper_power = D['Number of clarifiers']*base_power_scraper*(clarifier_flow/base_flow_scraper)**0.6 - -# # v notch weir -# # Source: https://www.alibaba.com/product-detail/50mm-Tube-Settler-Media-Modules-Inclined_1600835845218.html?spm=a2700.galleryofferlist.normal_offer.d_title.69135ff6o4kFPb -# base_cost_v_notch_weir = 6888 -# base_flow_v_notch_weir = 10 # in m3/hr -# C['v notch weir'] = D['Number of clarifiers']*base_cost_v_notch_weir*(clarifier_flow/base_flow_v_notch_weir)**0.6 - -# # Pump (construction and maintainance) -# pumps = self.pumps -# add_OPEX = self.add_OPEX -# pump_cost = 0. -# building_cost = 0. -# opex_o = 0. -# opex_m = 0. + # # Construction of concrete and stainless steel walls + # C['Wall concrete'] = D['Number of clarifiers']*D['Volume of concrete wall']*self.wall_concrete_unit_cost + # C['Wall stainless steel'] = D['Number of clarifiers']*D['Stainless steel']*self.stainless_steel_unit_cost + + # # Cost of equipment + + # # Source of scaling exponents: Process Design and Economics for Biochemical Conversion of Lignocellulosic Biomass to Ethanol by NREL. + + # # Scraper + # # Source: https://www.alibaba.com/product-detail/Peripheral-driving-clarifier-mud-scraper-waste_1600891102019.html?spm=a2700.details.0.0.47ab45a4TP0DLb + # base_cost_scraper = 2500 + # base_flow_scraper = 1 # in m3/hr (!!! Need to know whether this is for solids or influent !!!) + # clarifier_flow = self._mixed.get_total_flow('m3/hr')/D['Number of clarifiers'] + # C['Scraper'] = D['Number of clarifiers']*base_cost_scraper*(clarifier_flow/base_flow_scraper)**0.6 + # base_power_scraper = 2.75 # in kW + # scraper_power = D['Number of clarifiers']*base_power_scraper*(clarifier_flow/base_flow_scraper)**0.6 + + # # v notch weir + # # Source: https://www.alibaba.com/product-detail/50mm-Tube-Settler-Media-Modules-Inclined_1600835845218.html?spm=a2700.galleryofferlist.normal_offer.d_title.69135ff6o4kFPb + # base_cost_v_notch_weir = 6888 + # base_flow_v_notch_weir = 10 # in m3/hr + # C['v notch weir'] = D['Number of clarifiers']*base_cost_v_notch_weir*(clarifier_flow/base_flow_v_notch_weir)**0.6 + + # # Pump (construction and maintainance) + # pumps = self.pumps + # add_OPEX = self.add_OPEX + # pump_cost = 0. + # building_cost = 0. + # opex_o = 0. + # opex_m = 0. -# for i in pumps: -# p = getattr(self, f'{i}_pump') -# p_cost = p.baseline_purchase_costs -# p_add_opex = p.add_OPEX -# pump_cost += p_cost['Pump'] -# building_cost += p_cost['Pump building'] -# opex_o += p_add_opex['Pump operating'] -# opex_m += p_add_opex['Pump maintenance'] - -# C['Pumps'] = pump_cost*D['Number of pumps'] -# C['Pump building'] = building_cost*D['Number of pumps'] -# add_OPEX['Pump operating'] = opex_o*D['Number of pumps'] -# add_OPEX['Pump maintenance'] = opex_m*D['Number of pumps'] + # for i in pumps: + # p = getattr(self, f'{i}_pump') + # p_cost = p.baseline_purchase_costs + # p_add_opex = p.add_OPEX + # pump_cost += p_cost['Pump'] + # building_cost += p_cost['Pump building'] + # opex_o += p_add_opex['Pump operating'] + # opex_m += p_add_opex['Pump maintenance'] + + # C['Pumps'] = pump_cost*D['Number of pumps'] + # C['Pump building'] = building_cost*D['Number of pumps'] + # add_OPEX['Pump operating'] = opex_o*D['Number of pumps'] + # add_OPEX['Pump maintenance'] = opex_m*D['Number of pumps'] -# # Power -# pumping = 0. -# for ID in self.pumps: -# p = getattr(self, f'{ID}_pump') -# if p is None: -# continue -# pumping += p.power_utility.rate + # # Power + # pumping = 0. + # for ID in self.pumps: + # p = getattr(self, f'{ID}_pump') + # if p is None: + # continue + # pumping += p.power_utility.rate -# pumping = pumping*D['Number of pumps'] + # pumping = pumping*D['Number of pumps'] -# self.power_utility.consumption += pumping -# self.power_utility.consumption += scraper_power + # self.power_utility.consumption += pumping + # self.power_utility.consumption += scraper_power # Asign a bare module of 1 to all From 59f62c9da3cb2abfdf70e30d8af562c363a98198 Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Wed, 4 Oct 2023 14:36:55 -0500 Subject: [PATCH 196/424] Updated check in PC The lower limit of check on center feed depth has been modified to 10% based on advised range of upflow velocity --- qsdsan/sanunits/_clarifier.py | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index 2d85f75a..9f253a36 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -1232,6 +1232,9 @@ def yt(t, QC_ins, dQC_ins): default_F_BM = { 'Wall concrete': 1., 'Wall stainless steel': 1., + 'Scraper': 1, + 'v notch weir': 1, + 'Pumps': 1 } default_F_BM.update(default_WWTpump_F_BM) @@ -1261,7 +1264,7 @@ class PrimaryClarifier(SanUnit): Default value of 4.5 m would be used here. upflow_velocity : float, optional Speed with which influent enters the center feed of the clarifier [m/hr]. [4] - The default is 43.2 m/hr. (12 mm/sec) + The default is 36 m/hr. (10 mm/sec) F_BM : dict Equipment bare modules. @@ -1353,7 +1356,7 @@ class PrimaryClarifier(SanUnit): def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, init_with='WasteStream', thickener_perc=7, TSS_removal_perc=98, surface_overflow_rate = 41, depth_clarifier=4.5, - upflow_velocity=43.2, F_BM=default_F_BM, **kwargs): + upflow_velocity=36, F_BM=default_F_BM, **kwargs): SanUnit.__init__(self, ID, ins, outs, thermo, isdynamic=isdynamic, init_with=init_with) self.thickener_perc = thickener_perc @@ -1379,17 +1382,17 @@ def thickener_perc(self, tp): else: raise ValueError('percentage of SS in the underflow of the thickener expected from user') - @property - def solids_loading_rate(self): - '''solids_loading_rate is the loading in the clarifier''' - return self._slr - - @solids_loading_rate.setter - def solids_loading_rate(self, slr): - if slr is not None: - self._slr = slr - else: - raise ValueError('solids_loading_rate of the clarifier expected from user') + # @property + # def solids_loading_rate(self): + # '''solids_loading_rate is the loading in the clarifier''' + # return self._slr + + # @solids_loading_rate.setter + # def solids_loading_rate(self, slr): + # if slr is not None: + # self._slr = slr + # else: + # raise ValueError('solids_loading_rate of the clarifier expected from user') @property def TSS_removal_perc(self): @@ -1668,7 +1671,8 @@ def _design(self): mixed = self._mixed D = self.design_results - # Number of clarifiers based on tentative suggestions by Jeremy (would be verified through collaboration with industry) + # Number of clarifiers based on tentative suggestions by Jeremy + # (would be verified through collaboration with industry) total_flow = (mixed.get_total_flow('m3/hr')*24)/3785 # in MGD if total_flow <= 3: D['Number of clarifiers'] = 2 @@ -1730,7 +1734,8 @@ def _design(self): D['Center feed diameter'] = np.sqrt(4*Center_feed_area/np.pi) #Sanity check: Diameter of the center feed lies between 15-25% of tank diameter [4] - if D['Center feed diameter'] < 0.15*D['Cylindrical diameter'] or D['Center feed diameter'] > 0.25*D['Cylindrical diameter']: + #The lower limit of this check has been modified to 10% based on advised range of upflow velocity in [4]. + if D['Center feed diameter'] < 0.10*D['Cylindrical diameter'] or D['Center feed diameter'] > 0.25*D['Cylindrical diameter']: cf_dia = D['Center feed diameter'] tank_dia = D['Cylindrical diameter'] warn(f'Diameter of the center feed does not lie between 15-25% of tank diameter. It is {cf_dia*100/tank_dia}% of tank diameter') From 4ec5df016fd0c0f22410f2f1bda6d794769328aa Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Thu, 5 Oct 2023 16:11:53 -0500 Subject: [PATCH 197/424] Updated clarifier design and cost estimates Made several changes to secondary clarifier design, and added checks on calculated parameters based on MOP 8. Replicated few minor changes to PC as well. --- qsdsan/sanunits/_clarifier.py | 220 +++++++++++++++++++++------------- 1 file changed, 137 insertions(+), 83 deletions(-) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index 9f253a36..75cc14fa 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -50,6 +50,16 @@ def _settling_flux(X, v_max, v_max_practical, X_min, rh, rp, n0): # return X*max(v, 0) +# Asign a bare module of 1 to all +default_F_BM = { + 'Wall concrete': 1., + 'Wall stainless steel': 1., + 'Scraper': 1, + 'v notch weir': 1, + 'Pumps': 1 + } +default_F_BM.update(default_WWTpump_F_BM) + class FlatBottomCircularClarifier(SanUnit): """ A flat-bottom circular clarifier with a simple 1-dimensional @@ -71,8 +81,6 @@ class FlatBottomCircularClarifier(SanUnit): Surface area of the clarifier, in [m^2]. The default is 1500. height : float, optional Height of the clarifier, in [m]. The default is 4. - upflow_velocity : float, optional - Speed with which influent enters the center feed of the clarifier [m/hr]. The default is 43.2. N_layer : int, optional The number of layers to model settling. The default is 10. feed_layer : int, optional @@ -93,13 +101,23 @@ class FlatBottomCircularClarifier(SanUnit): fns : float, optional Non-settleable fraction of the suspended solids, dimensionless. Must be within [0, 1]. The default is 2.28e-3. - + downward_flow_velocity : float, optional + Speed on the basis of which center feed diameter is designed [m/hr]. The default is 42 m/hr (0.7 m/min). [2] + solids_loading_rate : float, optional + Rate of total suspended solids entering the secondary clarifier (kg/(m2*hr)). + The default is 5 kg/(m2*hr) [3, 4] + References ---------- .. [1] Takács, I.; Patry, G. G.; Nolasco, D. A Dynamic Model of the Clarification -Thickening Process. Water Res. 1991, 25 (10), 1263–1271. https://doi.org/10.1016/0043-1354(91)90066-Y. - + .. [2] Chapter-12: Suspended-growth Treatment Processes. WEF Manual of Practice No. 8. + 6th Edition. Virginia: McGraw-Hill, 2018. + .. [3] Metcalf, Leonard, Harrison P. Eddy, and Georg Tchobanoglous. Wastewater + engineering: treatment, disposal, and reuse. Vol. 4. New York: McGraw-Hill, 1991. + .. [4] Introduction to Wastewater Clarifier Design by Nikolay Voutchkov, PE, BCEE. + .. [5] RECOMMENDED STANDARDS for WASTEWATER FACILITIES. 10 state standards. 2014 edition. """ _N_ins = 1 @@ -113,13 +131,13 @@ class FlatBottomCircularClarifier(SanUnit): def __init__(self, ID='', ins=None, outs=(), thermo=None, init_with='WasteStream', underflow=2000, wastage=385, - surface_area=1500, height=4, upflow_velocity=43.2, N_layer=10, feed_layer=4, + surface_area=1500, height=4, N_layer=10, feed_layer=4, X_threshold=3000, v_max=474, v_max_practical=250, - rh=5.76e-4, rp=2.86e-3, fns=2.28e-3, F_BM_default=default_F_BM, isdynamic=True, **kwargs): + rh=5.76e-4, rp=2.86e-3, fns=2.28e-3, F_BM_default=default_F_BM, isdynamic=True, + downward_flow_velocity=42, solids_loading_rate = 6, **kwargs): SanUnit.__init__(self, ID, ins, outs, thermo, init_with, isdynamic=isdynamic, F_BM_default=1) self._h = height - self.upflow_velocity = upflow_velocity # in m/hr (converted from 12 mm/sec) self._Qras = underflow self._Qwas = wastage self._sludge = WasteStream() @@ -138,6 +156,10 @@ def __init__(self, ID='', ins=None, outs=(), thermo=None, self._solubles = None self._X_comp = np.zeros(len(self.components)) self._dX_comp = self._X_comp.copy() + + self._downward_flow_velocity = downward_flow_velocity # in m/hr (converted from 12 mm/sec) + self._slr = solids_loading_rate + self._mixed = WasteStream(f'{ID}_mixed') header = self._state_header self._state_header = list(header) + [f'TSS{i+1} [mg/L]' for i in range(N_layer)] @@ -281,7 +303,19 @@ def fns(self): def fns(self, fns): if fns < 0 or fns > 1: raise ValueError('fns must be within [0,1].') self._fns = fns - + + @property + def solids_loading_rate(self): + '''solids_loading_rate is the loading in the clarifier''' + return self._slr + + @solids_loading_rate.setter + def solids_loading_rate(self, slr): + if slr is not None: + self._slr = slr + else: + raise ValueError('solids_loading_rate of the clarifier expected from user') + def set_init_solubles(self, **kwargs): '''set the initial concentrations [mg/L] of solubles in the clarifier.''' Cs = np.zeros(len(self.components)) @@ -476,24 +510,25 @@ def dy_dt(t, QC_ins, QC, dQC_ins): self._ODE = dy_dt _units = { - 'Cylindrical volume': 'm3', - 'Cylindrical depth': 'm', - 'Cylindrical diameter': 'm', - - 'Conical radius': 'm', - 'Conical depth': 'm', - 'Conical volume': 'm3', - + 'Number of clarifiers': 'Unitless', + 'Volumetric flow': 'm3/day', + 'Clarifier depth': 'm', + 'Solids loading rate': 'kg/m2/hr', + 'Surface area': 'm2', + 'Clarifier diameter': 'm', 'Volume': 'm3', + 'Surface overflow rate': 'm3/day/m2', + 'Hydraulic Retention Time': 'hr', 'Center feed depth': 'm', - 'Upflow velocity': 'm/hr', + 'Downward flow velocity': 'm/hr', 'Center feed diameter': 'm', 'Volume of concrete wall': 'm3', 'Stainless steel': 'kg', 'Pump pipe stainless steel' : 'kg', - 'Pump stainless steel': 'kg' + 'Pump stainless steel': 'kg', + 'Number of pumps': 'Unitless' } - + def _design_pump(self): ID, pumps = self.ID, self.pumps @@ -546,67 +581,82 @@ def _design_pump(self): pipe_ss += p_design['Pump pipe stainless steel'] pump_ss += p_design['Pump stainless steel'] return pipe_ss, pump_ss - + def _design(self): self._mixed.mix_from(self.ins) - + mixed = self._mixed D = self.design_results - total_flow = self._mixed.get_total_flow('m3/hr') - - if total_flow <= 1580: # 10 MGD - design_flow = 790 # 5 MGD - elif total_flow >1580 and total_flow <= 4730: # Between 10 and 30 MGD - design_flow = 2365 # 15 MGD - elif total_flow > 4730 and total_flow <= 15770: # Between 30 and 100 MGD - design_flow = 3940 # 25 MGD + + # Number of clarifiers based on tentative suggestions by Jeremy + # (would be verified through collaboration with industry) + total_flow = (mixed.get_total_flow('m3/hr')*24)/3785 # in MGD + if total_flow <= 3: + D['Number of clarifiers'] = 2 + elif total_flow > 3 and total_flow <= 8: + D['Number of clarifiers'] = 3 + elif total_flow > 8 and total_flow <=20: + D['Number of clarifiers'] = 4 else: - design_flow = 5520 # 35 MGD - - D = self.design_results + D['Number of clarifiers'] = 4 + total_flow -= 20 + D['Number of clarifiers'] += np.ceil(total_flow/20) + + D['Volumetric flow'] = (mixed.get_total_flow('m3/hr')*24)/D['Number of clarifiers'] #m3/day - D['Number of clarifiers'] = np.ceil(self._mixed.get_total_flow('m3/hr')/design_flow) + # Sidewater depth of a cylindrical clarifier lies between 4-5 m (MOP 8) + D['Clarifier depth'] = self._h # in m - D['Cylindrical volume'] = self._V # in m3 - # Sidewater depth of a cylindrical clarifier lies between 2.5-5m - D['Cylindrical depth'] = self._h # in m - # The tank diameter can lie anywhere between 3 m to 100 m - D['Cylindrical diameter'] = (4*D['Cylindrical volume']/(3.14*D['Cylindrical depth']))**(1/2) # in m - - D['Conical radius'] = D['Cylindrical diameter']/2 - # The slope of the bottom conical floor lies between 1:10 to 1:12 - D['Conical depth'] = D['Conical radius']/10 - D['Conical volume'] = (3.14/3)*(D['Conical radius']**2)*D['Conical depth'] - - D['Volume'] = D['Cylindrical volume'] + D['Conical volume'] - - # Clarifiers can be center feed or peripheral feed. The design here is for the more - # commonly deployed center feed. - - # Depth of the center feed lies between 30-75% of sidewater depth - D['Center feed depth'] = 0.5*D['Cylindrical depth'] - # Typical conventional feed wells are designed for an average downflow velocity - # of 10-13 mm/s and maximum velocity of 25-30 mm/s - peak_flow_safety_factor = 2.5 # assumed based on average and maximum velocities - D['Upflow velocity'] = self.upflow_velocity*peak_flow_safety_factor # in m/hr - Center_feed_area = design_flow/D['Upflow velocity'] # in m2 - D['Center feed diameter'] = ((4*Center_feed_area)/3.14)**(1/2) # Sanity check: Diameter of the center feed lies between 15-25% of tank diameter + total_solids = mixed.get_TSS()*mixed.get_total_flow('m3/hr')/1000 # in kg/hr (mg/l * m3/hr) + solids_clarifier = total_solids/D['Number of clarifiers'] # in kg/hr + D['Solids loading rate'] = self._slr # kg/(m2*hr) + + # Check on SOR [3, 4, 5] + if D['Solids loading rate'] > 14: + slr = D['Solids loading rate'] + warn(f'Solids loading rate = {slr} is above recommended level of 14 kg/hr/m2') + + # Area of clarifier decided based on solids loading rate + D['Surface area'] = solids_clarifier/D['Solids loading rate'] #m2 + D['Clarifier diameter'] = np.sqrt(4*D['Surface area']/np.pi) # in m + D['Volume'] = D['Surface area']*D['Clarifier depth'] # in m3 + + # Check on SOR [3, 4, 5] + D['Surface overflow rate'] = D['Volumetric flow']/D['Surface area'] + if D['Surface overflow rate'] > 49: + sor = D['Surface overflow rate'] + warn(f'Surface overflow rate = {sor} is above recommended level of 49 m3/day/m2') + + # HRT + D['Hydraulic Retention Time'] = D['Volume']*24/D['Volumetric flow'] # in hr + + # Clarifiers can be center feed or peripheral feed. The design here is for the more commonly deployed center feed. + # Depth of the center feed lies between 30-75% of sidewater depth. [2] + D['Center feed depth'] = 0.5*D['Clarifier depth'] + # Criteria for downward velocity of flow determine + D['Downward flow velocity'] = self._downward_flow_velocity # in m/hr + Center_feed_area = (D['Volumetric flow']/24)/D['Downward flow velocity'] # in m2 + D['Center feed diameter'] = np.sqrt(4*Center_feed_area/np.pi) + #Sanity check: Diameter of the center feed lies between 20-25% of tank diameter [2] + if D['Center feed diameter'] < 0.20*D['Clarifier diameter'] or D['Center feed diameter'] > 0.25*D['Clarifier diameter']: + cf_dia = D['Center feed diameter'] + tank_dia = D['Clarifier diameter'] + warn(f'Diameter of the center feed does not lie between 20-25% of tank diameter. It is {cf_dia*100/tank_dia} % of tank diameter') + # Amount of concrete required - D_tank = D['Cylindrical depth']*39.37 # m to inches + D_tank = D['Clarifier depth']*39.37 # m to inches # Thickness of the wall concrete, [m]. Default to be minimum of 1 ft with 1 in added for every ft of depth over 12 ft. thickness_concrete_wall = (1 + max(D_tank-12, 0)/12)*0.3048 # from feet to m - inner_diameter = D['Cylindrical diameter'] + inner_diameter = D['Clarifier diameter'] outer_diameter = inner_diameter + 2*thickness_concrete_wall - volume_cylindrical_wall = (3.14*D['Cylindrical depth']/4)*(outer_diameter**2 - inner_diameter**2) - volume_conical_wall = (3.14/3)*(D['Conical depth']/4)*(outer_diameter**2 - inner_diameter**2) - D['Volume of concrete wall'] = volume_cylindrical_wall + volume_conical_wall # in m3 + D['Volume of concrete wall'] = (np.pi*D['Clarifier depth']/4)*(outer_diameter**2 - inner_diameter**2) # Amount of metal required for center feed - thickness_metal_wall = 0.5 # in m (!! NEED A RELIABLE SOURCE !!) + thickness_metal_wall = 0.3048 # equal to 1 feet, in m (!! NEED A RELIABLE SOURCE !!) inner_diameter_center_feed = D['Center feed diameter'] outer_diameter_center_feed = inner_diameter_center_feed + 2*thickness_metal_wall - volume_center_feed = (3.14*D['Center feed depth']/4)*(outer_diameter_center_feed**2 - inner_diameter_center_feed **2) + volume_center_feed = (np.pi*D['Center feed depth']/4)*(outer_diameter_center_feed**2 - inner_diameter_center_feed **2) density_ss = 7930 # kg/m3, 18/8 Chromium D['Stainless steel'] = volume_center_feed*density_ss # in kg @@ -614,6 +664,7 @@ def _design(self): pipe, pumps = self._design_pump() D['Pump pipe stainless steel'] = pipe D['Pump stainless steel'] = pumps + # For secondary clarifier D['Number of pumps'] = 2*D['Number of clarifiers'] @@ -634,7 +685,7 @@ def _cost(self): # Source: https://www.alibaba.com/product-detail/Peripheral-driving-clarifier-mud-scraper-waste_1600891102019.html?spm=a2700.details.0.0.47ab45a4TP0DLb base_cost_scraper = 2500 base_flow_scraper = 1 # in m3/hr (!!! Need to know whether this is for solids or influent !!!) - clarifier_flow = self._mixed.get_total_flow('m3/hr')/D['Number of clarifiers'] + clarifier_flow = D['Volumetric flow']/24 # in m3/hr C['Scraper'] = D['Number of clarifiers']*base_cost_scraper*(clarifier_flow/base_flow_scraper)**0.6 base_power_scraper = 2.75 # in kW scraper_power = D['Number of clarifiers']*base_power_scraper*(clarifier_flow/base_flow_scraper)**0.6 @@ -653,6 +704,7 @@ def _cost(self): opex_o = 0. opex_m = 0. + # i would be 0 and 1 for RAS and WAS respectively for i in pumps: p = getattr(self, f'{i}_pump') p_cost = p.baseline_purchase_costs @@ -661,11 +713,12 @@ def _cost(self): building_cost += p_cost['Pump building'] opex_o += p_add_opex['Pump operating'] opex_m += p_add_opex['Pump maintenance'] - - C['Pumps'] = pump_cost - C['Pump building'] = building_cost - add_OPEX['Pump operating'] = opex_o - add_OPEX['Pump maintenance'] = opex_m + + # All costs associated with pumping need to be multiplied by number of clarifiers + C['Pumps'] = pump_cost*D['Number of clarifiers'] + C['Pump building'] = building_cost*D['Number of clarifiers'] + add_OPEX['Pump operating'] = opex_o*D['Number of clarifiers'] + add_OPEX['Pump maintenance'] = opex_m*D['Number of clarifiers'] # Power pumping = 0. @@ -675,10 +728,11 @@ def _cost(self): continue pumping += p.power_utility.rate + pumping = pumping*D['Number of clarifiers'] + self.power_utility.rate += pumping self.power_utility.consumption += scraper_power - - + # %% class IdealClarifier(SanUnit): @@ -801,6 +855,7 @@ class PrimaryClarifierBSM2(SanUnit): The ratio of sludge to primary influent. The default is 0.007, based on IWA report.[1] f_corr : float Dimensionless correction factor for removal efficiency in the primary clarifier.[1] + cylindrical_depth : float, optional The depth of the cylindrical portion of clarifier [in m]. upflow_velocity : float, optional @@ -1262,8 +1317,8 @@ class PrimaryClarifier(SanUnit): depth_clarifier : float Depth of clarifier. Typical depths range from 3 m to 4.9 m [2, 3]. Default value of 4.5 m would be used here. - upflow_velocity : float, optional - Speed with which influent enters the center feed of the clarifier [m/hr]. [4] + downward_flow_velocity : float, optional + Speed on the basis of which center feed diameter is designed [m/hr]. [4] The default is 36 m/hr. (10 mm/sec) F_BM : dict Equipment bare modules. @@ -1356,14 +1411,14 @@ class PrimaryClarifier(SanUnit): def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, init_with='WasteStream', thickener_perc=7, TSS_removal_perc=98, surface_overflow_rate = 41, depth_clarifier=4.5, - upflow_velocity=36, F_BM=default_F_BM, **kwargs): + downward_flow_velocity=36, F_BM=default_F_BM, **kwargs): SanUnit.__init__(self, ID, ins, outs, thermo, isdynamic=isdynamic, init_with=init_with) self.thickener_perc = thickener_perc self.TSS_removal_perc = TSS_removal_perc self.surface_overflow_rate = surface_overflow_rate self.depth_clarifier = depth_clarifier - self.upflow_velocity = upflow_velocity + self.downward_flow_velocity = downward_flow_velocity self.F_BM.update(F_BM) self._mixed = WasteStream(f'{ID}_mixed') self._sludge = self.outs[0].copy(f'{ID}_sludge') @@ -1656,7 +1711,7 @@ def _design_pump(self): 'Volume': 'm3', 'Hydraulic Retention Time': 'hr', 'Center feed depth': 'm', - 'Upflow velocity': 'm/hr', + 'Downward flow velocity': 'm/hr', 'Center feed diameter': 'm', 'Volume of concrete wall': 'm3', 'Stainless steel': 'kg', @@ -1725,16 +1780,15 @@ def _design(self): # Typical conventional feed wells are designed for an average downflow velocity # of 10-13 mm/s and maximum velocity of 25-30 mm/s. [4] peak_flow_safety_factor = 2.5 # assumed based on average and maximum velocities - D['Upflow velocity'] = self.upflow_velocity*peak_flow_safety_factor # in m/hr - + D['Downward flow velocity'] = self.downward_flow_velocity*peak_flow_safety_factor # in m/hr - Center_feed_area = (D['Volumetric flow']/24)/D['Upflow velocity'] # in m2 + Center_feed_area = (D['Volumetric flow']/24)/D['Downward flow velocity'] # in m2 D['Center feed diameter'] = np.sqrt(4*Center_feed_area/np.pi) #Sanity check: Diameter of the center feed lies between 15-25% of tank diameter [4] - #The lower limit of this check has been modified to 10% based on advised range of upflow velocity in [4]. + #The lower limit of this check has been modified to 10% based on advised range of down flow velocity in [4]. if D['Center feed diameter'] < 0.10*D['Cylindrical diameter'] or D['Center feed diameter'] > 0.25*D['Cylindrical diameter']: cf_dia = D['Center feed diameter'] tank_dia = D['Cylindrical diameter'] @@ -1752,7 +1806,7 @@ def _design(self): D['Volume of concrete wall'] = volume_cylindercal_wall + volume_conical_wall # in m3 # Amount of metal required for center feed - thickness_metal_wall = 0.5 # in m (!! NEED A RELIABLE SOURCE !!) + thickness_metal_wall = 0.3048 # equal to 1 feet, in m (!! NEED A RELIABLE SOURCE !!) inner_diameter_center_feed = D['Center feed diameter'] outer_diameter_center_feed = inner_diameter_center_feed + 2*thickness_metal_wall volume_center_feed = (3.14*D['Center feed depth']/4)*(outer_diameter_center_feed**2 - inner_diameter_center_feed**2) @@ -1786,7 +1840,7 @@ def _cost(self): # Source: https://www.alibaba.com/product-detail/Peripheral-driving-clarifier-mud-scraper-waste_1600891102019.html?spm=a2700.details.0.0.47ab45a4TP0DLb base_cost_scraper = 2500 base_flow_scraper = 1 # in m3/hr (!!! Need to know whether this is for solids or influent !!!) - clarifier_flow = self._mixed.get_total_flow('m3/hr')/ D['Number of clarifiers'] + clarifier_flow = D['Volumetric flow']/24 C['Scraper'] = D['Number of clarifiers']*base_cost_scraper*(clarifier_flow/base_flow_scraper)**0.6 base_power_scraper = 2.75 # in kW scraper_power = D['Number of clarifiers']*base_power_scraper*(clarifier_flow/base_flow_scraper)**0.6 From ae1f91373042788fb30a0d36d88f65f44fde1562 Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Mon, 9 Oct 2023 08:37:32 -0500 Subject: [PATCH 198/424] Updated SC design Updates in design of SC: Area of clarifier either given by the user, or calculated from user defined influent TSS, influent flow, and SLR. A SLR is calculated based on simulation conditions, and the deviation is checked with design_SLR given by user. --- qsdsan/sanunits/_clarifier.py | 62 ++++++++++++++++++++++++----------- 1 file changed, 43 insertions(+), 19 deletions(-) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index 75cc14fa..5c396782 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -101,9 +101,14 @@ class FlatBottomCircularClarifier(SanUnit): fns : float, optional Non-settleable fraction of the suspended solids, dimensionless. Must be within [0, 1]. The default is 2.28e-3. + downward_flow_velocity : float, optional Speed on the basis of which center feed diameter is designed [m/hr]. The default is 42 m/hr (0.7 m/min). [2] - solids_loading_rate : float, optional + design_influent_TSS : float, optional + The design TSS concentration [mg/L] in the influent going to the secondary clarifier. + design_influent_flow : float, optional + The design influent tptal volumetric flow [m3/hr] going to the secondary clarifier. + design_solids_loading_rate : float, optional Rate of total suspended solids entering the secondary clarifier (kg/(m2*hr)). The default is 5 kg/(m2*hr) [3, 4] @@ -134,15 +139,23 @@ def __init__(self, ID='', ins=None, outs=(), thermo=None, surface_area=1500, height=4, N_layer=10, feed_layer=4, X_threshold=3000, v_max=474, v_max_practical=250, rh=5.76e-4, rp=2.86e-3, fns=2.28e-3, F_BM_default=default_F_BM, isdynamic=True, - downward_flow_velocity=42, solids_loading_rate = 6, **kwargs): + downward_flow_velocity=42, design_influent_TSS = None, design_influent_flow = None, + design_solids_loading_rate = 6, **kwargs): SanUnit.__init__(self, ID, ins, outs, thermo, init_with, isdynamic=isdynamic, F_BM_default=1) self._h = height self._Qras = underflow self._Qwas = wastage self._sludge = WasteStream() - self._V = surface_area * height - self._A = surface_area + + if surface_area != None: + self._A = surface_area + elif design_influent_TSS != None and design_influent_flow != None: + self._A = (design_influent_TSS*design_influent_flow)/(design_solids_loading_rate*1000) # 1000 in denominator for unit conversion + else: + RuntimeError('Either surface_area, or design_influent_TSS and design_influent_flow expected from user') + + self._V = self._A * height self._hj = height/N_layer self._N_layer = N_layer self.feed_layer = feed_layer @@ -158,7 +171,9 @@ def __init__(self, ID='', ins=None, outs=(), thermo=None, self._dX_comp = self._X_comp.copy() self._downward_flow_velocity = downward_flow_velocity # in m/hr (converted from 12 mm/sec) - self._slr = solids_loading_rate + self._design_tss = design_influent_TSS + self._design_flow = design_influent_flow + self._slr = design_solids_loading_rate self._mixed = WasteStream(f'{ID}_mixed') header = self._state_header @@ -513,10 +528,10 @@ def dy_dt(t, QC_ins, QC, dQC_ins): 'Number of clarifiers': 'Unitless', 'Volumetric flow': 'm3/day', 'Clarifier depth': 'm', - 'Solids loading rate': 'kg/m2/hr', 'Surface area': 'm2', 'Clarifier diameter': 'm', - 'Volume': 'm3', + 'Clarifier volume': 'm3', + 'Design solids loading rate': 'kg/m2/hr', 'Surface overflow rate': 'm3/day/m2', 'Hydraulic Retention Time': 'hr', 'Center feed depth': 'm', @@ -607,28 +622,37 @@ def _design(self): # Sidewater depth of a cylindrical clarifier lies between 4-5 m (MOP 8) D['Clarifier depth'] = self._h # in m + # Area of clarifier + # D['Surface area'] = solids_clarifier/D['Solids loading rate'] #m2 + D['Surface area'] = self._A/D['Number of clarifiers'] + D['Clarifier diameter'] = np.sqrt(4*D['Surface area']/np.pi) # in m + D['Clarifier volume'] = D['Surface area']*D['Clarifier depth'] # in m3 + + # Checks on SLR,, SOR, and HRT + + D['Design solids loading rate'] = self._slr # kg/(m2*hr) + total_solids = mixed.get_TSS()*mixed.get_total_flow('m3/hr')/1000 # in kg/hr (mg/l * m3/hr) solids_clarifier = total_solids/D['Number of clarifiers'] # in kg/hr - D['Solids loading rate'] = self._slr # kg/(m2*hr) + simulated_slr = solids_clarifier/D['Surface area'] # in kg/(m2*hr) - # Check on SOR [3, 4, 5] - if D['Solids loading rate'] > 14: - slr = D['Solids loading rate'] - warn(f'Solids loading rate = {slr} is above recommended level of 14 kg/hr/m2') - - # Area of clarifier decided based on solids loading rate - D['Surface area'] = solids_clarifier/D['Solids loading rate'] #m2 - D['Clarifier diameter'] = np.sqrt(4*D['Surface area']/np.pi) # in m - D['Volume'] = D['Surface area']*D['Clarifier depth'] # in m3 + # Consult Joy on the margin or error + if simulated_slr < 0.8*D['Design solids loading rate'] or simulated_slr > 1.2*D['Design solids loading rate']: + design_slr = D['Design solids loading rate'] + warn(f'Solids loading rate = {simulated_slr} is not within 20% of the recommended design level of {design_slr} kg/hr/m2') + + # Check on SLR [3, 4, 5] + if simulated_slr > 14: + warn(f'Solids loading rate = {simulated_slr} is above recommended level of 14 kg/hr/m2') # Check on SOR [3, 4, 5] - D['Surface overflow rate'] = D['Volumetric flow']/D['Surface area'] + D['Surface overflow rate'] = D['Volumetric flow']/D['Surface area'] # in m3/m2/hr if D['Surface overflow rate'] > 49: sor = D['Surface overflow rate'] warn(f'Surface overflow rate = {sor} is above recommended level of 49 m3/day/m2') # HRT - D['Hydraulic Retention Time'] = D['Volume']*24/D['Volumetric flow'] # in hr + D['Hydraulic Retention Time'] = D['Clarifier volume']*24/D['Volumetric flow'] # in hr # Clarifiers can be center feed or peripheral feed. The design here is for the more commonly deployed center feed. # Depth of the center feed lies between 30-75% of sidewater depth. [2] From ad1af109048ab5d2c2d72e87101b1e6fed36b39a Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Thu, 12 Oct 2023 12:10:26 -0500 Subject: [PATCH 199/424] Added concrete slab to clarifiers Include the design and cost of concrete slab in both clarifiers. In secondary clarifier, the slab forms the bottom of the tank. In primary clarifier, the slab forms the conical portion of the tank. --- qsdsan/sanunits/_clarifier.py | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index 5c396782..458c51c9 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -129,8 +129,9 @@ class FlatBottomCircularClarifier(SanUnit): _N_outs = 3 # Costs - wall_concrete_unit_cost = 650 / 0.765 # $/m3, 0.765 is to convert from $/yd3 - stainless_steel_unit_cost=1.8 # $/kg (Taken from Joy's METAB code) https://www.alibaba.com/product-detail/brushed-stainless-steel-plate-304l-stainless_1600391656401.html?spm=a2700.details.0.0.230e67e6IKwwFd + wall_concrete_unit_cost = 1081.73 # $/m3 (Hydromantis. CapdetWorks 4.0. https://www.hydromantis.com/CapdetWorks.html) + slab_concrete_unit_cost = 582.48 # $/m3 (Hydromantis. CapdetWorks 4.0. https://www.hydromantis.com/CapdetWorks.html) + stainless_steel_unit_cost=1.8 # Alibaba. Brushed Stainless Steel Plate 304. https://www.alibaba.com/product-detail/brushed-stainless-steel-plate-304l-stainless_1600391656401.html?spm=a2700.details.0.0.230e67e6IKwwFd pumps = ('ras', 'was',) @@ -670,12 +671,17 @@ def _design(self): # Amount of concrete required D_tank = D['Clarifier depth']*39.37 # m to inches - # Thickness of the wall concrete, [m]. Default to be minimum of 1 ft with 1 in added for every ft of depth over 12 ft. + # Thickness of the wall concrete, [m]. Default to be minimum of 1 ft with 1 in added for every ft of depth over 12 ft. (Brian's code) thickness_concrete_wall = (1 + max(D_tank-12, 0)/12)*0.3048 # from feet to m inner_diameter = D['Clarifier diameter'] outer_diameter = inner_diameter + 2*thickness_concrete_wall D['Volume of concrete wall'] = (np.pi*D['Clarifier depth']/4)*(outer_diameter**2 - inner_diameter**2) + # Concrete slab thickness, [ft], default to be 2 in thicker than the wall thickness. (Brian's code) + thickness_concrete_slab = thickness_concrete_wall + (2/12)*0.3048 # from inch to m + # From Brian's code + D['Volume of concrete slab'] = (thickness_concrete_slab + thickness_concrete_wall)*D['Surface area'] + # Amount of metal required for center feed thickness_metal_wall = 0.3048 # equal to 1 feet, in m (!! NEED A RELIABLE SOURCE !!) inner_diameter_center_feed = D['Center feed diameter'] @@ -699,6 +705,9 @@ def _cost(self): # Construction of concrete and stainless steel walls C['Wall concrete'] = D['Number of clarifiers']*D['Volume of concrete wall']*self.wall_concrete_unit_cost + + C['Slab concrete'] = D['Number of clarifiers']*D['Volume of concrete slab']*self.slab_concrete_unit_cost + C['Wall stainless steel'] = D['Number of clarifiers']*D['Stainless steel']*self.stainless_steel_unit_cost # Cost of equipment @@ -1427,8 +1436,9 @@ class PrimaryClarifier(SanUnit): _outs_size_is_fixed = False # Costs - wall_concrete_unit_cost = 650 / 0.765 # $/m3, 0.765 is to convert from $/yd3 - stainless_steel_unit_cost=1.8 # $/kg (Taken from Joy's METAB code) https://www.alibaba.com/product-detail/brushed-stainless-steel-plate-304l-stainless_1600391656401.html?spm=a2700.details.0.0.230e67e6IKwwFd + wall_concrete_unit_cost = 1081.73 # $/m3 (Hydromantis. CapdetWorks 4.0. https://www.hydromantis.com/CapdetWorks.html) + slab_concrete_unit_cost = 582.48 # $/m3 (Hydromantis. CapdetWorks 4.0. https://www.hydromantis.com/CapdetWorks.html) + stainless_steel_unit_cost=1.8 # Alibaba. Brushed Stainless Steel Plate 304. https://www.alibaba.com/product-detail/brushed-stainless-steel-plate-304l-stainless_1600391656401.html?spm=a2700.details.0.0.230e67e6IKwwFd pumps = ('sludge',) @@ -1738,6 +1748,7 @@ def _design_pump(self): 'Downward flow velocity': 'm/hr', 'Center feed diameter': 'm', 'Volume of concrete wall': 'm3', + 'Volume of concrete slab': 'm3', 'Stainless steel': 'kg', 'Pump pipe stainless steel' : 'kg', 'Pump stainless steel': 'kg', @@ -1806,7 +1817,6 @@ def _design(self): peak_flow_safety_factor = 2.5 # assumed based on average and maximum velocities D['Downward flow velocity'] = self.downward_flow_velocity*peak_flow_safety_factor # in m/hr - Center_feed_area = (D['Volumetric flow']/24)/D['Downward flow velocity'] # in m2 D['Center feed diameter'] = np.sqrt(4*Center_feed_area/np.pi) @@ -1825,9 +1835,13 @@ def _design(self): inner_diameter = D['Cylindrical diameter'] outer_diameter = inner_diameter + 2*thickness_concrete_wall volume_cylindercal_wall = (np.pi*D['Cylindrical depth']/4)*(outer_diameter**2 - inner_diameter**2) - volume_conical_wall = (np.pi/3)*(D['Conical depth']/4)*(outer_diameter**2 - inner_diameter**2) + D['Volume of concrete wall'] = volume_cylindercal_wall # in m3 - D['Volume of concrete wall'] = volume_cylindercal_wall + volume_conical_wall # in m3 + # Concrete slab thickness, [ft], default to be 2 in thicker than the wall thickness. (Brian's code) + thickness_concrete_slab = thickness_concrete_wall + (2/12)*0.3048 # from inch to m + outer_diameter_cone = inner_diameter + 2*(thickness_concrete_wall + thickness_concrete_slab) + volume_conical_wall = (np.pi/(3*4))*(((D['Conical depth'] + thickness_concrete_wall + thickness_concrete_slab)*(outer_diameter_cone**2)) - (D['Conical depth']*(inner_diameter)**2)) + D['Volume of concrete slab'] = volume_conical_wall # Amount of metal required for center feed thickness_metal_wall = 0.3048 # equal to 1 feet, in m (!! NEED A RELIABLE SOURCE !!) @@ -1854,6 +1868,9 @@ def _cost(self): # Construction of concrete and stainless steel walls C['Wall concrete'] = D['Number of clarifiers']*D['Volume of concrete wall']*self.wall_concrete_unit_cost + + C['Slab concrete'] = D['Number of clarifiers']*D['Volume of concrete slab']*self.slab_concrete_unit_cost + C['Wall stainless steel'] = D['Number of clarifiers']*D['Stainless steel']*self.stainless_steel_unit_cost # Cost of equipment From 2126e5835be08c7c33331c04828d2746713bacb9 Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Thu, 12 Oct 2023 12:13:08 -0500 Subject: [PATCH 200/424] Updated thickener (MOP-8) Updated design of gravity thickener (based on MOP 8) Have made several changes accordingly. Primarily, the diameter of the tank is limited to 22 m, and a design SLR is used to determine the surface area. The surface area governs the diameter. Changes in incinerator are commented for now. --- qsdsan/sanunits/_sludge_treatment.py | 223 ++++++++++++++++++--------- 1 file changed, 149 insertions(+), 74 deletions(-) diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py index e7fabbe0..e28af332 100644 --- a/qsdsan/sanunits/_sludge_treatment.py +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -16,6 +16,7 @@ from .. import SanUnit, WasteStream import numpy as np from ..sanunits import WWTpump +from warnings import warn from ..sanunits._pumping import ( default_F_BM as default_WWTpump_F_BM, default_equipment_lifetime as default_WWTpump_equipment_lifetime, @@ -48,13 +49,17 @@ class Thickener(SanUnit): TSS_removal_perc : float The percentage of suspended solids removed in the thickener.[1] solids_loading_rate : float - Solid loading rate in the thickener in [(kg/day)/m2]. [2] - Typical SLR value for thickener lies between 9.6-24 (lb/day)/ft2. - Here default value of 75 (kg/day)/m2 [15.36 (lb/day)/ft2] is used. - h_cylindrical = float - Height of cylinder forming the thickener.[2] - upflow_velocity : float, optional - Speed with which influent enters the center feed of the clarifier [m/hr]. The default is 43.2. + Solid loading rate in the thickener in [(kg/hr)/m2]. Default is 4 kg/(m2*hr) [2] + If the thickener is treating: + Only Primary clarifier sludge, then expected range: 4-6 kg/(m2*hr) + Only WAS (treated with air or oxygen): 0.5-1.5 kg/(m2*hr) + Primary clarfier sludge + WAS: 1.5-3.5 kg/(m2/hr) + h_thickener = float + Side water depth of the thickener. Typically lies between 3-4 m. [2] + Height of tank forming the thickener. + downward_flow_velocity : float, optional + Speed on the basis of which center feed diameter is designed [m/hr]. [3] + The default is 36 m/hr. (10 mm/sec) F_BM : dict Equipment bare modules. @@ -125,7 +130,10 @@ class Thickener(SanUnit): ---------- .. [1] Gernaey, Krist V., Ulf Jeppsson, Peter A. Vanrolleghem, and John B. Copp. Benchmarking of control strategies for wastewater treatment plants. IWA publishing, 2014. - [2] Metcalf, Leonard, Harrison P. Eddy, and Georg Tchobanoglous. Wastewater + .. [2] Chapter-21: Solids Thicknening (Table 21.3). WEF Manual of Practice No. 8. + 6th Edition. Virginia: McGraw-Hill, 2018. + .. [3] Introduction to Wastewater Clarifier Design by Nikolay Voutchkov, PE, BCEE. + .. [4] Metcalf, Leonard, Harrison P. Eddy, and Georg Tchobanoglous. Wastewater engineering: treatment, disposal, and reuse. Vol. 4. New York: McGraw-Hill, 1991. """ @@ -135,23 +143,23 @@ class Thickener(SanUnit): _outs_size_is_fixed = False # Costs - wall_concrete_unit_cost = 650 / 0.765 # $/m3, 0.765 is to convert from $/yd3 - stainless_steel_unit_cost=1.8 # $/kg (Taken from Joy's METAB code) https://www.alibaba.com/product-detail/brushed-stainless-steel-plate-304l-stainless_1600391656401.html?spm=a2700.details.0.0.230e67e6IKwwFd + wall_concrete_unit_cost = 1081.73 # $/m3 (Hydromantis. CapdetWorks 4.0. https://www.hydromantis.com/CapdetWorks.html) + slab_concrete_unit_cost = 582.48 # $/m3 (Hydromantis. CapdetWorks 4.0. https://www.hydromantis.com/CapdetWorks.html) + stainless_steel_unit_cost=1.8 # Alibaba. Brushed Stainless Steel Plate 304. https://www.alibaba.com/product-detail/brushed-stainless-steel-plate-304l-stainless_1600391656401.html?spm=a2700.details.0.0.230e67e6IKwwFd pumps = ('sludge',) def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, init_with='WasteStream', F_BM_default=default_F_BM, thickener_perc=7, - TSS_removal_perc=98, solids_loading_rate = 75, h_cylindrical=2, - upflow_velocity=43.2, design_flow = 113, F_BM=default_F_BM, **kwargs): + TSS_removal_perc=98, solids_loading_rate =4, h_thickener=4, + upflow_velocity= 36, F_BM=default_F_BM, **kwargs): SanUnit.__init__(self, ID, ins, outs, thermo, isdynamic=isdynamic, init_with=init_with) self.thickener_perc = thickener_perc self.TSS_removal_perc = TSS_removal_perc self.solids_loading_rate = solids_loading_rate - self.h_cylindrical = h_cylindrical + self.h_thickener = h_thickener self.upflow_velocity = upflow_velocity - self.design_flow = design_flow # 0.60 MGD = 113 m3/hr self.F_BM.update(F_BM) self._mixed = WasteStream(f'{ID}_mixed') self._sludge = self.outs[0].copy(f'{ID}_sludge') @@ -385,37 +393,16 @@ def yt(t, QC_ins, dQC_ins): _update_state() _update_dstate() self._AE = yt - - _units = { - 'slr': 'kg/m2', - 'Daily mass of solids handled': 'kg', - 'Surface area': 'm2', - - 'Cylindrical diameter': 'm', - 'Cylindrical depth': 'm', - 'Cylindrical volume': 'm3', - - 'Conical radius': 'm', - 'Conical depth': 'm', - 'Conical volume': 'm3', - - 'Volume': 'm3', - 'Center feed depth': 'm', - 'Upflow velocity': 'm/hr', - 'Center feed diameter': 'm', - 'Volume of concrete wall': 'm3', - 'Stainless steel': 'kg', - 'Pump pipe stainless steel' : 'kg', - 'Pump stainless steel': 'kg' - } - + def _design_pump(self): ID, pumps = self.ID, self.pumps self._sludge.copy_like(self.outs[0]) sludge = self._sludge + ins_dct = { 'sludge': sludge, } + type_dct = dict.fromkeys(pumps, 'sludge') inputs_dct = dict.fromkeys(pumps, (1,)) @@ -450,52 +437,115 @@ def _design_pump(self): pump_ss += p_design['Pump stainless steel'] return pipe_ss, pump_ss + _units = { + 'Design solids loading rate': 'kg/m2/hr', + 'Total mass of solids handled': 'kg', + 'Surface area': 'm2', + 'Thickener diameter': 'm', + 'Number of thickeners' : 'Unitless', + 'Thickener depth': 'm', + 'Conical depth': 'm', + 'Cylindrical depth': 'm', + 'Cylindrical volume': 'm3', + 'Conical volume': 'm3', + 'Thickener volume': 'm3', + + 'Center feed depth': 'm', + 'Downward flow velocity': 'm/hr', + 'Volumetric flow': 'm3/hr', + 'Center feed diameter': 'm', + 'Thickener depth': 'm', + 'Volume of concrete wall': 'm3', + 'Volume of concrete slab': 'm3', + 'Stainless steel': 'kg', + 'Pump pipe stainless steel' : 'kg', + 'Pump stainless steel': 'kg', + 'Number of pumps': 'Unitless' + } + def _design(self): - D = self.design_results - D['Number of thickeners'] = np.ceil(self._mixed.get_total_flow('m3/hr')/self.design_flow) - D['slr'] = self.solids_loading_rate # in (kg/day)/m2 + + self._mixed.mix_from(self.ins) mixed = self._mixed - D['Daily mass of solids handled'] = ((mixed.get_TSS()/1000)*mixed.get_total_flow('m3/hr')*24)/D['Number of thickeners'] # (mg/L)*[1/1000(kg*L)/(mg*m3)](m3/hr)*(24hr/day) = (kg/day) - D['Surface area'] = D['Daily mass of solids handled']/D['slr'] # in m2 - - # design['Hydraulic_Loading'] = (self.ins[0].F_vol*24)/design['Area'] #in m3/(m2*day) - D['Cylindrical diameter'] = np.sqrt(4*D['Surface area']/np.pi) #in m - D['Cylindrical depth'] = self.h_cylindrical #in m - D['Cylindrical volume'] = np.pi*np.square(D['Cylindrical diameter']/2)*D['Cylindrical depth'] #in m3 - - D['Conical radius'] = D['Cylindrical diameter']/2 - # The slope of the bottom conical floor lies between 1:10 to 1:12 - D['Conical depth'] = D['Conical radius']/10 - D['Conical volume'] = (3.14/3)*(D['Conical radius']**2)*D['Conical depth'] - - D['Volume'] = D['Cylindrical volume'] + D['Conical volume'] + D = self.design_results - # The design here is for center feed thickener. - - # Depth of the center feed lies between 30-75% of sidewater depth + # D['Number of thickeners'] = np.ceil(self._mixed.get_total_flow('m3/hr')/self.design_flow) + D['Design solids loading rate'] = self.solids_loading_rate # in (kg/hr)/m2 + D['Total mass of solids handled'] = (mixed.get_TSS()/1000)*mixed.get_total_flow('m3/hr') # (mg/L)*[1/1000(kg*L)/(mg*m3)](m3/hr) = (kg/hr) + + # Common gravity thickener configurations have tanks with diameter between 21-24m (MOP 8) + diameter_thickener = 24 + number_of_thickeners = 0 + while diameter_thickener >= 22: + number_of_thickeners += 1 + total_surface_area = D['Total mass of solids handled']/D['Design solids loading rate'] #m2 + surface_area_thickener = total_surface_area/number_of_thickeners + diameter_thickener = np.sqrt(4*surface_area_thickener/np.pi) + + D['Surface area'] = surface_area_thickener #in m2 + D['Thickener diameter'] = diameter_thickener #in m + D['Number of thickeners'] = number_of_thickeners + + # Common gravity thickener configurations have sidewater depth between 3-4m (MOP 8) + D['Thickener depth'] = self.h_thickener #in m + # The thickener tank floor generally has slope between 2:12 and 3:12 (MOP 8) + D['Conical depth'] = (2/12)*(D['Thickener diameter']/2) + D['Cylindrical depth'] = D['Thickener depth'] - D['Conical depth'] + + # Checks on depth + if D['Cylindrical depth'] < 0: + cyl_depth = D['Cylindrical depth'] + RuntimeError(f'Cylindrical depth (= {cyl_depth} ) is negative') + + if D['Cylindrical depth'] < D['Conical depth']: + cyl_depth = D['Cylindrical depth'] + con_depth = D['Conical depth'] + RuntimeError(f'Cylindrical depth (= {cyl_depth} ) is lower than Conical depth (= {con_depth})') + + D['Cylindrical volume'] = np.pi*np.square(D['Thickener diameter']/2)*D['Cylindrical depth'] #in m3 + D['Conical volume'] = (np.pi/3)*(D['Thickener diameter']/2)**2*D['Conical depth'] + D['Thickener volume'] = D['Cylindrical volume'] + D['Conical volume'] + + #Check on SOR is pending + + # The design here is for center feed of thickener. + # Depth of the center feed lies between 30-75% of sidewater depth. [2] D['Center feed depth'] = 0.5*D['Cylindrical depth'] # Typical conventional feed wells are designed for an average downflow velocity - # of 10-13 mm/s and maximum velocity of 25-30 mm/s + # of 10-13 mm/s and maximum velocity of 25-30 mm/s. [4] peak_flow_safety_factor = 2.5 # assumed based on average and maximum velocities - D['Upflow velocity'] = self.upflow_velocity*peak_flow_safety_factor # in m/hr - Center_feed_area = mixed.get_total_flow('m3/hr')/D['Upflow velocity'] # in m2 - D['Center feed diameter'] = ((4*Center_feed_area)/3.14)**(1/2) # Sanity check: Diameter of the center feed lies between 15-25% of tank diameter + D['Downward flow velocity'] = self.downward_flow_velocity*peak_flow_safety_factor # in m/hr + + D['Volumetric flow'] = mixed.get_total_flow('m3/hr')/D['Number of clarifiers'] # m3/hr + Center_feed_area = D['Volumetric flow']/D['Downward flow velocity'] # in m2 + D['Center feed diameter'] = np.sqrt(4*Center_feed_area/np.pi) # in m + + #Diameter of the center feed does not exceed 40% of tank diameter [2] + if D['Center feed diameter'] > 0.40*D['Thickener diameter']: + cf_dia = D['Center feed diameter'] + tank_dia = D['Thickener diameter'] + warn(f'Diameter of the center feed exceeds 40% of tank diameter. It is {cf_dia*100/tank_dia}% of tank diameter') # Amount of concrete required - D_tank = D['Cylindrical depth']*39.37 # m to inches - # Thickness of the wall concrete, [m]. Default to be minimum of 1 ft with 1 in added for every ft of depth over 12 ft. + D_tank = D['Thickener depth']*39.37 # m to inches + # Thickness of the wall concrete [m]. Default to be minimum of 1 feet with 1 inch added for every feet of depth over 12 feet. thickness_concrete_wall = (1 + max(D_tank-12, 0)/12)*0.3048 # from feet to m - inner_diameter = D['Cylindrical diameter'] + inner_diameter = D['Thickener diameter'] outer_diameter = inner_diameter + 2*thickness_concrete_wall - volume_cylindercal_wall = (3.14*D['Cylindrical depth']/4)*(outer_diameter**2 - inner_diameter**2) - volume_conical_wall = (3.14/3)*(D['Conical depth']/4)*(outer_diameter**2 - inner_diameter**2) + volume_cylindercal_wall = (np.pi*D['Cylindrical depth']/4)*(outer_diameter**2 - inner_diameter**2) + D['Volume of concrete wall'] = volume_cylindercal_wall # in m3 + + # Concrete slab thickness, [ft], default to be 2 in thicker than the wall thickness. (Brian's code) + thickness_concrete_slab = thickness_concrete_wall + (2/12)*0.3048 # from inch to m + outer_diameter_cone = inner_diameter + 2*(thickness_concrete_wall + thickness_concrete_slab) + volume_conical_wall = (np.pi/(3*4))*(((D['Conical depth'] + thickness_concrete_wall + thickness_concrete_slab)*(outer_diameter_cone**2)) - (D['Conical depth']*(inner_diameter)**2)) + D['Volume of concrete slab'] = volume_conical_wall - D['Volume of concrete wall'] = volume_cylindercal_wall + volume_conical_wall # in m3 # Amount of metal required for center feed - thickness_metal_wall = 0.5 # in m (!! NEED A RELIABLE SOURCE !!) + thickness_metal_wall = 0.3048 # equal to 1 feet, in m (!! NEED A RELIABLE SOURCE !!) inner_diameter_center_feed = D['Center feed diameter'] outer_diameter_center_feed = inner_diameter_center_feed + 2*thickness_metal_wall - volume_center_feed = (3.14*D['Center feed depth']/4)*(outer_diameter_center_feed**2 - inner_diameter_center_feed **2) + volume_center_feed = (3.14*D['Center feed depth']/4)*(outer_diameter_center_feed**2 - inner_diameter_center_feed**2) density_ss = 7930 # kg/m3, 18/8 Chromium D['Stainless steel'] = volume_center_feed*density_ss # in kg @@ -504,29 +554,30 @@ def _design(self): D['Pump pipe stainless steel'] = pipe D['Pump stainless steel'] = pumps - #For thickeners + #For thickener D['Number of pumps'] = D['Number of thickeners'] def _cost(self): self._mixed.mix_from(self.ins) + mixed = self._mixed D = self.design_results C = self.baseline_purchase_costs # Construction of concrete and stainless steel walls C['Wall concrete'] = D['Number of thickeners']*D['Volume of concrete wall']*self.wall_concrete_unit_cost + C['Slab concrete'] = D['Number of thickeners']*D['Volume of concrete slab']*self.slab_concrete_unit_cost C['Wall stainless steel'] = D['Number of thickeners']*D['Stainless steel']*self.stainless_steel_unit_cost # Cost of equipment - # Source of scaling exponents: Process Design and Economics for Biochemical Conversion of Lignocellulosic Biomass to Ethanol by NREL. # Scraper # Source: https://www.alibaba.com/product-detail/Peripheral-driving-clarifier-mud-scraper-waste_1600891102019.html?spm=a2700.details.0.0.47ab45a4TP0DLb base_cost_scraper = 2500 base_flow_scraper = 1 # in m3/hr (!!! Need to know whether this is for solids or influent !!!) - thickener_flow = self._mixed.get_total_flow('m3/hr')/D['Number of thickeners'] + thickener_flow = mixed.get_total_flow('m3/hr')/D['Number of thickeners'] C['Scraper'] = D['Number of thickeners']*base_cost_scraper*(thickener_flow/base_flow_scraper)**0.6 base_power_scraper = 2.75 # in kW scraper_power = D['Number of thickeners']*base_power_scraper*(thickener_flow/base_flow_scraper)**0.6 @@ -1026,6 +1077,27 @@ def _init_state(self): self._cached_state = self._state.copy() self._cached_t = 0 + # def _cost(self): + + # C = self.baseline_purchase_costs + + # sludge, air, fuel = self.ins + + # solids_load_treated = sludge.get_total_flow('m3/hr')*sludge.get_TSS('mg/L')/1000 # in kg/hr + + # C = self.baseline_purchase_costs + + # # Based on regression equations obtained from CapdetWorks + # C['Construction and equipment costs'] = 119629*(solids_load_treated)**0.9282 + # C['Installed incinerator costs'] = 114834*(solids_load_treated)**0.9284 + # C['Slab concrete costs'] = 782.28*(solids_load_treated)**0.9111 + # C['Building costs'] = 4429.8*(solids_load_treated)**0.911 + + # # Based on regression equations obtained from CapdetWorks + # add_OPEX = self.add_OPEX + # add_OPEX['Material and supply costs'] = 0.0614*(solids_load_treated)**0.9282 + # add_OPEX['Energy costs'] = 1.3079*(solids_load_treated)**0.9901 + def _update_state(self): cmps = self.components for ws in self.outs: @@ -1123,4 +1195,7 @@ def yt(t, QC_ins, dQC_ins): self._cached_t = t _update_state() _update_dstate() - self._AE = yt \ No newline at end of file + self._AE = yt + + + \ No newline at end of file From c3cc0be13448d2fcc81b56bb5d9a74a432c03544 Mon Sep 17 00:00:00 2001 From: Yalin Date: Sat, 14 Oct 2023 10:28:23 -0400 Subject: [PATCH 201/424] remove not useful file --- qsdsan/sanunits/ipython.html | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 qsdsan/sanunits/ipython.html diff --git a/qsdsan/sanunits/ipython.html b/qsdsan/sanunits/ipython.html deleted file mode 100644 index d7f4813d..00000000 --- a/qsdsan/sanunits/ipython.html +++ /dev/null @@ -1,16 +0,0 @@ - - - - -

Python 3.9.12 (main, Apr 4 2022, 05:22:27) [MSC v.1916 64 bit (AMD64)]

-

Type "copyright", "credits" or "license" for more information.

-


-

IPython 8.2.0 -- An enhanced Interactive Python.

-


Restarting kernel...

- - -
-


-

In [1]:

\ No newline at end of file From cb8a7463f6a188f0478dfdef6683ef206121e78c Mon Sep 17 00:00:00 2001 From: Yalin Date: Sat, 14 Oct 2023 10:28:48 -0400 Subject: [PATCH 202/424] fix bugs --- qsdsan/sanunits/__init__.py | 4 +- qsdsan/sanunits/_clarifier.py | 64 ++++++++++++++-------------- qsdsan/sanunits/_sludge_treatment.py | 6 +-- 3 files changed, 37 insertions(+), 37 deletions(-) diff --git a/qsdsan/sanunits/__init__.py b/qsdsan/sanunits/__init__.py index baf71a90..c8559ecc 100644 --- a/qsdsan/sanunits/__init__.py +++ b/qsdsan/sanunits/__init__.py @@ -58,7 +58,7 @@ # Units that rely on other units from ._activated_sludge_process import * -from ._anaerobic_reactors import * +from ._anaerobic_reactor import * from ._clarifier import * from ._distillation import * from ._flash import * @@ -100,7 +100,7 @@ _internal_circulation_rx, _junction, _lagoon, - _membrane_bioreactors, + _membrane_bioreactor, _membrane_distillation, _membrane_gas_extraction, _non_reactive, diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index fd42ee61..bab06d57 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -880,7 +880,7 @@ class PrimaryClarifierBSM2(SanUnit): Parameters ---------- ID : str - ID for the clarifier. The default is ''. + ID for the clarifier. ins : class:`WasteStream` Influent to the clarifier. Expected number of influent is 3. outs : class:`WasteStream` @@ -906,14 +906,14 @@ class PrimaryClarifierBSM2(SanUnit): >>> cmps_test = cmps.subgroup(['S_F', 'S_NH4', 'X_OHO', 'H2O']) >>> set_thermo(cmps_test) >>> ws = WasteStream('ws', S_F = 10, S_NH4 = 20, X_OHO = 15, H2O=1000) - >>> from qsdsan.sanunits import PrimaryClarifier - >>> PC = PrimaryClarifier(ID='PC', ins= (ws,), outs=('eff', 'sludge')) + >>> from qsdsan.sanunits import PrimaryClarifierBSM2 + >>> PC = PrimaryClarifierBSM2(ID='PC', ins= (ws,), outs=('eff', 'sludge')) >>> PC.simulate() >>> uf, of = PC.outs >>> uf.imass['X_OHO']/ws.imass['X_OHO'] # doctest: +ELLIPSIS 0.280... >>> PC.show() - PrimaryClarifier: PC + PrimaryClarifierBSM2: PC ins... [0] ws phase: 'l', T: 298.15 K, P: 101325 Pa @@ -922,49 +922,49 @@ class PrimaryClarifierBSM2(SanUnit): X_OHO 1.5e+04 H2O 1e+06 WasteStream-specific properties: - pH : 7.0 - COD : 23873.0 mg/L - BOD : 14963.2 mg/L - TC : 8298.3 mg/L - TOC : 8298.3 mg/L - TN : 20363.2 mg/L - TP : 367.6 mg/L - TK : 68.3 mg/L + pH : 7.0 + COD : 23873.0 mg/L + BOD : 14963.2 mg/L + TC : 8298.3 mg/L + TOC : 8298.3 mg/L + TN : 20363.2 mg/L + TP : 367.6 mg/L + TK : 68.3 mg/L outs... - [0] uf + [0] eff phase: 'l', T: 298.15 K, P: 101325 Pa flow (g/hr): S_F 70 S_NH4 140 X_OHO 4.2e+03 H2O 7e+03 WasteStream-specific properties: - pH : 7.0 - COD : 428873.3 mg/L - BOD : 244072.7 mg/L - TC : 156644.5 mg/L - TOC : 156644.5 mg/L - TN : 43073.0 mg/L - TP : 8085.4 mg/L - TK : 2011.4 mg/L - [1] of + pH : 7.0 + COD : 428873.3 mg/L + BOD : 244072.7 mg/L + TC : 156644.5 mg/L + TOC : 156644.5 mg/L + TN : 43073.0 mg/L + TP : 8085.4 mg/L + TK : 2011.4 mg/L + [1] sludge phase: 'l', T: 298.15 K, P: 101325 Pa flow (g/hr): S_F 9.93e+03 S_NH4 1.99e+04 X_OHO 1.08e+04 H2O 9.93e+05 WasteStream-specific properties: - pH : 7.0 - COD : 19982.3 mg/L - BOD : 12762.2 mg/L - TC : 6873.2 mg/L - TOC : 6873.2 mg/L - TN : 20145.0 mg/L - TP : 293.5 mg/L - TK : 49.6 mg/L + pH : 7.0 + COD : 19982.3 mg/L + BOD : 12762.2 mg/L + TC : 6873.2 mg/L + TOC : 6873.2 mg/L + TN : 20145.0 mg/L + TP : 293.5 mg/L + TK : 49.6 mg/L References ---------- - .. [1] Gernaey, Krist V., Ulf Jeppsson, Peter A. Vanrolleghem, and John B. Copp. + [1] Gernaey, Krist V., Ulf Jeppsson, Peter A. Vanrolleghem, and John B. Copp. Benchmarking of control strategies for wastewater treatment plants. IWA publishing, 2014. [2] Metcalf, Leonard, Harrison P. Eddy, and Georg Tchobanoglous. Wastewater engineering: treatment, disposal, and reuse. Vol. 4. New York: McGraw-Hill, 1991. @@ -1734,7 +1734,7 @@ def _design_pump(self): return pipe_ss, pump_ss _units = { - 'Number of clarifiers': 'Unitless', + 'Number of clarifiers': 'ea', 'SOR': 'm3/day/m2', 'Volumetric flow': 'm3/day', 'Surface area': 'm2', diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py index e28af332..8e822b8c 100644 --- a/qsdsan/sanunits/_sludge_treatment.py +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -152,14 +152,14 @@ class Thickener(SanUnit): def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, init_with='WasteStream', F_BM_default=default_F_BM, thickener_perc=7, TSS_removal_perc=98, solids_loading_rate =4, h_thickener=4, - upflow_velocity= 36, F_BM=default_F_BM, **kwargs): + downward_flow_velocity= 36, F_BM=default_F_BM, **kwargs): SanUnit.__init__(self, ID, ins, outs, thermo, isdynamic=isdynamic, init_with=init_with) self.thickener_perc = thickener_perc self.TSS_removal_perc = TSS_removal_perc self.solids_loading_rate = solids_loading_rate self.h_thickener = h_thickener - self.upflow_velocity = upflow_velocity + self.downward_flow_velocity = downward_flow_velocity self.F_BM.update(F_BM) self._mixed = WasteStream(f'{ID}_mixed') self._sludge = self.outs[0].copy(f'{ID}_sludge') @@ -516,7 +516,7 @@ def _design(self): peak_flow_safety_factor = 2.5 # assumed based on average and maximum velocities D['Downward flow velocity'] = self.downward_flow_velocity*peak_flow_safety_factor # in m/hr - D['Volumetric flow'] = mixed.get_total_flow('m3/hr')/D['Number of clarifiers'] # m3/hr + D['Volumetric flow'] = mixed.get_total_flow('m3/hr')/D['Number of thickeners'] # m3/hr Center_feed_area = D['Volumetric flow']/D['Downward flow velocity'] # in m2 D['Center feed diameter'] = np.sqrt(4*Center_feed_area/np.pi) # in m From ba98dc7aa3ab74a1f17fed5ee63519ab84c21c39 Mon Sep 17 00:00:00 2001 From: Yalin Date: Sun, 15 Oct 2023 19:04:47 -0400 Subject: [PATCH 203/424] fix typo --- qsdsan/sanunits/_sludge_treatment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py index 8e822b8c..81321dd9 100644 --- a/qsdsan/sanunits/_sludge_treatment.py +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -25,7 +25,7 @@ __all__ = ('Thickener', 'Centrifuge', 'Incinerator') -# Asign a bare module of 1 to all +# Assign a bare module of 1 to all default_F_BM = { 'Wall concrete': 1., 'Wall stainless steel': 1., From 729da0621141f7cb8eb7913db3b2c78cb9a410d1 Mon Sep 17 00:00:00 2001 From: Yalin Date: Sun, 15 Oct 2023 19:07:53 -0400 Subject: [PATCH 204/424] fix typo --- qsdsan/sanunits/_sludge_treatment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py index 81321dd9..660d0ecd 100644 --- a/qsdsan/sanunits/_sludge_treatment.py +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -53,7 +53,7 @@ class Thickener(SanUnit): If the thickener is treating: Only Primary clarifier sludge, then expected range: 4-6 kg/(m2*hr) Only WAS (treated with air or oxygen): 0.5-1.5 kg/(m2*hr) - Primary clarfier sludge + WAS: 1.5-3.5 kg/(m2/hr) + Primary clarifier sludge + WAS: 1.5-3.5 kg/(m2/hr) h_thickener = float Side water depth of the thickener. Typically lies between 3-4 m. [2] Height of tank forming the thickener. From a6c7a249828a456c677e303300680a2b7365e01c Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Mon, 16 Oct 2023 12:38:10 -0500 Subject: [PATCH 205/424] Added citation for PC_BSM2 Added bare module factor for slab concrete. Also added specific citation for primary clarifier. --- qsdsan/sanunits/_clarifier.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index 458c51c9..3073e097 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -49,10 +49,10 @@ def _settling_flux(X, v_max, v_max_practical, X_min, rh, rp, n0): # v = min(v_max_practical, v_max*(exp(-rh*X_star) - exp(-rp*X_star))) # return X*max(v, 0) - # Asign a bare module of 1 to all default_F_BM = { 'Wall concrete': 1., + 'Slab concrete': 1., 'Wall stainless steel': 1., 'Scraper': 1, 'v notch weir': 1, @@ -889,12 +889,12 @@ class PrimaryClarifierBSM2(SanUnit): f_corr : float Dimensionless correction factor for removal efficiency in the primary clarifier.[1] - cylindrical_depth : float, optional - The depth of the cylindrical portion of clarifier [in m]. - upflow_velocity : float, optional - Speed with which influent enters the center feed of the clarifier [m/hr]. The default is 43.2. - F_BM : dict - Equipment bare modules. + # cylindrical_depth : float, optional + # The depth of the cylindrical portion of clarifier [in m]. + # upflow_velocity : float, optional + # Speed with which influent enters the center feed of the clarifier [m/hr]. The default is 43.2. + # F_BM : dict + # Equipment bare modules. Examples -------- @@ -965,6 +965,8 @@ class PrimaryClarifierBSM2(SanUnit): Benchmarking of control strategies for wastewater treatment plants. IWA publishing, 2014. [2] Metcalf, Leonard, Harrison P. Eddy, and Georg Tchobanoglous. Wastewater engineering: treatment, disposal, and reuse. Vol. 4. New York: McGraw-Hill, 1991. + [3] Otterpohl R. and Freund M. (1992). Dynamic Models for clarifiers of activated sludge + plants with dry and wet weather flows. Water Sci. Technol., 26(5-6), 1391-1400. """ _N_ins = 3 @@ -1319,6 +1321,7 @@ def yt(t, QC_ins, dQC_ins): # Asign a bare module of 1 to all default_F_BM = { 'Wall concrete': 1., + 'Slab concrete': 1., 'Wall stainless steel': 1., 'Scraper': 1, 'v notch weir': 1, From 3b0d097e53f5ea3421dcbbb3339ad800c8c5f380 Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Mon, 16 Oct 2023 12:39:29 -0500 Subject: [PATCH 206/424] Updated thickener based on MOP-8 Updated thickener based on MOP-8. --- qsdsan/sanunits/_sludge_treatment.py | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py index e28af332..85167669 100644 --- a/qsdsan/sanunits/_sludge_treatment.py +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -22,13 +22,16 @@ default_equipment_lifetime as default_WWTpump_equipment_lifetime, ) - __all__ = ('Thickener', 'Centrifuge', 'Incinerator') # Asign a bare module of 1 to all default_F_BM = { 'Wall concrete': 1., + 'Slab concrete': 1., 'Wall stainless steel': 1., + 'Scraper': 1, + 'v notch weir': 1, + 'Pumps': 1 } default_F_BM.update(default_WWTpump_F_BM) @@ -54,6 +57,9 @@ class Thickener(SanUnit): Only Primary clarifier sludge, then expected range: 4-6 kg/(m2*hr) Only WAS (treated with air or oxygen): 0.5-1.5 kg/(m2*hr) Primary clarfier sludge + WAS: 1.5-3.5 kg/(m2/hr) + design_diameter: float + The design diameter of the thickener. Used to determine number of thickner units required given a SLR [m]. + Common gravity thickener configurations have tanks with diameter between 21-24m [2] h_thickener = float Side water depth of the thickener. Typically lies between 3-4 m. [2] Height of tank forming the thickener. @@ -151,15 +157,16 @@ class Thickener(SanUnit): def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, init_with='WasteStream', F_BM_default=default_F_BM, thickener_perc=7, - TSS_removal_perc=98, solids_loading_rate =4, h_thickener=4, - upflow_velocity= 36, F_BM=default_F_BM, **kwargs): + TSS_removal_perc=98, solids_loading_rate =4, design_diameter=22, + h_thickener=4, downward_flow_velocity= 36, F_BM=default_F_BM, **kwargs): SanUnit.__init__(self, ID, ins, outs, thermo, isdynamic=isdynamic, init_with=init_with) self.thickener_perc = thickener_perc self.TSS_removal_perc = TSS_removal_perc self.solids_loading_rate = solids_loading_rate + self.design_diameter = design_diameter self.h_thickener = h_thickener - self.upflow_velocity = upflow_velocity + self.downward_flow_velocity = downward_flow_velocity self.F_BM.update(F_BM) self._mixed = WasteStream(f'{ID}_mixed') self._sludge = self.outs[0].copy(f'{ID}_sludge') @@ -474,9 +481,9 @@ def _design(self): D['Total mass of solids handled'] = (mixed.get_TSS()/1000)*mixed.get_total_flow('m3/hr') # (mg/L)*[1/1000(kg*L)/(mg*m3)](m3/hr) = (kg/hr) # Common gravity thickener configurations have tanks with diameter between 21-24m (MOP 8) - diameter_thickener = 24 + diameter_thickener = self.design_diameter number_of_thickeners = 0 - while diameter_thickener >= 22: + while diameter_thickener >= self.design_diameter: # make this user defined number_of_thickeners += 1 total_surface_area = D['Total mass of solids handled']/D['Design solids loading rate'] #m2 surface_area_thickener = total_surface_area/number_of_thickeners @@ -516,7 +523,7 @@ def _design(self): peak_flow_safety_factor = 2.5 # assumed based on average and maximum velocities D['Downward flow velocity'] = self.downward_flow_velocity*peak_flow_safety_factor # in m/hr - D['Volumetric flow'] = mixed.get_total_flow('m3/hr')/D['Number of clarifiers'] # m3/hr + D['Volumetric flow'] = mixed.get_total_flow('m3/hr')/D['Number of thickeners'] # m3/hr Center_feed_area = D['Volumetric flow']/D['Downward flow velocity'] # in m2 D['Center feed diameter'] = np.sqrt(4*Center_feed_area/np.pi) # in m @@ -643,8 +650,9 @@ class Centrifuge(Thickener): The percentage of Suspended Sludge in the underflow of the dewatering unit.[1] TSS_removal_perc : float The percentage of suspended solids removed in the dewatering unit.[1] + solids_feed_rate : float - Rate of solids processed by one centrifuge in dry tonne per day (dtpd). + Rate at which solids are processed by a centrifuge in dry tonne per day (dtpd). Default value is 70 dtpd. # specific_gravity_sludge: float @@ -775,13 +783,15 @@ def _design(self): self._mixed.mix_from(self.ins) mixed = self._mixed - D = self.design_results + TSS_rmv = self._TSS_rmv solids_feed_rate = 44.66*self.solids_feed_rate # 44.66 is factor to convert tonne/day to kg/hr + # Cake's total solids and TSS are essentially the same (pg. 24-6 [3]) # If TSS_rmv = 98, then total_mass_dry_solids_removed = (0.98)*(influent TSS mass) total_mass_dry_solids_removed = (TSS_rmv/100)*((mixed.get_TSS()*self.ins[0].F_vol)/1000) # in kg/hr + D['Number of centrifuges'] = np.ceil(total_mass_dry_solids_removed/solids_feed_rate) k = 0.00000056 # Based on emprical formula (pg. 24-23 of [3]) g = 9.81 # m/s2 From 8415ce0a1e92529510ec7ede9e80b3735de06c77 Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Mon, 16 Oct 2023 14:14:18 -0500 Subject: [PATCH 207/424] blank comment blank comment --- qsdsan/sanunits/_sludge_treatment.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py index 660d0ecd..8dbf855f 100644 --- a/qsdsan/sanunits/_sludge_treatment.py +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -623,6 +623,7 @@ def _cost(self): self.power_utility.rate += pumping self.power_utility.rate += scraper_power +# blank comment # %% From ed22bcf3fca6d67ca7e8a93be53c7358f2ecc78e Mon Sep 17 00:00:00 2001 From: Yalin Date: Mon, 16 Oct 2023 21:54:53 -0400 Subject: [PATCH 208/424] fix typos --- qsdsan/sanunits/_clarifier.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index bab06d57..e2080be9 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -529,7 +529,7 @@ def dy_dt(t, QC_ins, QC, dQC_ins): self._ODE = dy_dt _units = { - 'Number of clarifiers': 'Unitless', + 'Number of clarifiers': 'ea', 'Volumetric flow': 'm3/day', 'Clarifier depth': 'm', 'Surface area': 'm2', @@ -545,7 +545,7 @@ def dy_dt(t, QC_ins, QC, dQC_ins): 'Stainless steel': 'kg', 'Pump pipe stainless steel' : 'kg', 'Pump stainless steel': 'kg', - 'Number of pumps': 'Unitless' + 'Number of pumps': 'ea' } def _design_pump(self): @@ -1063,8 +1063,8 @@ def _run(self): uf.set_flow(Cs,'kg/hr') def _init_state(self): - # if multiple wastestreams exist then concentration and total inlow - # would be calculated assumping perfect mixing + # if multiple wastestreams exist then concentration and total flow + # would be calculated assuming perfect mixing Qs = self._ins_QC[:,-1] Cs = self._ins_QC[:,:-1] self._state = np.append(Qs @ Cs / Qs.sum(), Qs.sum()) @@ -1283,7 +1283,7 @@ def yt(t, QC_ins, dQC_ins): # base_flow_v_notch_weir = 10 # in m3/hr # C['v notch weir'] = D['Number of clarifiers']*base_cost_v_notch_weir*(clarifier_flow/base_flow_v_notch_weir)**0.6 - # # Pump (construction and maintainance) + # # Pump (construction and maintenance) # pumps = self.pumps # add_OPEX = self.add_OPEX # pump_cost = 0. @@ -1319,7 +1319,7 @@ def yt(t, QC_ins, dQC_ins): # self.power_utility.consumption += scraper_power -# Asign a bare module of 1 to all +# Assign a bare module of 1 to all default_F_BM = { 'Wall concrete': 1., 'Wall stainless steel': 1., @@ -1613,14 +1613,14 @@ def _update_state(self): Qu_factor = self.updated_Qu_factor cmps = self.components - # For sludge, the particulate concentrations are multipled by thickener factor, and + # For sludge, the particulate concentrations are multiplied by thickener factor, and # flowrate is multiplied by Qu_factor. The soluble concentrations remains same. uf, of = self.outs if uf.state is None: uf.state = np.zeros(len(cmps)+1) uf.state[:-1] = self._state[:-1]*cmps.s*1 + self._state[:-1]*cmps.x*thickener_factor uf.state[-1] = self._state[-1]*Qu_factor - # For effluent, the particulate concentrations are multipled by thinning factor, and + # For effluent, the particulate concentrations are multiplied by thinning factor, and # flowrate is multiplied by Qu_factor. The soluble concentrations remains same. if of.state is None: of.state = np.zeros(len(cmps)+1) of.state[:-1] = self._state[:-1]*cmps.s*1 + self._state[:-1]*cmps.x*thinning_factor @@ -1643,14 +1643,14 @@ def _update_dstate(self): Qu_factor = self.updated_Qu_factor cmps = self.components - # For sludge, the particulate concentrations are multipled by thickener factor, and + # For sludge, the particulate concentrations are multiplied by thickener factor, and # flowrate is multiplied by Qu_factor. The soluble concentrations remains same. uf, of = self.outs if uf.dstate is None: uf.dstate = np.zeros(len(cmps)+1) uf.dstate[:-1] = self._dstate[:-1]*cmps.s*1 + self._dstate[:-1]*cmps.x*thickener_factor uf.dstate[-1] = self._dstate[-1]*Qu_factor - # For effluent, the particulate concentrations are multipled by thinning factor, and + # For effluent, the particulate concentrations are multiplied by thinning factor, and # flowrate is multiplied by Qu_factor. The soluble concentrations remains same. if of.dstate is None: of.dstate = np.zeros(len(cmps)+1) of.dstate[:-1] = self._dstate[:-1]*cmps.s*1 + self._dstate[:-1]*cmps.x*thinning_factor @@ -1755,7 +1755,7 @@ def _design_pump(self): 'Stainless steel': 'kg', 'Pump pipe stainless steel' : 'kg', 'Pump stainless steel': 'kg', - 'Number of pumps': 'Unitless' + 'Number of pumps': 'ea' } def _design(self): @@ -1783,7 +1783,7 @@ def _design(self): D['Surface area'] = D['Volumetric flow']/D['SOR'] # in m2 D['Cylindrical diameter'] = np.sqrt(4*D['Surface area']/np.pi) #in m - #Check on cylinderical diameter [2, 3] + #Check on cylindrical diameter [2, 3] if D['Cylindrical diameter'] < 3 or D['Cylindrical diameter'] > 60: Cylindrical_dia = D['Cylindrical diameter'] warn(f'Cylindrical diameter = {Cylindrical_dia} is not between 3 m and 60 m') @@ -1794,7 +1794,7 @@ def _design(self): D['Clarifier depth'] = self.depth_clarifier #in m D['Cylindrical depth'] = D['Clarifier depth'] - D['Conical depth'] - # Check on cylinderical and conical depths + # Check on cylindrical and conical depths if D['Cylindrical depth'] < D['Conical depth']: Cylindrical_depth = D['Cylindrical depth'] Conical_depth = D['Conical depth'] From 88dd407f384834796fbddce647e8e700b1cda709 Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Wed, 25 Oct 2023 14:03:15 -0500 Subject: [PATCH 209/424] Added BM factors to centrifuge Added missing bare module factors to centrifuge. --- qsdsan/sanunits/_sludge_treatment.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py index 8dbf855f..91f9f62f 100644 --- a/qsdsan/sanunits/_sludge_treatment.py +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -25,14 +25,17 @@ __all__ = ('Thickener', 'Centrifuge', 'Incinerator') -# Assign a bare module of 1 to all +# Asign a bare module of 1 to all default_F_BM = { 'Wall concrete': 1., + 'Slab concrete': 1., 'Wall stainless steel': 1., + 'Scraper': 1, + 'v notch weir': 1, + 'Pumps': 1 } default_F_BM.update(default_WWTpump_F_BM) - class Thickener(SanUnit): """ @@ -623,8 +626,6 @@ def _cost(self): self.power_utility.rate += pumping self.power_utility.rate += scraper_power -# blank comment - # %% class Centrifuge(Thickener): From 08b4ae148d7af0c88ea1b701ff4578b018e30ce9 Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Wed, 25 Oct 2023 16:33:41 -0500 Subject: [PATCH 210/424] Design and cost of centrifuge Update design, cost, and default design parameters for centrifuge. --- qsdsan/sanunits/_sludge_treatment.py | 69 ++++++++++++++-------------- 1 file changed, 35 insertions(+), 34 deletions(-) diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py index 91f9f62f..9425d9e0 100644 --- a/qsdsan/sanunits/_sludge_treatment.py +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -22,7 +22,6 @@ default_equipment_lifetime as default_WWTpump_equipment_lifetime, ) - __all__ = ('Thickener', 'Centrifuge', 'Incinerator') # Asign a bare module of 1 to all @@ -32,7 +31,11 @@ 'Wall stainless steel': 1., 'Scraper': 1, 'v notch weir': 1, - 'Pumps': 1 + 'Pumps': 1, + + # Centrifuge + 'Bowl stainless steel': 1, + 'Conveyor': 1 } default_F_BM.update(default_WWTpump_F_BM) @@ -628,6 +631,15 @@ def _cost(self): # %% +# Asign a bare module of 1 to all + +# default_F_BM = { +# 'Bowl stainless steel': 1, +# 'Conveyor': 1, +# 'Pumps': 1, +# } +# default_F_BM.update(default_WWTpump_F_BM) + class Centrifuge(Thickener): """ @@ -647,28 +659,23 @@ class Centrifuge(Thickener): The percentage of suspended solids removed in the dewatering unit.[1] solids_feed_rate : float Rate of solids processed by one centrifuge in dry tonne per day (dtpd). - Default value is 70 dtpd. - - # specific_gravity_sludge: float - # Specific gravity of influent sludge from secondary clarifier.[2,3] - # cake density: float - # Density of effleunt dewatered sludge.[2,3] - + Default value is 150 dtpd. [6] g_factor : float Factor by which g (9.81 m/s2) is multiplied to obtain centrifugal acceleration. g_factor typically lies between 1500 and 3000. centrifugal acceleration = g * g_factor = k * (RPM)^2 * diameter [3] rotational_speed : float - rotational speed of the centrifuge in rpm. [3] + rotational speed of the centrifuge in rpm. Typical rpm is between 2000-3000 rpm [MOP-8, PAGE 1733] LtoD: The ratio of length to diameter of the centrifuge. The value typically lies between 3-4. [4] polymer_dosage : float mass of polymer utilised (lb) per tonne of dry solid waste (lbs/tonne).[5] Depends on the type of influents, please refer to [5] for appropriate values. + Default value of 20 lbs/tonne is taken from [5], based on Primary + WAS aerated undigested value. h_cylindrical: float length of cylindrical portion of dewatering unit. h_conical: float - length of conical portion of dewatering unit.[ + length of conical portion of dewatering unit. References @@ -681,6 +688,8 @@ class Centrifuge(Thickener): No. 8 ASCE Manuals and Reports on Engineering Practice No. 76, Fifth Edition. [4] https://www.alibaba.com/product-detail/Multifunctional-Sludge-Dewatering-Decanter-Centrifuge_1600285055254.html?spm=a2700.galleryofferlist.normal_offer.d_title.1cd75229sPf1UW&s=p [5] United States Environmental Protection Agency (EPA) 'Biosolids Technology Fact Sheet Centrifuge Thickening and Dewatering' + [6] San Diego (.gov) Chapter - 3 'Solids Treatment Facility' + (https://www.sandiego.gov/sites/default/files/legacy/mwwd/pdf/mbc/chapterthree.pdf) """ _N_ins = 1 @@ -688,27 +697,19 @@ class Centrifuge(Thickener): _ins_size_is_fixed = False # Costs - stainless_steel_unit_cost=1.8 # $/kg (Taken from Joy's METAB code) https://www.alibaba.com/product-detail/brushed-stainless-steel-plate-304l-stainless_1600391656401.html?spm=a2700.details.0.0.230e67e6IKwwFd - screw_conveyor_unit_cost_by_weight = 1800/800 # $/kg (Source: https://www.alibaba.com/product-detail/Engineers-Available-Service-Stainless-Steel-U_60541536633.html?spm=a2700.galleryofferlist.normal_offer.d_title.1fea1f65v6R3OQ&s=p) - polymer_cost_by_weight = 5 # !!!Placeholder value!!! + stainless_steel_unit_cost=1.8 # $/Kg (Taken from Joy's METAB code) https://www.alibaba.com/product-detail/brushed-stainless-steel-plate-304l-stainless_1600391656401.html?spm=a2700.details.0.0.230e67e6IKwwFd + polymer_cost_by_weight = 2.2 # $/Kg (Source: https://www.alibaba.com/product-detail/dewatering-pool-chemicals-cationic-polyacrylamide-cas_1600194474507.html?spm=a2700.galleryofferlist.topad_classic.i5.5de8615c4zGAhg) def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, init_with='WasteStream', F_BM_default=default_F_BM, thickener_perc=28, TSS_removal_perc=98, - solids_feed_rate = 70, - # specific_gravity_sludge=1.03, cake_density=965, - g_factor=2500, rotational_speed = 40, LtoD = 4, - polymer_dosage = 0.0075, h_cylindrical=2, h_conical=1, - **kwargs): + solids_feed_rate = 70, g_factor=2500, rotational_speed = 2500, LtoD = 4, F_BM = default_F_BM, + polymer_dosage = 20, h_cylindrical=2, h_conical=1, **kwargs): + Thickener.__init__(self, ID=ID, ins=ins, outs=outs, thermo=thermo, isdynamic=isdynamic, init_with=init_with, F_BM_default=1, thickener_perc=thickener_perc, TSS_removal_perc=TSS_removal_perc, **kwargs) - # self._mixed = self.ins[0].copy(f'{ID}_mixed') - # self._inf = self.ins[0].copy(f'{ID}_inf') - self.solids_feed_rate = solids_feed_rate - # self.specific_gravity_sludge=specific_gravity_sludge - # self.cake_density=cake_density #in kg/m3 self.g_factor = g_factor #unitless, centrifugal acceleration = g_factor*9.81 self.rotational_speed = rotational_speed #in revolution/min self.LtoD = LtoD @@ -717,19 +718,17 @@ def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, self.h_conical = h_conical _units = { - 'Number of centrifuges': '?', + 'Number of centrifuges': 'ea', 'Diameter of bowl': 'm', 'Total length of bowl': 'm', 'Length of cylindrical portion': 'm', 'Length of conical portion': 'm', 'Volume of bowl': 'm3', - 'Stainless steel for bowl': 'kg', - 'Polymer feed rate': 'kg/hr', 'Pump pipe stainless steel' : 'kg', 'Pump stainless steel': 'kg', - 'Number of pumps': '?' + 'Number of pumps': 'ea' } def _design_pump(self): @@ -773,8 +772,7 @@ def _design_pump(self): pump_ss += p_design['Pump stainless steel'] return pipe_ss, pump_ss - def _design(self): - + def _design(self): self._mixed.mix_from(self.ins) mixed = self._mixed @@ -785,6 +783,7 @@ def _design(self): # If TSS_rmv = 98, then total_mass_dry_solids_removed = (0.98)*(influent TSS mass) total_mass_dry_solids_removed = (TSS_rmv/100)*((mixed.get_TSS()*self.ins[0].F_vol)/1000) # in kg/hr D['Number of centrifuges'] = np.ceil(total_mass_dry_solids_removed/solids_feed_rate) + k = 0.00000056 # Based on emprical formula (pg. 24-23 of [3]) g = 9.81 # m/s2 # The inner diameterof the bowl is calculated based on an empirical formula. 1000 is used to convert mm to m. @@ -799,6 +798,7 @@ def _design(self): thickness_of_bowl_wall = 0.1 # in m (!!! NEED A RELIABLE SOURCE !!!) inner_diameter = D['Diameter of bowl'] outer_diameter = inner_diameter + 2*thickness_of_bowl_wall + volume_cylindrical_wall = (np.pi*D['Length of cylindrical portion']/4)*(outer_diameter**2 - inner_diameter**2) volume_conical_wall = (np.pi/3)*(D['Length of conical portion']/4)*(outer_diameter**2 - inner_diameter**2) D['Volume of bowl'] = volume_cylindrical_wall + volume_conical_wall # in m3 @@ -806,13 +806,14 @@ def _design(self): density_ss = 7930 # kg/m3, 18/8 Chromium D['Stainless steel for bowl'] = D['Volume of bowl']*density_ss # in kg - polymer_dosage_rate = 0.000453592*self.polymer_dosage # convert from (lbs, polymer/tonne, solids) to (kg, polymer/kg, solids) - D['Polymer feed rate'] = (polymer_dosage_rate*solids_feed_rate) # in kg, polymer/hr + polymer_dosage_rate = 0.000453592*self.polymer_dosage # convert from (polymer (lbs)/solids (tonne)) to (polymer (kg)/solids (kg)) + D['Polymer feed rate'] = (polymer_dosage_rate*solids_feed_rate) # in polymer (kg)/hr # Pumps pipe, pumps = self._design_pump() D['Pump pipe stainless steel'] = pipe D['Pump stainless steel'] = pumps + # For centrifuges D['Number of pumps'] = D['Number of centrifuges'] @@ -826,8 +827,6 @@ def _cost(self): # Construction of concrete and stainless steel walls C['Bowl stainless steel'] = D['Number of centrifuges']*D['Stainless steel for bowl']*self.stainless_steel_unit_cost - # C['Conveyor stainless steel'] = D['Number of centrifuges']*D['Stainless steel for conveyor']*self.screw_conveyor_unit_cost_by_weight - C['Polymer'] = D['Number of centrifuges']*D['Polymer feed rate']*self.polymer_cost_by_weight # Conveyor # Source: https://www.alibaba.com/product-detail/Engineers-Available-Service-Stainless-Steel-U_60541536633.html?spm=a2700.galleryofferlist.normal_offer.d_title.1fea1f65v6R3OQ&s=p @@ -845,6 +844,8 @@ def _cost(self): building_cost = 0. opex_o = 0. opex_m = 0. + + add_OPEX['Polymer'] = D['Number of centrifuges']*D['Polymer feed rate']*self.polymer_cost_by_weight for i in pumps: p = getattr(self, f'{i}_pump') From d57a78de65c31410e304a7804a8e2bf0135b4e27 Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Mon, 30 Oct 2023 14:29:11 -0500 Subject: [PATCH 211/424] Design and cost of tanks Started adding design components for aeration tank. --- .../sanunits/_suspended_growth_bioreactor.py | 129 +++++++++++++++++- 1 file changed, 123 insertions(+), 6 deletions(-) diff --git a/qsdsan/sanunits/_suspended_growth_bioreactor.py b/qsdsan/sanunits/_suspended_growth_bioreactor.py index a2659d5a..09c8e6b2 100644 --- a/qsdsan/sanunits/_suspended_growth_bioreactor.py +++ b/qsdsan/sanunits/_suspended_growth_bioreactor.py @@ -79,6 +79,14 @@ class CSTR(SanUnit): The default is None. V_max : float Designed volume, in [m^3]. The default is 1000. + + W_tank : float + The design width of the tank, in [m]. The default is 6.4 m (21 ft). [1, Yalin's adaptation of code] + D_tank : float + The design depth of the tank in [m]. The default is 3.65 m (12 ft). [1, Yalin's adaptation of code] + freeboard : float + Freeboard added to the depth of the reactor tank, [m]. The default is 0.61 m (2 ft). [1, Yalin's adaptation of code] + aeration : float or :class:`Process`, optional Aeration setting. Either specify a targeted dissolved oxygen concentration in [mg O2/L] or provide a :class:`Process` object to represent aeration, @@ -92,17 +100,25 @@ class CSTR(SanUnit): Any exogenous dynamic variables that affect the process mass balance, e.g., temperature, sunlight irradiance. Must be independent of state variables of the suspended_growth_model (if has one). + + References: + + [1] Shoener, B. D.; Zhong, C.; Greiner, A. D.; Khunjar, W. O.; Hong, P.-Y.; Guest, J. S. + Design of Anaerobic Membrane Bioreactors for the Valorization + of Dilute Organic Carbon Waste Streams. + Energy Environ. Sci. 2016, 9 (3), 1102–1112. + https://doi.org/10.1039/C5EE03715H. + ''' - _N_ins = 3 _N_outs = 1 _ins_size_is_fixed = False _outs_size_is_fixed = False def __init__(self, ID='', ins=None, outs=(), split=None, thermo=None, - init_with='WasteStream', V_max=1000, aeration=2.0, - DO_ID='S_O2', suspended_growth_model=None, - isdynamic=True, exogenous_vars=(), **kwargs): + init_with='WasteStream', V_max=1000, W_tank = 6.4, D_tank = 3.65, + freeboard = 0.61, t_wall = None, t_slab = None, aeration=2.0, + DO_ID='S_O2', suspended_growth_model=None, isdynamic=True, exogenous_vars=(), **kwargs): SanUnit.__init__(self, ID, ins, outs, thermo, init_with, isdynamic=isdynamic, exogenous_vars=exogenous_vars, **kwargs) self._V_max = V_max @@ -112,9 +128,17 @@ def __init__(self, ID='', ins=None, outs=(), split=None, thermo=None, self._concs = None self._mixed = WasteStream() self.split = split + + # Design parameters + self._W_tank = W_tank + self._D_tank = D_tank + self._freeboard = freeboard + self._t_wall = t_wall + self._t_slab = t_slab + # for attr, value in kwargs.items(): # setattr(self, attr, value) - + @property def V_max(self): '''[float] The designed maximum liquid volume, not accounting for increased volume due to aeration, in m^3.''' @@ -123,7 +147,59 @@ def V_max(self): @V_max.setter def V_max(self, Vm): self._V_max = Vm + + @property + def W_tank(self): + '''[float] The design width of the tank, in m.''' + return self._W_tank + + @W_tank.setter + def W_tank(self, W_tank): + self._W_tank = W_tank + + @property + def D_tank(self): + '''[float] The design depth of the tank, in m.''' + return self._D_tank + @D_tank.setter + def D_tank(self, D_tank): + self._D_tank = D_tank + + @property + def freeboard(self): + '''[float] Freeboard added to the depth of the reactor tank, [m].''' + return self._freeboard + + @freeboard.setter + def freeboard(self, i): + self._freeboard = i + + @property + def t_wall(self): + ''' + [float] Thickness of the wall concrete, [m]. + default to be minimum of 1 ft with 1 in added for every ft of depth over 12 ft. + ''' + D_tank = self.D_tank*39.37 # m to inches + return self._t_wall or (1 + max(D_tank - 12, 0)/12)*0.3048 # from feet to m + + @t_wall.setter + def t_wall(self, i): + self._t_wall = i + + @property + def t_slab(self): + ''' + [float] Concrete slab thickness, [m], + default to be 2 in thicker than the wall thickness. + ''' + return self._t_slab or (self.t_wall + 2/12)*0.3048 # from feet to m + + @t_slab.setter + def t_slab(self, i): + self._t_slab = i + @property def aeration(self): '''[:class:`Process` or float or NoneType] Aeration model.''' @@ -287,9 +363,50 @@ def dy_dt(t, QC_ins, QC, dQC_ins): _update_dstate() self._ODE = dy_dt + + _units = { + 'Tank volume': 'm3', + 'Tank width': 'm', + 'Tank depth': 'm', + 'Tank length': 'm', + + 'Volume of concrete wall': 'm3', + 'Volume of concrete slab': 'm3' + } def _design(self): - pass + self._mixed.mix_from(self.ins) + mixed = self._mixed + D = self.design_results + + D['Tank volume'] = self.V_max + D['Tank width'] = self.W_tank + D['Tank depth'] = self.D_tank + D['Tank length'] = D['Volume']/(D['Tank width']*D['Tank depth']) + + t_wall, t_slab = self.t_wall, self.t_slab + t = t_wall + t_slab + D_tot = D['Tank depth'] + self.freeboard + + # get volume of wall concrete + VWC = 2*((D['Tank length'] + 2*t_wall)*t_wall*D_tot) + 2*(D['Tank width']*t_wall*D_tot) + + # get volume of slab concrete + VSC = (D['Tank length'] + 2*t_wall)*(D['Tank width'] + 2*t_wall)*t + + D['Volume of concrete wall'] = VWC + D['Volume of concrete slab'] = VSC + + def _cost(self): + + self._mixed.mix_from(self.ins) + + D = self.design_results + C = self.baseline_purchase_costs + + # Construction of concrete and stainless steel walls + C['Wall concrete'] = D['Volume of concrete wall']*self.wall_concrete_unit_cost + C['Slab concrete'] = D['Volume of concrete slab']*self.slab_concrete_unit_cost #%% class BatchExperiment(SanUnit): From 787a4a52ac578fea3e1b127925bd90a69a5c11f7 Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Tue, 31 Oct 2023 11:53:27 -0500 Subject: [PATCH 212/424] Design and cost of ASP A dummy unit to account for the design and cost of treatment chain in ASP. --- qsdsan/sanunits/_activated_sludge_process.py | 223 ++++++++++++++++++- 1 file changed, 222 insertions(+), 1 deletion(-) diff --git a/qsdsan/sanunits/_activated_sludge_process.py b/qsdsan/sanunits/_activated_sludge_process.py index 9750ce5d..40090f71 100644 --- a/qsdsan/sanunits/_activated_sludge_process.py +++ b/qsdsan/sanunits/_activated_sludge_process.py @@ -724,4 +724,225 @@ def constr_access(self): return self._constr_access @constr_access.setter def constr_access(self, i): - self._constr_access = i \ No newline at end of file + self._constr_access = i + + +class TreatmentChain(Mixer): + + # Costs + wall_concrete_unit_cost = 1081.73 # $/m3 (Hydromantis. CapdetWorks 4.0. https://www.hydromantis.com/CapdetWorks.html) + slab_concrete_unit_cost = 582.48 # $/m3 (Hydromantis. CapdetWorks 4.0. https://www.hydromantis.com/CapdetWorks.html) + + def __init__(self, ID='', ins=None, outs=(), thermo=None, init_with='WasteStream', + F_BM_default=1, N_train=2, V_tank=1000, + W_tank = 6.4, D_tank = 3.66, freeboard = 0.61, W_dist = 1.37, W_eff = 1.5, # all in meter (converted from feet to m, Shoener et al.2016) + W_RAS = 1.5, # in m (assumed same as W_eff) + excav_slope = 1.5, # horizontal/vertical (Shoener et al.2016) + constr_access = 0.92, # in meter (converted from feet to m, Shoener et al. 2016) + F_BM=default_F_BM, lifetime=default_equipment_lifetime, + **kwargs): + SanUnit.__init__(self, ID, ins, outs, thermo, init_with, F_BM_default=1) + + self.N_train = N_train + self.V_tank = V_tank + self.W_tank = W_tank + self.D_tank = D_tank + self.freeboard = freeboard + self.W_dist = W_dist + self.W_eff = W_eff + self.W_RAS = W_RAS + self.excav_slope = excav_slope + self.constr_access = constr_access + self.F_BM.update(F_BM) + + @property + def N_train(self): + ''' + [int] Number of treatment train, should be at least two in case one failing. + ''' + return self._N_train + + @N_train.setter + def N_train(self, i): + i = ceil(i) + if i < 2: + raise ValueError('`N_train` should be at least 2.') + self._N_train = i + + @property + def V_tank(self): + '''[float] Volume of tank, [m3].''' + return self._V_tank + + @V_tank.setter + def V_tank(self, i): + self._V_tank = i + + @property + def W_tank(self): + '''[float] Width of one tank, [m].''' + return self._W_tank + + @W_tank.setter + def W_tank(self, i): + self._W_tank = i + + @property + def D_tank(self): + '''[float] Depth of one tank, [m].''' + return self._D_tank + + @D_tank.setter + def D_tank(self, i): + self._D_tank = i + + @property + def W_dist(self): + '''[float] Width of the distribution channel, [m].''' + return self._W_dist + + @W_dist.setter + def W_dist(self, i): + self._W_dist = i + + @property + def W_eff(self): + '''[float] Width of the effluent channel, [m].''' + return self._W_eff + + @W_eff.setter + def W_eff(self, i): + self._W_eff = i + + @property + def W_RAS(self): + '''[float] Width of the recirculation channel, [m].''' + return self._W_RAS + + @W_RAS.setter + def W_eff(self, i): + self._W_RAS = i + + @property + def freeboard(self): + '''[float] Freeboard added to the depth of the reactor tank, [m].''' + return self._freeboard + + @freeboard.setter + def freeboard(self, i): + self._freeboard = i + + @property + def t_wall(self): + ''' + [float] Thickness of the wall concrete, [m]. + default to be minimum of 1 ft with 1 in added for every ft of depth over 12 ft. + ''' + D_tank = self.D_tank*39.37 # m to inches + return self._t_wall or (1 + max(D_tank - 12, 0)/12)*0.3048 # from feet to m + + @t_wall.setter + def t_wall(self, i): + self._t_wall = i + + @property + def t_slab(self): + ''' + [float] Concrete slab thickness, [m], + default to be 2 in thicker than the wall thickness. + ''' + return self._t_slab or (self.t_wall + 2/12)*0.3048 # from feet to m + + @t_slab.setter + def t_slab(self, i): + self._t_slab = i + + @property + def excav_slope(self): + '''[float] Slope for excavation (horizontal/vertical).''' + return self._excav_slope + + @excav_slope.setter + def excav_slope(self, i): + self._excav_slope = i + + @property + def constr_access(self): + '''[float] Extra room for construction access, [m].''' + return self._constr_access + + @constr_access.setter + def constr_access(self, i): + self._constr_access = i + + def _run(self): + pass + + def _design(self): + self._mixed.mix_from(self.ins) + mixed = self._mixed + + D = self.design_results + + D['N_train'] = self.N_train + D['Tank volume'] = self.V_tank # in m3 + D['HRT'] = D['Tank volume']/mixed.get_total_flow('m3/hr') # in hr + D['Tank width'] = self.W_tank # in m + D['Tank depth'] = self.D_tank # in m + D['Tank length'] = D['Tank volume']/(D['Aeration tank width']*D['Aeration tank depth']) # in m + + + t_wall, t_slab = self.t_wall, self.t_slab + W_N_trains = (D['Aeration tank width'] + 2*t_wall)*D['N_train'] - t_wall*(D['N_train']-1) + + D_tot = D['Tank depth'] + self.freeboard + t = t_wall + t_slab + + get_VWC = lambda L1, N: N * t_wall * L1 * D_tot # get volume of wall concrete + get_VSC = lambda L2: t * L2 * W_N_trains # get volume of slab concrete + + # Aeration tanks, [m3] + VWC = get_VWC(L1= D['Tank length'], N=(D['N_train'] + 1)) + VSC = get_VSC(L2= D['Tank length']) + + # Distribution channel, [m3] + W_dist, W_eff, W_RAS = self.W_dist, self.W_eff, self.W_RAS + VWC += get_VWC(L1=(W_N_trains+W_dist), N=2) # N =2 for two walls + VSC += get_VSC(L2=(W_dist + 2*t_wall)) + + # Effluent channel, [m3] + VWC += get_VWC(L1=(W_N_trains + W_eff), N=2) # N =2 for two walls + VSC += get_VSC(L2=(W_eff + 2*t_wall)) + + # RAS channel, [m3] + VWC += get_VWC(L1=(W_N_trains + W_RAS), N=2) # N =2 for two walls + VSC += get_VSC(L2=(W_RAS + 2*t_wall)) + + D['Wall concrete'] = VWC + D['Slab concrete'] = VSC + + # Excavation + excav_slope, constr_access = self.excav_slope, self.constr_access + + # Aeration tank and clarifier + VEX = calculate_excavation_volume( + L=(W_dist + D['Tank length']), W = W_N_trains, D = D['Tank depth'], + excav_slope=excav_slope, constr_access=constr_access) + + D['Excavation'] = VEX + + # Pumps + pipe, pumps = self._design_pump() + D['Pump pipe stainless steel'] = pipe + D['Pump stainless steel'] = pumps + + + + + + + + + + + \ No newline at end of file From f699ac5b265fd6c945cc746ccdf7a2975a9e8692 Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Tue, 31 Oct 2023 12:29:26 -0500 Subject: [PATCH 213/424] Added blower and pipe design Assumed design Q_air (in m3/hr) for individual treatment chain as a user input. --- qsdsan/sanunits/_activated_sludge_process.py | 24 ++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/qsdsan/sanunits/_activated_sludge_process.py b/qsdsan/sanunits/_activated_sludge_process.py index 40090f71..6830bade 100644 --- a/qsdsan/sanunits/_activated_sludge_process.py +++ b/qsdsan/sanunits/_activated_sludge_process.py @@ -739,6 +739,7 @@ def __init__(self, ID='', ins=None, outs=(), thermo=None, init_with='WasteStream W_RAS = 1.5, # in m (assumed same as W_eff) excav_slope = 1.5, # horizontal/vertical (Shoener et al.2016) constr_access = 0.92, # in meter (converted from feet to m, Shoener et al. 2016) + Q_air_design = 1000, # in m3/hr **NO SOURCE FOR DEFAULT VALUE YET** F_BM=default_F_BM, lifetime=default_equipment_lifetime, **kwargs): SanUnit.__init__(self, ID, ins, outs, thermo, init_with, F_BM_default=1) @@ -753,6 +754,12 @@ def __init__(self, ID='', ins=None, outs=(), thermo=None, init_with='WasteStream self.W_RAS = W_RAS self.excav_slope = excav_slope self.constr_access = constr_access + self.Q_air_design = Q_air_design # **NO SOURCE FOR DEFAULT VALUE YET** + + self.blower = blower = Blower('blower', linked_unit=self, N_reactor=N_train) + self.air_piping = air_piping = GasPiping('air_piping', linked_unit=self, N_reactor=N_train) + self.equipments = (blower, air_piping) + self.F_BM.update(F_BM) @property @@ -875,6 +882,15 @@ def constr_access(self): def constr_access(self, i): self._constr_access = i + @property + def Q_air_design(self): + '''[float] Air flow required for one treatment train, [m3/hr].''' + return self._Q_air_design + + @Q_air_design.setter + def Q_air_design(self, i): + self._Q_air_design = i + def _run(self): pass @@ -931,6 +947,14 @@ def _design(self): D['Excavation'] = VEX + Q_air_design = self.Q_air_design + # Blower and gas piping (taken from 'ActivatedSludgeProcess' SanUnit) + air_cfm = auom('m3/hr').convert(Q_air_design, 'cfm') + blower, piping = self.equipments + blower.N_reactor = piping.N_reactor = D['N_train'] + blower.gas_demand_per_reactor = piping.gas_demand_per_reactor = air_cfm + self.add_equipment_design() + # Pumps pipe, pumps = self._design_pump() D['Pump pipe stainless steel'] = pipe From b132eddb08c6ae723136b104f87c843980cc7a49 Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Wed, 1 Nov 2023 13:47:28 -0500 Subject: [PATCH 214/424] Included pump design to treatment chain Added a design recirculation flow, and a 'design_pump' function to facilitate the working of pump in treatment chain. --- qsdsan/sanunits/_activated_sludge_process.py | 108 +++++++++++++++---- 1 file changed, 86 insertions(+), 22 deletions(-) diff --git a/qsdsan/sanunits/_activated_sludge_process.py b/qsdsan/sanunits/_activated_sludge_process.py index 6830bade..6215de17 100644 --- a/qsdsan/sanunits/_activated_sludge_process.py +++ b/qsdsan/sanunits/_activated_sludge_process.py @@ -726,7 +726,7 @@ def constr_access(self): def constr_access(self, i): self._constr_access = i - + class TreatmentChain(Mixer): # Costs @@ -740,6 +740,7 @@ def __init__(self, ID='', ins=None, outs=(), thermo=None, init_with='WasteStream excav_slope = 1.5, # horizontal/vertical (Shoener et al.2016) constr_access = 0.92, # in meter (converted from feet to m, Shoener et al. 2016) Q_air_design = 1000, # in m3/hr **NO SOURCE FOR DEFAULT VALUE YET** + Q_recirculation = 1000, # in m3/day **NO SOURCE FOR DEFAULT VALUE YET** F_BM=default_F_BM, lifetime=default_equipment_lifetime, **kwargs): SanUnit.__init__(self, ID, ins, outs, thermo, init_with, F_BM_default=1) @@ -755,11 +756,11 @@ def __init__(self, ID='', ins=None, outs=(), thermo=None, init_with='WasteStream self.excav_slope = excav_slope self.constr_access = constr_access self.Q_air_design = Q_air_design # **NO SOURCE FOR DEFAULT VALUE YET** + self.Q_recirculation = Q_recirculation self.blower = blower = Blower('blower', linked_unit=self, N_reactor=N_train) self.air_piping = air_piping = GasPiping('air_piping', linked_unit=self, N_reactor=N_train) self.equipments = (blower, air_piping) - self.F_BM.update(F_BM) @property @@ -890,26 +891,99 @@ def Q_air_design(self): @Q_air_design.setter def Q_air_design(self, i): self._Q_air_design = i - + + @property + def Q_recirculation(self): + '''[float] Recirculated waste flow in the treatment train, [m3/day].''' + return self._Q_recirculation + + @Q_recirculation.setter + def Q_recirculation(self, i): + self._Q_recirculation = i + def _run(self): pass + def _design_pump(self): + + ID, pumps = self.ID, self.pumps + D = self.design_results + + Q_recirculation = D['Q_recirculation'] + meter_to_feet = 3.28 + Tank_length = D['Tank length']*meter_to_feet # in ft + + Q_recirculation_mgd = Q_recirculation*0.000264 #m3/day to MGD + Q_mgd = { + 'recirculation': Q_recirculation_mgd, + } + + type_dct = dict.fromkeys(pumps, 'recirculation_CSTR') + inputs_dct = dict.fromkeys(pumps, (self.N_train, Tank_length,)) + + for i in pumps: + if hasattr(self, f'{i}_pump'): + p = getattr(self, f'{i}_pump') + setattr(p, 'add_inputs', inputs_dct[i]) + else: + ID = f'{ID}_{i}' + capacity_factor=1 + pump = WWTpump( + ID=ID, ins= None, pump_type=type_dct[i], + Q_mgd=Q_mgd[i], add_inputs=inputs_dct[i], + capacity_factor=capacity_factor, + include_pump_cost=True, + include_building_cost=False, + include_OM_cost=True, + ) + setattr(self, f'{i}_pump', pump) + + pipe_ss, pump_ss = 0., 0. + for i in pumps: + p = getattr(self, f'{i}_pump') + p.simulate() + p_design = p.design_results + pipe_ss += p_design['Pump pipe stainless steel'] + pump_ss += p_design['Pump stainless steel'] + + return pipe_ss, pump_ss + + _units = { + 'Number of trains': 'ea', + 'Tank volume': 'm3', + 'HRT': 'd', + 'Tank width': 'm', + 'Tank depth': 'm', + 'Tank length': 'm', + + 'Wall concrete': 'm3', + 'Slab concrete': 'm3', + 'Excavation': 'm3', + + 'Q_recirculation': 'm3/day', + + 'Pump pipe stainless steel': 'kg', + 'Pump stainless steel': 'kg', + + + } + def _design(self): self._mixed.mix_from(self.ins) mixed = self._mixed D = self.design_results - D['N_train'] = self.N_train + D['Number of trains'] = self.N_train D['Tank volume'] = self.V_tank # in m3 D['HRT'] = D['Tank volume']/mixed.get_total_flow('m3/hr') # in hr D['Tank width'] = self.W_tank # in m D['Tank depth'] = self.D_tank # in m - D['Tank length'] = D['Tank volume']/(D['Aeration tank width']*D['Aeration tank depth']) # in m + D['Tank length'] = D['Tank volume']/(D['Tank width']*D['Tank depth']) # in m t_wall, t_slab = self.t_wall, self.t_slab - W_N_trains = (D['Aeration tank width'] + 2*t_wall)*D['N_train'] - t_wall*(D['N_train']-1) + W_N_trains = (D['Tank width'] + 2*t_wall)*D['Number of trains'] - t_wall*(D['Number of trains'] - 1) D_tot = D['Tank depth'] + self.freeboard t = t_wall + t_slab @@ -918,7 +992,7 @@ def _design(self): get_VSC = lambda L2: t * L2 * W_N_trains # get volume of slab concrete # Aeration tanks, [m3] - VWC = get_VWC(L1= D['Tank length'], N=(D['N_train'] + 1)) + VWC = get_VWC(L1= D['Tank length'], N=(D['Number of trains'] + 1)) VSC = get_VSC(L2= D['Tank length']) # Distribution channel, [m3] @@ -939,16 +1013,17 @@ def _design(self): # Excavation excav_slope, constr_access = self.excav_slope, self.constr_access - - # Aeration tank and clarifier + # Aeration tank VEX = calculate_excavation_volume( L=(W_dist + D['Tank length']), W = W_N_trains, D = D['Tank depth'], excav_slope=excav_slope, constr_access=constr_access) D['Excavation'] = VEX - Q_air_design = self.Q_air_design + D['Q_recirculation'] = self.Q_recirculation + # Blower and gas piping (taken from 'ActivatedSludgeProcess' SanUnit) + Q_air_design = self.Q_air_design # in m3 air_cfm = auom('m3/hr').convert(Q_air_design, 'cfm') blower, piping = self.equipments blower.N_reactor = piping.N_reactor = D['N_train'] @@ -958,15 +1033,4 @@ def _design(self): # Pumps pipe, pumps = self._design_pump() D['Pump pipe stainless steel'] = pipe - D['Pump stainless steel'] = pumps - - - - - - - - - - - \ No newline at end of file + D['Pump stainless steel'] = pumps \ No newline at end of file From 4731a1c74911a79e772e033cf89c6e1b93bdc745 Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Wed, 1 Nov 2023 15:17:32 -0500 Subject: [PATCH 215/424] Added cost and documentation for treatment chain --- qsdsan/sanunits/_activated_sludge_process.py | 134 ++++++++++++++++--- 1 file changed, 116 insertions(+), 18 deletions(-) diff --git a/qsdsan/sanunits/_activated_sludge_process.py b/qsdsan/sanunits/_activated_sludge_process.py index 6215de17..14598f52 100644 --- a/qsdsan/sanunits/_activated_sludge_process.py +++ b/qsdsan/sanunits/_activated_sludge_process.py @@ -728,15 +728,73 @@ def constr_access(self, i): class TreatmentChain(Mixer): + ''' + Dummy unit with no run function of its own. To be used to calculate the + design and cost of treatment chains. Code largely dereived from code scripts + for [2]_. + + Parameters + ---------- + ins : Iterable + None expected (dummy unit). + outs : Iterable + None expected (dummy unit). + N_train : int + Number of treatment train, should be at least two in case one failing. + V_tank : float + Volume of tank, [m3]. Default is 1000 m3. + W_tank : float + Width of tank, [m]. Default is 6.4 m. [1] + D_tank : float + Depth of tank, [m]. Default is 3.66 m. [1] + freeboard : float + Freeboard added to the depth of the reactor tank, [m]. Default is 0.61 m. [1] + W_dist : float + Width of distribution channel, [m]. Default is 1.37 m. [1] + W_eff: float + Width of effluent channel, [m]. Default is 1.5 m. [1] + W_recirculation: float + Width of recirculation channel, [m]. Default is 1.5 m. + excav_slope: float + Slope for excavation (horizontal/vertical). Default is 1.5. [1] + constr_access: float + Extra room for construction access, [m]. Default is 0.92 m. [1] + Q_air_design: float + Used to calculate the power of blower. + Air flow required for one treatment train [m3/hr]. + Q_recirculation: float + Used to calculate pumping power. + Design recirculated flow in the treatment train [m3/day]. + kwargs : dict + Other attributes to be set. + + References + ---------- + .. [1] Rittmann, B.; McCarty, P.; McCarty, P. L.; Bruce, R. + Environmental Biotechnology: Principles and Applications; + McGraw-Hill Companies,Incorporated, 2001. + .. [2] Shoener, B. D.; Zhong, C.; Greiner, A. D.; Khunjar, W. O.; Hong, P.-Y.; Guest, J. S. + Design of Anaerobic Membrane Bioreactors for the Valorization + of Dilute Organic Carbon Waste Streams. + Energy Environ. Sci. 2016, 9 (3), 1102–1112. + https://doi.org/10.1039/C5EE03715H. + + See Also + -------- + `MATLAB codes `_ used in ref 1, + especially the system layout `diagrams `_. + + ''' # Costs wall_concrete_unit_cost = 1081.73 # $/m3 (Hydromantis. CapdetWorks 4.0. https://www.hydromantis.com/CapdetWorks.html) slab_concrete_unit_cost = 582.48 # $/m3 (Hydromantis. CapdetWorks 4.0. https://www.hydromantis.com/CapdetWorks.html) + excav_unit_cost = (8+0.3) / 0.765 # $/m3, 0.765 is to convert from $/yd3 **NOT UPDATED** (taken from Shoener et al.) - def __init__(self, ID='', ins=None, outs=(), thermo=None, init_with='WasteStream', + def __init__(self, ID='', ins=None, outs=None, thermo=None, init_with='WasteStream', F_BM_default=1, N_train=2, V_tank=1000, W_tank = 6.4, D_tank = 3.66, freeboard = 0.61, W_dist = 1.37, W_eff = 1.5, # all in meter (converted from feet to m, Shoener et al.2016) - W_RAS = 1.5, # in m (assumed same as W_eff) + W_recirculation = 1.5, # in m (assumed same as W_eff) excav_slope = 1.5, # horizontal/vertical (Shoener et al.2016) constr_access = 0.92, # in meter (converted from feet to m, Shoener et al. 2016) Q_air_design = 1000, # in m3/hr **NO SOURCE FOR DEFAULT VALUE YET** @@ -752,7 +810,7 @@ def __init__(self, ID='', ins=None, outs=(), thermo=None, init_with='WasteStream self.freeboard = freeboard self.W_dist = W_dist self.W_eff = W_eff - self.W_RAS = W_RAS + self.W_recirculation = W_recirculation self.excav_slope = excav_slope self.constr_access = constr_access self.Q_air_design = Q_air_design # **NO SOURCE FOR DEFAULT VALUE YET** @@ -823,13 +881,13 @@ def W_eff(self, i): self._W_eff = i @property - def W_RAS(self): + def W_recirculation(self): '''[float] Width of the recirculation channel, [m].''' - return self._W_RAS + return self._W_recirculation - @W_RAS.setter - def W_eff(self, i): - self._W_RAS = i + @W_recirculation.setter + def W_recirculation(self, i): + self._W_recirculation = i @property def freeboard(self): @@ -894,7 +952,7 @@ def Q_air_design(self, i): @property def Q_recirculation(self): - '''[float] Recirculated waste flow in the treatment train, [m3/day].''' + '''[float] Design recirculated flow in the treatment train, [m3/day].''' return self._Q_recirculation @Q_recirculation.setter @@ -955,17 +1013,12 @@ def _design_pump(self): 'Tank width': 'm', 'Tank depth': 'm', 'Tank length': 'm', - 'Wall concrete': 'm3', 'Slab concrete': 'm3', 'Excavation': 'm3', - 'Q_recirculation': 'm3/day', - 'Pump pipe stainless steel': 'kg', 'Pump stainless steel': 'kg', - - } def _design(self): @@ -996,7 +1049,7 @@ def _design(self): VSC = get_VSC(L2= D['Tank length']) # Distribution channel, [m3] - W_dist, W_eff, W_RAS = self.W_dist, self.W_eff, self.W_RAS + W_dist, W_eff, W_recirculation = self.W_dist, self.W_eff, self.W_recirculation VWC += get_VWC(L1=(W_N_trains+W_dist), N=2) # N =2 for two walls VSC += get_VSC(L2=(W_dist + 2*t_wall)) @@ -1005,8 +1058,8 @@ def _design(self): VSC += get_VSC(L2=(W_eff + 2*t_wall)) # RAS channel, [m3] - VWC += get_VWC(L1=(W_N_trains + W_RAS), N=2) # N =2 for two walls - VSC += get_VSC(L2=(W_RAS + 2*t_wall)) + VWC += get_VWC(L1=(W_N_trains + W_recirculation), N=2) # N =2 for two walls + VSC += get_VSC(L2=(W_recirculation + 2*t_wall)) D['Wall concrete'] = VWC D['Slab concrete'] = VSC @@ -1033,4 +1086,49 @@ def _design(self): # Pumps pipe, pumps = self._design_pump() D['Pump pipe stainless steel'] = pipe - D['Pump stainless steel'] = pumps \ No newline at end of file + D['Pump stainless steel'] = pumps + + def _cost(self): + D = self.design_results, + C = self.baseline_purchase_costs + + ### Capital ### + # Concrete and excavation + VWC = D['Wall concrete'] + VSC = D['Slab concrete'] + VEX = D['Excavation'] + + C['Wall concrete'] = VWC * self.wall_concrete_unit_cost + C['Slab concrete'] = VSC * self.slab_concrete_unit_cost + C['Reactor excavation'] = VEX * self.excav_unit_cost + + # Pump + pumps, add_OPEX = self.pumps, self.add_OPEX + pump_cost, building_cost, opex_o, opex_m = 0., 0., 0., 0. + for i in pumps: + p = getattr(self, f'{i}_pump') + p_cost, p_add_opex = p.baseline_purchase_costs, p.add_OPEX + pump_cost += p_cost['Pump'] + building_cost += p_cost['Pump building'] + opex_o += p_add_opex['Pump operating'] + opex_m += p_add_opex['Pump maintenance'] + + C['Pumps'] = pump_cost + C['Pump building'] = building_cost + add_OPEX['Pump operating'] = opex_o + add_OPEX['Pump maintenance'] = opex_m + + # Blower + self.add_equipment_cost() + + ### Heat and power ### + + # Power + pumping = 0. + for ID in self.pumps: + p = getattr(self, f'{ID}_pump') + if p is None: + continue + pumping += p.power_utility.rate + + self.power_utility.rate = self.blower.design_results['Total blower power'] + pumping \ No newline at end of file From 042dfe11ad034e7a3a04cdcacb330ebcd0b2858a Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Wed, 1 Nov 2023 16:23:43 -0500 Subject: [PATCH 216/424] Minor adjustments in treatment chain Minor adjustments with documentation and debugging, made while integrating the unit with a centralized WRRF. --- qsdsan/sanunits/_activated_sludge_process.py | 34 ++++++++------------ 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/qsdsan/sanunits/_activated_sludge_process.py b/qsdsan/sanunits/_activated_sludge_process.py index 14598f52..424b4a09 100644 --- a/qsdsan/sanunits/_activated_sludge_process.py +++ b/qsdsan/sanunits/_activated_sludge_process.py @@ -17,10 +17,10 @@ from math import ceil from biosteam import Stream from .. import SanUnit -from ..sanunits import HXutility, WWTpump +from ..sanunits import HXutility, WWTpump, Mixer from ..equipments import Blower, GasPiping from ..utils import auom, calculate_excavation_volume -__all__ = ('ActivatedSludgeProcess',) +__all__ = ('ActivatedSludgeProcess','TreatmentTrains',) _ft2_to_m2 = auom('ft2').conversion_factor('m2') F_BM_pump = 1.18*(1+0.007/100) # 0.007 is for miscellaneous costs @@ -727,10 +727,10 @@ def constr_access(self, i): self._constr_access = i -class TreatmentChain(Mixer): +class TreatmentTrains(Mixer): ''' Dummy unit with no run function of its own. To be used to calculate the - design and cost of treatment chains. Code largely dereived from code scripts + design and cost of treatment trains in ASP. Code largely dereived from code scripts for [2]_. Parameters @@ -742,7 +742,7 @@ class TreatmentChain(Mixer): N_train : int Number of treatment train, should be at least two in case one failing. V_tank : float - Volume of tank, [m3]. Default is 1000 m3. + Volume of tank (represents one treatment chain), [m3]. Default is 1000 m3. W_tank : float Width of tank, [m]. Default is 6.4 m. [1] D_tank : float @@ -764,16 +764,13 @@ class TreatmentChain(Mixer): Air flow required for one treatment train [m3/hr]. Q_recirculation: float Used to calculate pumping power. - Design recirculated flow in the treatment train [m3/day]. + Design recirculated flow for one treatment train [m3/day]. ** confirm the logic of 'one' ** kwargs : dict Other attributes to be set. References ---------- - .. [1] Rittmann, B.; McCarty, P.; McCarty, P. L.; Bruce, R. - Environmental Biotechnology: Principles and Applications; - McGraw-Hill Companies,Incorporated, 2001. - .. [2] Shoener, B. D.; Zhong, C.; Greiner, A. D.; Khunjar, W. O.; Hong, P.-Y.; Guest, J. S. + .. [1] Shoener, B. D.; Zhong, C.; Greiner, A. D.; Khunjar, W. O.; Hong, P.-Y.; Guest, J. S. Design of Anaerobic Membrane Bioreactors for the Valorization of Dilute Organic Carbon Waste Streams. Energy Environ. Sci. 2016, 9 (3), 1102–1112. @@ -789,10 +786,10 @@ class TreatmentChain(Mixer): # Costs wall_concrete_unit_cost = 1081.73 # $/m3 (Hydromantis. CapdetWorks 4.0. https://www.hydromantis.com/CapdetWorks.html) slab_concrete_unit_cost = 582.48 # $/m3 (Hydromantis. CapdetWorks 4.0. https://www.hydromantis.com/CapdetWorks.html) - excav_unit_cost = (8+0.3) / 0.765 # $/m3, 0.765 is to convert from $/yd3 **NOT UPDATED** (taken from Shoener et al.) - - def __init__(self, ID='', ins=None, outs=None, thermo=None, init_with='WasteStream', - F_BM_default=1, N_train=2, V_tank=1000, + excav_unit_cost = (8 + 0.3) / 0.765 # $/m3, 0.765 is to convert from $/yd3 **NOT UPDATED** (taken from Shoener et al.) + + def __init__(self, ID='', ins=None, outs= (), thermo=None, init_with='WasteStream', + F_BM_default=1, isdynamic=False, N_train=2, V_tank=1000, W_tank = 6.4, D_tank = 3.66, freeboard = 0.61, W_dist = 1.37, W_eff = 1.5, # all in meter (converted from feet to m, Shoener et al.2016) W_recirculation = 1.5, # in m (assumed same as W_eff) excav_slope = 1.5, # horizontal/vertical (Shoener et al.2016) @@ -801,7 +798,8 @@ def __init__(self, ID='', ins=None, outs=None, thermo=None, init_with='WasteStre Q_recirculation = 1000, # in m3/day **NO SOURCE FOR DEFAULT VALUE YET** F_BM=default_F_BM, lifetime=default_equipment_lifetime, **kwargs): - SanUnit.__init__(self, ID, ins, outs, thermo, init_with, F_BM_default=1) + SanUnit.__init__(self, ID, ins, outs, thermo, init_with, + F_BM_default=F_BM_default, isdynamic=isdynamic) self.N_train = N_train self.V_tank = V_tank @@ -958,9 +956,6 @@ def Q_recirculation(self): @Q_recirculation.setter def Q_recirculation(self, i): self._Q_recirculation = i - - def _run(self): - pass def _design_pump(self): @@ -972,6 +967,7 @@ def _design_pump(self): Tank_length = D['Tank length']*meter_to_feet # in ft Q_recirculation_mgd = Q_recirculation*0.000264 #m3/day to MGD + Q_mgd = { 'recirculation': Q_recirculation_mgd, } @@ -1120,8 +1116,6 @@ def _cost(self): # Blower self.add_equipment_cost() - - ### Heat and power ### # Power pumping = 0. From 7f4dee0381592d53200c678febb4943a97c34782 Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Wed, 1 Nov 2023 17:13:48 -0500 Subject: [PATCH 217/424] Debugging changes Edits during debugging --- qsdsan/sanunits/_activated_sludge_process.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/qsdsan/sanunits/_activated_sludge_process.py b/qsdsan/sanunits/_activated_sludge_process.py index 424b4a09..7d39813f 100644 --- a/qsdsan/sanunits/_activated_sludge_process.py +++ b/qsdsan/sanunits/_activated_sludge_process.py @@ -16,7 +16,7 @@ from warnings import warn from math import ceil from biosteam import Stream -from .. import SanUnit +from .. import SanUnit, WasteStream from ..sanunits import HXutility, WWTpump, Mixer from ..equipments import Blower, GasPiping from ..utils import auom, calculate_excavation_volume @@ -787,6 +787,11 @@ class TreatmentTrains(Mixer): wall_concrete_unit_cost = 1081.73 # $/m3 (Hydromantis. CapdetWorks 4.0. https://www.hydromantis.com/CapdetWorks.html) slab_concrete_unit_cost = 582.48 # $/m3 (Hydromantis. CapdetWorks 4.0. https://www.hydromantis.com/CapdetWorks.html) excav_unit_cost = (8 + 0.3) / 0.765 # $/m3, 0.765 is to convert from $/yd3 **NOT UPDATED** (taken from Shoener et al.) + + _t_wall = None + _t_slab = None + + pumps = ('recirculation_CSTR',) def __init__(self, ID='', ins=None, outs= (), thermo=None, init_with='WasteStream', F_BM_default=1, isdynamic=False, N_train=2, V_tank=1000, @@ -796,11 +801,13 @@ def __init__(self, ID='', ins=None, outs= (), thermo=None, init_with='WasteStrea constr_access = 0.92, # in meter (converted from feet to m, Shoener et al. 2016) Q_air_design = 1000, # in m3/hr **NO SOURCE FOR DEFAULT VALUE YET** Q_recirculation = 1000, # in m3/day **NO SOURCE FOR DEFAULT VALUE YET** - F_BM=default_F_BM, lifetime=default_equipment_lifetime, + F_BM=default_F_BM, lifetime=default_equipment_lifetime, rigorous=False, **kwargs): SanUnit.__init__(self, ID, ins, outs, thermo, init_with, F_BM_default=F_BM_default, isdynamic=isdynamic) + self.rigorous = rigorous + self._mixed = WasteStream(f'{ID}_mixed') self.N_train = N_train self.V_tank = V_tank self.W_tank = W_tank @@ -1005,7 +1012,7 @@ def _design_pump(self): _units = { 'Number of trains': 'ea', 'Tank volume': 'm3', - 'HRT': 'd', + 'HRT': 'hr', 'Tank width': 'm', 'Tank depth': 'm', 'Tank length': 'm', @@ -1075,7 +1082,8 @@ def _design(self): Q_air_design = self.Q_air_design # in m3 air_cfm = auom('m3/hr').convert(Q_air_design, 'cfm') blower, piping = self.equipments - blower.N_reactor = piping.N_reactor = D['N_train'] + + blower.N_reactor = piping.N_reactor = D['Number of trains'] blower.gas_demand_per_reactor = piping.gas_demand_per_reactor = air_cfm self.add_equipment_design() From 363c452de15ba6eec0e0eb41a4b94fe50a7862b8 Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Fri, 3 Nov 2023 08:03:28 -0500 Subject: [PATCH 218/424] Edits to debug the pumping cost in Treatment chain --- qsdsan/sanunits/_activated_sludge_process.py | 38 ++++++++++++-------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/qsdsan/sanunits/_activated_sludge_process.py b/qsdsan/sanunits/_activated_sludge_process.py index 7d39813f..3996cd1b 100644 --- a/qsdsan/sanunits/_activated_sludge_process.py +++ b/qsdsan/sanunits/_activated_sludge_process.py @@ -18,6 +18,12 @@ from biosteam import Stream from .. import SanUnit, WasteStream from ..sanunits import HXutility, WWTpump, Mixer + +from ..sanunits._pumping import ( + default_F_BM as default_WWTpump_F_BM, + default_equipment_lifetime as default_WWTpump_equipment_lifetime, + ) + from ..equipments import Blower, GasPiping from ..utils import auom, calculate_excavation_volume __all__ = ('ActivatedSludgeProcess','TreatmentTrains',) @@ -793,7 +799,7 @@ class TreatmentTrains(Mixer): pumps = ('recirculation_CSTR',) - def __init__(self, ID='', ins=None, outs= (), thermo=None, init_with='WasteStream', + def __init__(self, ID='', ins= None, outs= (), thermo=None, init_with='WasteStream', F_BM_default=1, isdynamic=False, N_train=2, V_tank=1000, W_tank = 6.4, D_tank = 3.66, freeboard = 0.61, W_dist = 1.37, W_eff = 1.5, # all in meter (converted from feet to m, Shoener et al.2016) W_recirculation = 1.5, # in m (assumed same as W_eff) @@ -806,6 +812,7 @@ def __init__(self, ID='', ins=None, outs= (), thermo=None, init_with='WasteStrea SanUnit.__init__(self, ID, ins, outs, thermo, init_with, F_BM_default=F_BM_default, isdynamic=isdynamic) + self._effluent = self.outs[0].copy(f'{ID}_effluent') self.rigorous = rigorous self._mixed = WasteStream(f'{ID}_mixed') self.N_train = N_train @@ -969,14 +976,21 @@ def _design_pump(self): ID, pumps = self.ID, self.pumps D = self.design_results - Q_recirculation = D['Q_recirculation'] + self._effluent.copy_like(self.outs[0]) + effluent = self._effluent + + ins_dct = { + 'recirculation_CSTR': effluent, + } + + Q_recirculation = D['Q recirculation'] meter_to_feet = 3.28 Tank_length = D['Tank length']*meter_to_feet # in ft Q_recirculation_mgd = Q_recirculation*0.000264 #m3/day to MGD Q_mgd = { - 'recirculation': Q_recirculation_mgd, + 'recirculation_CSTR': Q_recirculation_mgd, } type_dct = dict.fromkeys(pumps, 'recirculation_CSTR') @@ -990,7 +1004,7 @@ def _design_pump(self): ID = f'{ID}_{i}' capacity_factor=1 pump = WWTpump( - ID=ID, ins= None, pump_type=type_dct[i], + ID=ID, ins= ins_dct[i], pump_type=type_dct[i], Q_mgd=Q_mgd[i], add_inputs=inputs_dct[i], capacity_factor=capacity_factor, include_pump_cost=True, @@ -1019,7 +1033,7 @@ def _design_pump(self): 'Wall concrete': 'm3', 'Slab concrete': 'm3', 'Excavation': 'm3', - 'Q_recirculation': 'm3/day', + 'Q recirculation': 'm3/day', 'Pump pipe stainless steel': 'kg', 'Pump stainless steel': 'kg', } @@ -1076,7 +1090,7 @@ def _design(self): D['Excavation'] = VEX - D['Q_recirculation'] = self.Q_recirculation + D['Q recirculation'] = self.Q_recirculation # Blower and gas piping (taken from 'ActivatedSludgeProcess' SanUnit) Q_air_design = self.Q_air_design # in m3 @@ -1093,18 +1107,14 @@ def _design(self): D['Pump stainless steel'] = pumps def _cost(self): - D = self.design_results, + D = self.design_results C = self.baseline_purchase_costs ### Capital ### # Concrete and excavation - VWC = D['Wall concrete'] - VSC = D['Slab concrete'] - VEX = D['Excavation'] - - C['Wall concrete'] = VWC * self.wall_concrete_unit_cost - C['Slab concrete'] = VSC * self.slab_concrete_unit_cost - C['Reactor excavation'] = VEX * self.excav_unit_cost + C['Wall concrete'] = D['Wall concrete'] * self.wall_concrete_unit_cost + C['Slab concrete'] = D['Slab concrete'] * self.slab_concrete_unit_cost + C['Reactor excavation'] = D['Excavation'] * self.excav_unit_cost # Pump pumps, add_OPEX = self.pumps, self.add_OPEX From a2ca3506e1b21a98abc2c2abb28410f47baf81d5 Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Fri, 3 Nov 2023 08:03:46 -0500 Subject: [PATCH 219/424] Minor edit --- qsdsan/sanunits/_clarifier.py | 1 - 1 file changed, 1 deletion(-) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index 402854dd..8461ce3f 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -1716,7 +1716,6 @@ def _design_pump(self): else: ID = f'{ID}_{i}' capacity_factor=1 - # No. of pumps = No. of influents pump = WWTpump( ID=ID, ins= ins_dct[i], pump_type=type_dct[i], Q_mgd=influent_Q_mgd, add_inputs=inputs_dct[i], From 9a439ce1065c594b70edfc783ba8d65cf6c18328 Mon Sep 17 00:00:00 2001 From: Yalin Date: Sun, 12 Nov 2023 10:08:42 -0500 Subject: [PATCH 220/424] workaround to prevent negatives/better error prompting --- qsdsan/sanunits/_clarifier.py | 13 +++++++------ qsdsan/sanunits/_sludge_treatment.py | 4 ++-- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index 8461ce3f..2bc5c6c4 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -52,7 +52,7 @@ def _settling_flux(X, v_max, v_max_practical, X_min, rh, rp, n0): # v = min(v_max_practical, v_max*(exp(-rh*X_star) - exp(-rp*X_star))) # return X*max(v, 0) -# Asign a bare module of 1 to all +# Assign a bare module of 1 to all default_F_BM = { 'Wall concrete': 1., 'Slab concrete': 1., @@ -1040,12 +1040,13 @@ def _f_i(self): fx = xcod/self._mixed.COD corr = self._corr HRT = self._HRT - n_COD = corr*(2.88*fx - 0.118)*(1.45 + 6.15*np.log(HRT*24*60)) + # n_COD should not be negative + n_COD = max(0, corr*(2.88*fx - 0.118)*(1.45 + 6.15*np.log(HRT*24*60))) f_i = 1 - (n_COD/100) return f_i def _run(self): - uf, of = self.outs + uf, of = self.outs # underflow, overflow cmps = self.components mixed = self._mixed mixed.mix_from(self.ins) @@ -1335,7 +1336,7 @@ def yt(t, QC_ins, dQC_ins): class PrimaryClarifier(SanUnit): """ - Primary clarifier adapted from the design of thickener as defined in BSM-2. [1] + Primary clarifier adapted from the design of thickener as defined in BSM2. [1] ---------- ID : str ID for the Primary Clarifier. The default is ''. @@ -1532,7 +1533,7 @@ def _cal_thickener_factor(self, TSS_in): if thickener_factor<1: thickener_factor=1 return thickener_factor - else: return None + else: raise ValueError(f'Influent TSS is not valid ({TSS_in:.2f} mg/L).') def _cal_parameters(self, thickener_factor): if thickener_factor<1: @@ -1897,7 +1898,7 @@ def _cost(self): base_flow_v_notch_weir = 10 # in m3/hr C['v notch weir'] = D['Number of clarifiers']*base_cost_v_notch_weir*(clarifier_flow/base_flow_v_notch_weir)**0.6 - # Pump (construction and maintainance) + # Pump (construction and maintenance) pumps = self.pumps add_OPEX = self.add_OPEX pump_cost = 0. diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py index 9425d9e0..565e5534 100644 --- a/qsdsan/sanunits/_sludge_treatment.py +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -24,7 +24,7 @@ __all__ = ('Thickener', 'Centrifuge', 'Incinerator') -# Asign a bare module of 1 to all +# Assign a bare module of 1 to all default_F_BM = { 'Wall concrete': 1., 'Slab concrete': 1., @@ -239,7 +239,7 @@ def _cal_thickener_factor(self, TSS_in): if thickener_factor<1: thickener_factor=1 return thickener_factor - else: return None + else: raise ValueError(f'Influent TSS is not valid ({TSS_in:.2f} mg/L).') def _cal_parameters(self, thickener_factor): if thickener_factor<1: From 4a59887f66de6e9d063ee3dbd6b7693147d8b1ee Mon Sep 17 00:00:00 2001 From: Yalin Date: Sun, 12 Nov 2023 22:01:15 -0500 Subject: [PATCH 221/424] very minor typo fix --- qsdsan/_waste_stream.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qsdsan/_waste_stream.py b/qsdsan/_waste_stream.py index eedf9304..3f597fbd 100644 --- a/qsdsan/_waste_stream.py +++ b/qsdsan/_waste_stream.py @@ -633,7 +633,7 @@ def composite(self, variable, flow=False, exclude_gas=True, if specification: try: specified_IDs = set(_get(all_cmps, specification)) - except AttributeError: # no pre-defined groups + except AttributeError: # no predefined groups try: specified_IDs = _specific_groups[specification] except KeyError: # specification not in the default ones @@ -1073,7 +1073,7 @@ def get_VSS(self, include_colloidal=False): return VSS def get_ISS(self): - '''[float] Inorganic/involatile suspended solids, in mg/L.''' + '''[float] Inorganic/non-volatile suspended solids, in mg/L.''' return self.composite('solids', particle_size='x', volatile=False) From 105eb63ec5ea3fc42c45b2b9b26f998d50b65551 Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Tue, 14 Nov 2023 19:41:55 -0600 Subject: [PATCH 222/424] Revert "Merge branch 'main' of https://github.com/QSD-Group/QSDsan into metro" This reverts commit 42507c8fc903cf9d67c78b89e55a852824453820, reversing changes made to a2ca3506e1b21a98abc2c2abb28410f47baf81d5. --- README.rst | 10 +- docs/source/api/sanunits/_index.rst | 1 + .../api/sanunits/encapsulation_bioreactor.rst | 4 + docs/source/conf.py | 14 +- docs/source/images/custom_binder_logo.svg | 1 - docs/source/tutorials/0_Quick_Overview.ipynb | 35 +- docs/source/tutorials/10_Process.ipynb | 1808 +++++++++++++++-- .../tutorials/11_Dynamic_Simulation.ipynb | 1767 ++++++++-------- .../12_Anaerobic_Digestion_Model_No_1.ipynb | 1538 -------------- docs/source/tutorials/12_Chlorination.ipynb | 1046 ++++++++++ docs/source/tutorials/1_Helpful_Basics.ipynb | 39 +- docs/source/tutorials/2_Component.ipynb | 35 +- docs/source/tutorials/3_WasteStream.ipynb | 37 +- docs/source/tutorials/4_SanUnit_basic.ipynb | 35 +- .../source/tutorials/5_SanUnit_advanced.ipynb | 35 +- docs/source/tutorials/6_System.ipynb | 37 +- docs/source/tutorials/7_TEA.ipynb | 35 +- docs/source/tutorials/8_LCA.ipynb | 37 +- ...Uncertainty_and_Sensitivity_Analyses.ipynb | 37 +- docs/source/tutorials/Tutorial_11.ipynb | 401 ++++ docs/source/tutorials/{assets => }/_bkm.tsv | 0 docs/source/tutorials/_index.rst | 1 - docs/source/tutorials/assets/adm1.jpg | Bin 57220 -> 0 bytes legacy_files/binder.yaml | Bin 924 -> 0 bytes qsdsan/_impact_item.py | 2 +- qsdsan/sanunits/_abstract.py | 3 +- requirements.txt | 5 +- setup.py | 2 +- 28 files changed, 3967 insertions(+), 2998 deletions(-) create mode 100644 docs/source/api/sanunits/encapsulation_bioreactor.rst delete mode 100644 docs/source/images/custom_binder_logo.svg delete mode 100644 docs/source/tutorials/12_Anaerobic_Digestion_Model_No_1.ipynb create mode 100644 docs/source/tutorials/12_Chlorination.ipynb create mode 100644 docs/source/tutorials/Tutorial_11.ipynb rename docs/source/tutorials/{assets => }/_bkm.tsv (100%) delete mode 100644 docs/source/tutorials/assets/adm1.jpg delete mode 100644 legacy_files/binder.yaml diff --git a/README.rst b/README.rst index f444b684..17f65f5b 100644 --- a/README.rst +++ b/README.rst @@ -31,8 +31,8 @@ QSDsan: Quantitative Sustainable Design for Sanitation and Resource Recovery Sys :target: https://codecov.io/gh/QSD-Group/QSDsan .. Binder launch of tutorials -.. image:: ./docs/source/images/custom_binder_logo.svg - :target: https://mybinder.org/v2/gh/QSD-Group/QSDsan-env/main?urlpath=git-pull%3Frepo%3Dhttps%253A%252F%252Fgithub.com%252FQSD-group%252FQSDsan%26urlpath%3Dtree%252FQSDsan%252Fdocs%252Fsource%252Ftutorials%26branch%3Dmain +.. image:: https://mybinder.org/badge_logo.svg + :target: https://mybinder.org/v2/gh/QSD-Group/QSDsan/main?filepath=%2Fdocs%2Fsource%2Ftutorials .. Email subscription form .. image:: https://img.shields.io/badge/news-subscribe-F3A93C?style=flat&logo=rss @@ -162,8 +162,4 @@ References .. [2] Li, Y.; Trimmer, J.T.; Hand, S.; Zhang, X.; Chambers, K.G.; Lohman, H.A.C.; Shi, R.; Byrne, D.M.; Cook, S.M.; Guest, J.S. Quantitative Sustainable Design (QSD): A Methodology for the Prioritization of Research, Development, and Deployment of Technologies. (Tutorial Review) Environ. Sci.: Water Res. Technol. 2022, 8 (11), 2439–2465. https://doi.org/10.1039/D2EW00431C. -.. [3] Cortés-Peña, Y.; Kumar, D.; Singh, V.; Guest, J.S. BioSTEAM: A Fast and Flexible Platform for the Design, Simulation, and Techno-Economic Analysis of Biorefineries under Uncertainty. ACS Sustainable Chem. Eng. 2020, 8 (8), 3302–3310. https://doi.org/10.1021/acssuschemeng.9b07040. - - -.. Custom launch badges: https://mybinder.readthedocs.io/en/latest/howto/badges.html -.. binder_badge: https://img.shields.io/badge/launch-binder%20%7C%20tutorial-579ACA.svg?logo=data:image/png;base64,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 +.. [3] Cortés-Peña, Y.; Kumar, D.; Singh, V.; Guest, J.S. BioSTEAM: A Fast and Flexible Platform for the Design, Simulation, and Techno-Economic Analysis of Biorefineries under Uncertainty. ACS Sustainable Chem. Eng. 2020, 8 (8), 3302–3310. https://doi.org/10.1021/acssuschemeng.9b07040. \ No newline at end of file diff --git a/docs/source/api/sanunits/_index.rst b/docs/source/api/sanunits/_index.rst index 72aa4069..1c1eab87 100644 --- a/docs/source/api/sanunits/_index.rst +++ b/docs/source/api/sanunits/_index.rst @@ -37,6 +37,7 @@ Individual Unit Operations CropApplication DynamicInfluent ElectrochemicalCell + encapsulation_bioreactor Excretion Flash heat_exchanging diff --git a/docs/source/api/sanunits/encapsulation_bioreactor.rst b/docs/source/api/sanunits/encapsulation_bioreactor.rst new file mode 100644 index 00000000..bc4d9476 --- /dev/null +++ b/docs/source/api/sanunits/encapsulation_bioreactor.rst @@ -0,0 +1,4 @@ +Encapsulation Bioreactor +======================== +.. automodule:: qsdsan.sanunits._encapsulation_bioreactor + :members: \ No newline at end of file diff --git a/docs/source/conf.py b/docs/source/conf.py index 524df415..2e7bbf8b 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -31,7 +31,7 @@ # built documents. # # The short X.Y version. -version = '1.3.1' +version = '1.3.0' # The full version, including alpha/beta/rc tags. release = version @@ -127,10 +127,10 @@ # -- External mapping ------------------------------------------------------- intersphinx_mapping = { - 'biosteam': ('https://biosteam.readthedocs.io', None), - 'thermosteam': ('https://biosteam.readthedocs.io', None), - 'BioSTEAM': ('https://biosteam.readthedocs.io', None), - 'Thermosteam': ('https://biosteam.readthedocs.io', None), - 'scipy': ('https://docs.scipy.org/doc/scipy', None), - 'SALib': ('https://salib.readthedocs.io', None), + 'biosteam': ('https://biosteam.readthedocs.io/en/latest', None), + 'thermosteam': ('https://biosteam.readthedocs.io/en/latest', None), + 'BioSTEAM': ('https://biosteam.readthedocs.io/en/latest', None), + 'Thermosteam': ('https://biosteam.readthedocs.io/en/latest', None), + 'scipy': ('https://docs.scipy.org/doc/scipy/', None), + 'SALib': ('https://salib.readthedocs.io/en/latest', None), } diff --git a/docs/source/images/custom_binder_logo.svg b/docs/source/images/custom_binder_logo.svg deleted file mode 100644 index 00919718..00000000 --- a/docs/source/images/custom_binder_logo.svg +++ /dev/null @@ -1 +0,0 @@ -launch: binder | tutoriallaunchbinder | tutorial \ No newline at end of file diff --git a/docs/source/tutorials/0_Quick_Overview.ipynb b/docs/source/tutorials/0_Quick_Overview.ipynb index 5f4341ec..ed9e66f4 100644 --- a/docs/source/tutorials/0_Quick_Overview.ipynb +++ b/docs/source/tutorials/0_Quick_Overview.ipynb @@ -9,9 +9,9 @@ "\n", "- **Prepared by:**\n", " \n", - " - [Yalin Li](https://qsdsan.readthedocs.io/en/latest/CONTRIBUTING.html)\n", + " - [Yalin Li](https://qsdsan.readthedocs.io/en/latest/authors/Yalin_Li.html)\n", " \n", - "To run tutorials in your browser, go to this [Binder page](https://mybinder.org/v2/gh/QSD-Group/QSDsan-env/main?urlpath=git-pull%3Frepo%3Dhttps%253A%252F%252Fgithub.com%252FQSD-group%252FQSDsan%26urlpath%3Dtree%252FQSDsan%252Fdocs%252Fsource%252Ftutorials%26branch%3Dmain)." + "To run tutorials in your browser, go to this [Binder page](https://mybinder.org/v2/gh/QSD-Group/QSDsan/main?filepath=%2Fdocs%2Fsource%2Ftutorials)." ] }, { @@ -605,36 +605,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" - }, - "varInspector": { - "cols": { - "lenName": 16, - "lenType": 16, - "lenVar": 40 - }, - "kernels_config": { - "python": { - "delete_cmd_postfix": "", - "delete_cmd_prefix": "del ", - "library": "var_list.py", - "varRefreshCmd": "print(var_dic_list())" - }, - "r": { - "delete_cmd_postfix": ") ", - "delete_cmd_prefix": "rm(", - "library": "var_list.r", - "varRefreshCmd": "cat(var_dic_list()) " - } - }, - "types_to_exclude": [ - "module", - "function", - "builtin_function_or_method", - "instance", - "_Feature" - ], - "window_display": false + "version": "3.8.12" } }, "nbformat": 4, diff --git a/docs/source/tutorials/10_Process.ipynb b/docs/source/tutorials/10_Process.ipynb index 5d1069ff..7cc73a09 100644 --- a/docs/source/tutorials/10_Process.ipynb +++ b/docs/source/tutorials/10_Process.ipynb @@ -9,7 +9,7 @@ "\n", "- **Prepared by:**\n", " \n", - " - [Joy Zhang](https://qsdsan.readthedocs.io/en/latest/CONTRIBUTING.html)\n", + " - [Joy Zhang](https://qsdsan.readthedocs.io/en/latest/authors/Joy_Zhang.html)\n", " \n", "- **Covered topics:**\n", "\n", @@ -20,9 +20,9 @@ " \n", "- **Video demo:**\n", "\n", - " - [Joy Zhang](https://qsdsan.readthedocs.io/en/latest/CONTRIBUTING.html)\n", + " - [Joy Zhang](https://qsdsan.readthedocs.io/en/latest/authors/Joy_Zhang.html)\n", " \n", - "To run tutorials in your browser, go to this [Binder page](https://mybinder.org/v2/gh/QSD-Group/QSDsan-env/main?urlpath=git-pull%3Frepo%3Dhttps%253A%252F%252Fgithub.com%252FQSD-group%252FQSDsan%26urlpath%3Dtree%252FQSDsan%252Fdocs%252Fsource%252Ftutorials%26branch%3Dmain).\n", + "To run tutorials in your browser, go to this [Binder page](https://mybinder.org/v2/gh/QSD-Group/QSDsan/main?filepath=%2Fdocs%2Fsource%2Ftutorials).\n", " \n", "You can also watch a video demo on YouTube ([part 1](https://youtu.be/r9HrfTH9_Tg), [part 2](https://youtu.be/noVSJboqSuc)) (subscriptions & likes appreciated!)." ] @@ -82,7 +82,15 @@ "execution_count": 2, "id": "3dc1138e", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "This tutorial was made with qsdsan v1.2.5.\n" + ] + } + ], "source": [ "import qsdsan as qs\n", "print(f'This tutorial was made with qsdsan v{qs.__version__}.')" @@ -109,10 +117,24 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 4, "id": "61b1bd62", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "Image(url='https://lucid.app/publicSegments/view/2c231fa2-6065-46b9-83af-a790ce38b6c0/image.png', width=600)" ] @@ -136,10 +158,21 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 5, "id": "75766cf7", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "qsdsan._process.Process" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# If you check\n", "qs.Process\n", @@ -159,10 +192,21 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 6, "id": "50db4564", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# If you check\n", "qs.processes" @@ -178,10 +222,37 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 7, "id": "fc717b78", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "('DiffusedAeration',\n", + " 'create_asm1_cmps',\n", + " 'ASM1',\n", + " 'create_asm2d_cmps',\n", + " 'ASM2d',\n", + " 'create_adm1_cmps',\n", + " 'ADM1',\n", + " 'non_compet_inhibit',\n", + " 'substr_inhibit',\n", + " 'T_correction_factor',\n", + " 'pH_inhibit',\n", + " 'Hill_inhibit',\n", + " 'rhos_adm1',\n", + " 'Decay',\n", + " 'KineticReaction',\n", + " 'create_pm2_cmps',\n", + " 'PM2')" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# To see the list of objects that can be directly imported from this folder\n", "qs.processes.__all__" @@ -199,12 +270,39 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 8, "id": "6e6931bb", "metadata": { "scrolled": true }, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "{'__all__',\n", + " '__builtins__',\n", + " '__cached__',\n", + " '__doc__',\n", + " '__file__',\n", + " '__loader__',\n", + " '__name__',\n", + " '__package__',\n", + " '__path__',\n", + " '__spec__',\n", + " '_adm1',\n", + " '_aeration',\n", + " '_asm1',\n", + " '_asm2d',\n", + " '_decay',\n", + " '_kinetic_reaction',\n", + " '_pm2'}" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# You can see other attributes of the `qs.processes` folder with the `dir` function\n", "set(dir(qs.processes)) - set(qs.processes.__all__)" @@ -220,10 +318,21 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 9, "id": "82751cf8", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# For example, `_asm1.py` is a script in the `processes` folder.\n", "qs.processes._asm1" @@ -254,10 +363,18 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 10, "id": "7e4b98a4", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CompiledComponents([S_I, S_S, X_I, X_S, X_BH, X_BA, X_P, S_O, S_NO, S_NH, S_ND, X_ND, S_ALK, S_N2, H2O])\n" + ] + } + ], "source": [ "# Before we get to the subsections, let's get ready by loading ASM1-related objects in qsdsan\n", "from qsdsan.processes import create_asm1_cmps, ASM1\n", @@ -277,10 +394,27 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 11, "id": "2b7ccd5b", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Thermo(\n", + " chemicals=CompiledComponents([S_I, S_S, X_I, X_S, X_BH, X_BA, X_P, S_O, S_NO, S_NH, S_ND, X_ND, S_ALK, S_N2, H2O]),\n", + " mixture=Mixture(\n", + " rule='ideal', ...\n", + " include_excess_energies=False\n", + " ),\n", + " Gamma=DortmundActivityCoefficients,\n", + " Phi=IdealFugacityCoefficients,\n", + " PCF=MockPoyintingCorrectionFactors\n", + ")\n" + ] + } + ], "source": [ "# By default, the thermo is set with this `CompiledComponents` object upon its creation.\n", "# We can verify that by calling the `get_thermo` function\n", @@ -289,10 +423,21 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 12, "id": "948340c9", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "(qsdsan._process.CompiledProcesses,)" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# Next we need to create an instance of the ASM1 model\n", "# We can see that `ASM1` is a subclass of `CompiledProcesses`, so it can be used for demonstration\n", @@ -302,7 +447,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 13, "id": "7357bed8", "metadata": { "scrolled": true @@ -315,12 +460,20 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 14, "id": "bdf27943", "metadata": { "scrolled": true }, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ASM1([aero_growth_hetero, anox_growth_hetero, aero_growth_auto, decay_hetero, decay_auto, ammonification, hydrolysis, hydrolysis_N])\n" + ] + } + ], "source": [ "# Without getting into the details of ASM1, we will leave all parameters at their default values \n", "asm1 = ASM1()\n", @@ -350,10 +503,43 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 15, "id": "25a826a8", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Process: aero_growth_hetero\n", + "[stoichiometry] S_S: -1.0/Y_H\n", + " X_BH: 1.00\n", + " S_O: 1.0*(Y_H - 1.0)/Y_H\n", + " S_NH: -0.0800\n", + " S_ALK: -0.0686\n", + "[reference] X_BH\n", + "[rate equation] S_NH*S_O*S_S*X_BH*mu_H/((K_N...\n", + "[parameters] Y_H: 0.67\n", + " Y_A: 0.24\n", + " f_P: 0.08\n", + " mu_H: 4\n", + " K_S: 10\n", + " K_O_H: 0.2\n", + " K_NO: 0.5\n", + " b_H: 0.3\n", + " mu_A: 0.5\n", + " K_NH: 1\n", + " K_O_A: 0.4\n", + " b_A: 0.05\n", + " eta_g: 0.8\n", + " k_a: 0.05\n", + " k_h: 3\n", + " K_X: 0.1\n", + " eta_h: 0.8\n", + "[dynamic parameters] \n" + ] + } + ], "source": [ "# Let's take the 0th process in `asm1` as an example to learn about `Process`:\n", "# p1 = asm1.tuple[0]\n", @@ -387,10 +573,25 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 16, "id": "baea578a", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "{'S_S': -1.49253731343284,\n", + " 'X_BH': 1.00000000000000,\n", + " 'S_O': -0.492537313432836,\n", + " 'S_NH': -0.0800000000000000,\n", + " 'S_ALK': -0.0685997415522571}" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# For example, we can retrieve information on the stoichiometry of this process\n", "p1.stoichiometry" @@ -410,10 +611,21 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 17, "id": "acb5b8e0", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "'X_BH'" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# This information can also be accessed by calling the `ref_component` property.\n", "p1.ref_component" @@ -421,12 +633,26 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 18, "id": "d0612896", "metadata": { "scrolled": true }, - "outputs": [], + "outputs": [ + { + "data": { + "text/latex": [ + "$\\displaystyle \\frac{4.0 S_{NH} S_{O} S_{S} X_{BH}}{\\left(S_{NH} + 1.0\\right) \\left(S_{O} + 0.2\\right) \\left(S_{S} + 10.0\\right)}$" + ], + "text/plain": [ + "4.0*S_NH*S_O*S_S*X_BH/((S_NH + 1.0)*(S_O + 0.2)*(S_S + 10.0))" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# Another defining characteristics of a process is its rate equation, which is stored as a\n", "# property of the `Process` object\n", @@ -445,10 +671,24 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 19, "id": "0ce7b4df", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/latex": [ + "$\\displaystyle \\frac{S_{NH} S_{O} S_{S} X_{BH} \\mu_{H}}{\\left(K_{NH} + S_{NH}\\right) \\left(K_{O H} + S_{O}\\right) \\left(K_{S} + S_{S}\\right)}$" + ], + "text/plain": [ + "S_NH*S_O*S_S*X_BH*mu_H/((K_NH + S_NH)*(K_O_H + S_O)*(K_S + S_S))" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# If we access the private attribute\n", "p1._rate_equation" @@ -464,7 +704,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 20, "id": "50b77ac8", "metadata": {}, "outputs": [], @@ -482,10 +722,21 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 21, "id": "0142f901", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "array([1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.])" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# Now that we've understood the required input to the rate equation, we can try evaluating the\n", "# process rate. Let's try with all component concentrations equal to 1.\n", @@ -504,10 +755,21 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 22, "id": "aceafb27", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "0.15151515151515152" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# Note that the `rate_equation` attribute only stores the formula.\n", "# The evaluation of process rate is done through the `rate_function` attribute, which is rendered\n", @@ -529,10 +791,37 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 23, "id": "62dc9537", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "{'Y_H': 0.67,\n", + " 'Y_A': 0.24,\n", + " 'f_P': 0.08,\n", + " 'mu_H': 4.0,\n", + " 'K_S': 10.0,\n", + " 'K_O_H': 0.2,\n", + " 'K_NO': 0.5,\n", + " 'b_H': 0.3,\n", + " 'mu_A': 0.5,\n", + " 'K_NH': 1.0,\n", + " 'K_O_A': 0.4,\n", + " 'b_A': 0.05,\n", + " 'eta_g': 0.8,\n", + " 'k_a': 0.05,\n", + " 'k_h': 3.0,\n", + " 'K_X': 0.1,\n", + " 'eta_h': 0.8}" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# For `asm1` and the individual processes within `asm1`, parameters are stored as a dictionary\n", "p1.parameters\n", @@ -550,10 +839,37 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 24, "id": "0ab063bf", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "{'Y_H': 0.8,\n", + " 'Y_A': 0.24,\n", + " 'f_P': 0.08,\n", + " 'mu_H': 6.0,\n", + " 'K_S': 8.0,\n", + " 'K_O_H': 0.2,\n", + " 'K_NO': 0.5,\n", + " 'b_H': 0.3,\n", + " 'mu_A': 0.5,\n", + " 'K_NH': 1.0,\n", + " 'K_O_A': 0.4,\n", + " 'b_A': 0.05,\n", + " 'eta_g': 0.8,\n", + " 'k_a': 0.05,\n", + " 'k_h': 3.0,\n", + " 'K_X': 0.1,\n", + " 'eta_h': 0.8}" + ] + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# If you update parameter values for `p1`, the same parameters will be updated accordingly for\n", "# `asm1` and any other processes in `asm1`.\n", @@ -563,10 +879,21 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 25, "id": "c69a557c", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "0.2777777777777778" + ] + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# If you evaluate process rate or stoichiometry again with the same input, \n", "# you should now expect different output.\n", @@ -584,10 +911,21 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 26, "id": "b7115278", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "('COD', 'charge', 'N')" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "p1.conserved_for" ] @@ -602,10 +940,20 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 27, "id": "772e7e78", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "C:\\Users\\joy_c\\anaconda3\\envs\\tut\\lib\\site-packages\\qsdsan\\_process.py:499: UserWarning: The following materials aren't strictly conserved by the stoichiometric coefficients. A positive value means the material is created, a negative value means the material is destroyed:\n", + " charge: -5.20417042793042E-18\n", + " warn(\"The following materials aren't strictly conserved by the \"\n" + ] + } + ], "source": [ "# No return indicates that all materials in `conserved_for` are conserved.\n", "# Otherwise, a warning or a `RuntimeError` will be raised.\n", @@ -635,10 +983,21 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 28, "id": "b5bde6b2", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "(qsdsan._process.Processes,)" + ] + }, + "execution_count": 28, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "qs.CompiledProcesses.__bases__" ] @@ -653,10 +1012,21 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 29, "id": "3ee5e0dd", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 29, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# Let's verify that\n", "isinstance(asm1, qs.CompiledProcesses)" @@ -672,10 +1042,18 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 30, "id": "9f6ae9b6", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Processes([aero_growth_hetero, anox_growth_hetero, aero_growth_auto, decay_hetero, decay_auto, ammonification, hydrolysis, hydrolysis_N])\n" + ] + } + ], "source": [ "asm1 = qs.Processes(asm1.tuple)\n", "asm1.show()" @@ -683,12 +1061,30 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 31, "id": "3b1e8036", "metadata": { "scrolled": true }, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "{'aero_growth_hetero': ,\n", + " 'anox_growth_hetero': ,\n", + " 'aero_growth_auto': ,\n", + " 'decay_hetero': ,\n", + " 'decay_auto': ,\n", + " 'ammonification': ,\n", + " 'hydrolysis': ,\n", + " 'hydrolysis_N': }" + ] + }, + "execution_count": 31, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# Let's see what difference \"decompiling\" made\n", "asm1.__dict__" @@ -704,12 +1100,191 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": 32, "id": "48443f1d", "metadata": { "scrolled": true }, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "{'aero_growth_hetero': ,\n", + " 'anox_growth_hetero': ,\n", + " 'aero_growth_auto': ,\n", + " 'decay_hetero': ,\n", + " 'decay_auto': ,\n", + " 'ammonification': ,\n", + " 'hydrolysis': ,\n", + " 'hydrolysis_N': ,\n", + " 'tuple': (,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ),\n", + " 'size': 8,\n", + " 'IDs': ('aero_growth_hetero',\n", + " 'anox_growth_hetero',\n", + " 'aero_growth_auto',\n", + " 'decay_hetero',\n", + " 'decay_auto',\n", + " 'ammonification',\n", + " 'hydrolysis',\n", + " 'hydrolysis_N'),\n", + " '_index': {'aero_growth_hetero': 0,\n", + " 'anox_growth_hetero': 1,\n", + " 'aero_growth_auto': 2,\n", + " 'decay_hetero': 3,\n", + " 'decay_auto': 4,\n", + " 'ammonification': 5,\n", + " 'hydrolysis': 6,\n", + " 'hydrolysis_N': 7},\n", + " '_components': CompiledComponents([S_I, S_S, X_I, X_S, X_BH, X_BA, X_P, S_O, S_NO, S_NH, S_ND, X_ND, S_ALK, S_N2, H2O]),\n", + " '_parameters': {'Y_H': 0.8,\n", + " 'Y_A': 0.24,\n", + " 'f_P': 0.08,\n", + " 'mu_H': 6.0,\n", + " 'K_S': 8.0,\n", + " 'K_O_H': 0.2,\n", + " 'K_NO': 0.5,\n", + " 'b_H': 0.3,\n", + " 'mu_A': 0.5,\n", + " 'K_NH': 1.0,\n", + " 'K_O_A': 0.4,\n", + " 'b_A': 0.05,\n", + " 'eta_g': 0.8,\n", + " 'k_a': 0.05,\n", + " 'k_h': 3.0,\n", + " 'K_X': 0.1,\n", + " 'eta_h': 0.8},\n", + " '_dyn_params': {},\n", + " '_stoichiometry': [[0,\n", + " -1.25000000000000,\n", + " 0,\n", + " 0,\n", + " 1.00000000000000,\n", + " 0,\n", + " 0,\n", + " -0.250000000000000,\n", + " 0,\n", + " -0.0800000000000000,\n", + " 0,\n", + " 0,\n", + " -0.0685997415522571,\n", + " 0,\n", + " 0],\n", + " [0,\n", + " -1.25000000000000,\n", + " 0,\n", + " 0,\n", + " 1.00000000000000,\n", + " 0,\n", + " 0,\n", + " 0,\n", + " -0.0875000000000000,\n", + " -0.0800000000000001,\n", + " 0,\n", + " 0,\n", + " 0.00643122577052393,\n", + " 0.0875000000000000,\n", + " 0],\n", + " [0,\n", + " 0,\n", + " 0,\n", + " 0,\n", + " 0,\n", + " 1.00000000000000,\n", + " 0,\n", + " -18.0476190476190,\n", + " 4.16666666666667,\n", + " -4.24666666666667,\n", + " 0,\n", + " 0,\n", + " -7.21440615324570,\n", + " 0,\n", + " 0],\n", + " [0,\n", + " 0,\n", + " 0,\n", + " 0.920000000000000,\n", + " -1.00000000000000,\n", + " 0,\n", + " 0.0800000000000000,\n", + " 0,\n", + " 0,\n", + " 0,\n", + " 0,\n", + " 0.0752000000000000,\n", + " 0,\n", + " 0,\n", + " 0],\n", + " [0,\n", + " 0,\n", + " 0,\n", + " 0.920000000000000,\n", + " 0,\n", + " -1.00000000000000,\n", + " 0.0800000000000000,\n", + " 0,\n", + " 0,\n", + " 0,\n", + " 0,\n", + " 0.0752000000000000,\n", + " 0,\n", + " 0,\n", + " 0],\n", + " [0,\n", + " 0,\n", + " 0,\n", + " 0,\n", + " 0,\n", + " 0,\n", + " 0,\n", + " 0,\n", + " 0,\n", + " 1.00000000000000,\n", + " -1.00000000000000,\n", + " 0,\n", + " 0.857496769403214,\n", + " 0,\n", + " 0],\n", + " [0, 1.0, 0, -1.0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n", + " [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.0, -1.0, 0, 0, 0]],\n", + " '_stoichio_lambdified': None,\n", + " '_rate_equations': (S_NH*S_O*S_S*X_BH*mu_H/((K_NH + S_NH)*(K_O_H + S_O)*(K_S + S_S)),\n", + " K_O_H*S_NH*S_NO*S_S*X_BH*eta_g*mu_H/((K_NH + S_NH)*(K_NO + S_NO)*(K_O_H + S_O)*(K_S + S_S)),\n", + " S_NH*S_O*X_BA*mu_A/((K_NH + S_NH)*(K_O_A + S_O)),\n", + " X_BH*b_H,\n", + " X_BA*b_A,\n", + " S_ND*X_BH*k_a,\n", + " X_BH*X_S*k_h*(K_O_H*S_NO*eta_h/((K_NO + S_NO)*(K_O_H + S_O)) + S_O/(K_O_H + S_O))/(K_X*X_BH + X_S),\n", + " X_BH*X_ND*k_h*(K_O_H*S_NO*eta_h/((K_NO + S_NO)*(K_O_H + S_O)) + S_O/(K_O_H + S_O))/(K_X*X_BH + X_S)),\n", + " '_production_rates': [0,\n", + " -1.25*K_O_H*S_NH*S_NO*S_S*X_BH*eta_g*mu_H/((K_NH + S_NH)*(K_NO + S_NO)*(K_O_H + S_O)*(K_S + S_S)) - 1.25*S_NH*S_O*S_S*X_BH*mu_H/((K_NH + S_NH)*(K_O_H + S_O)*(K_S + S_S)) + 1.0*X_BH*X_S*k_h*(K_O_H*S_NO*eta_h/((K_NO + S_NO)*(K_O_H + S_O)) + S_O/(K_O_H + S_O))/(K_X*X_BH + X_S),\n", + " 0,\n", + " 0.92*X_BA*b_A - 1.0*X_BH*X_S*k_h*(K_O_H*S_NO*eta_h/((K_NO + S_NO)*(K_O_H + S_O)) + S_O/(K_O_H + S_O))/(K_X*X_BH + X_S) + 0.92*X_BH*b_H,\n", + " 1.0*K_O_H*S_NH*S_NO*S_S*X_BH*eta_g*mu_H/((K_NH + S_NH)*(K_NO + S_NO)*(K_O_H + S_O)*(K_S + S_S)) + 1.0*S_NH*S_O*S_S*X_BH*mu_H/((K_NH + S_NH)*(K_O_H + S_O)*(K_S + S_S)) - 1.0*X_BH*b_H,\n", + " 1.0*S_NH*S_O*X_BA*mu_A/((K_NH + S_NH)*(K_O_A + S_O)) - 1.0*X_BA*b_A,\n", + " 0.08*X_BA*b_A + 0.08*X_BH*b_H,\n", + " -0.25*S_NH*S_O*S_S*X_BH*mu_H/((K_NH + S_NH)*(K_O_H + S_O)*(K_S + S_S)) - 18.047619047619*S_NH*S_O*X_BA*mu_A/((K_NH + S_NH)*(K_O_A + S_O)),\n", + " -0.0875*K_O_H*S_NH*S_NO*S_S*X_BH*eta_g*mu_H/((K_NH + S_NH)*(K_NO + S_NO)*(K_O_H + S_O)*(K_S + S_S)) + 4.16666666666667*S_NH*S_O*X_BA*mu_A/((K_NH + S_NH)*(K_O_A + S_O)),\n", + " -0.0800000000000001*K_O_H*S_NH*S_NO*S_S*X_BH*eta_g*mu_H/((K_NH + S_NH)*(K_NO + S_NO)*(K_O_H + S_O)*(K_S + S_S)) + 1.0*S_ND*X_BH*k_a - 0.08*S_NH*S_O*S_S*X_BH*mu_H/((K_NH + S_NH)*(K_O_H + S_O)*(K_S + S_S)) - 4.24666666666667*S_NH*S_O*X_BA*mu_A/((K_NH + S_NH)*(K_O_A + S_O)),\n", + " -1.0*S_ND*X_BH*k_a + 1.0*X_BH*X_ND*k_h*(K_O_H*S_NO*eta_h/((K_NO + S_NO)*(K_O_H + S_O)) + S_O/(K_O_H + S_O))/(K_X*X_BH + X_S),\n", + " 0.0752*X_BA*b_A - 1.0*X_BH*X_ND*k_h*(K_O_H*S_NO*eta_h/((K_NO + S_NO)*(K_O_H + S_O)) + S_O/(K_O_H + S_O))/(K_X*X_BH + X_S) + 0.0752*X_BH*b_H,\n", + " 0.00643122577052393*K_O_H*S_NH*S_NO*S_S*X_BH*eta_g*mu_H/((K_NH + S_NH)*(K_NO + S_NO)*(K_O_H + S_O)*(K_S + S_S)) + 0.857496769403214*S_ND*X_BH*k_a - 0.0685997415522571*S_NH*S_O*S_S*X_BH*mu_H/((K_NH + S_NH)*(K_O_H + S_O)*(K_S + S_S)) - 7.2144061532457*S_NH*S_O*X_BA*mu_A/((K_NH + S_NH)*(K_O_A + S_O)),\n", + " 0.0875*K_O_H*S_NH*S_NO*S_S*X_BH*eta_g*mu_H/((K_NH + S_NH)*(K_NO + S_NO)*(K_O_H + S_O)*(K_S + S_S)),\n", + " 0],\n", + " '_rate_function': None}" + ] + }, + "execution_count": 32, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "asm1.compile()\n", "asm1.__dict__" @@ -725,10 +1300,181 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": 33, "id": "027cbd6b", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
S_IS_SX_IX_SX_BH...S_NDX_NDS_ALKS_N2H2O
aero_growth_hetero0-1.25001...00-0.068600
anox_growth_hetero0-1.25001...000.006430.08750
aero_growth_auto00000...00-7.2100
decay_hetero0000.92-1...00.0752000
decay_auto0000.920...00.0752000
ammonification00000...-100.85700
hydrolysis010-10...00000
hydrolysis_N00000...1-1000
\n", + "

8 rows × 15 columns

\n", + "
" + ], + "text/plain": [ + " S_I S_S X_I X_S X_BH ... S_ND X_ND S_ALK S_N2 H2O\n", + "aero_growth_hetero 0 -1.25 0 0 1 ... 0 0 -0.0686 0 0\n", + "anox_growth_hetero 0 -1.25 0 0 1 ... 0 0 0.00643 0.0875 0\n", + "aero_growth_auto 0 0 0 0 0 ... 0 0 -7.21 0 0\n", + "decay_hetero 0 0 0 0.92 -1 ... 0 0.0752 0 0 0\n", + "decay_auto 0 0 0 0.92 0 ... 0 0.0752 0 0 0\n", + "ammonification 0 0 0 0 0 ... -1 0 0.857 0 0\n", + "hydrolysis 0 1 0 -1 0 ... 0 0 0 0 0\n", + "hydrolysis_N 0 0 0 0 0 ... 1 -1 0 0 0\n", + "\n", + "[8 rows x 15 columns]" + ] + }, + "execution_count": 33, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# For example, the stoichiometric coefficients of all processes are compiled into a table that\n", "# is in consistent format as a Petersen matrix\n", @@ -737,10 +1483,88 @@ }, { "cell_type": "code", - "execution_count": 33, + "execution_count": 34, "id": "18541f64", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
rate_equation
aero_growth_hetero6.0*S_NH*S_O*S_S*X_BH/((S_NH + ...
anox_growth_hetero0.96*S_NH*S_NO*S_S*X_BH/((S_NH ...
aero_growth_auto0.5*S_NH*S_O*X_BA/((S_NH + 1.0)...
decay_hetero0.3*X_BH
decay_auto0.05*X_BA
ammonification0.05*S_ND*X_BH
hydrolysis3.0*X_BH*X_S*(0.16*S_NO/((S_NO ...
hydrolysis_N3.0*X_BH*X_ND*(0.16*S_NO/((S_NO...
\n", + "
" + ], + "text/plain": [ + " rate_equation\n", + "aero_growth_hetero 6.0*S_NH*S_O*S_S*X_BH/((S_NH + ...\n", + "anox_growth_hetero 0.96*S_NH*S_NO*S_S*X_BH/((S_NH ...\n", + "aero_growth_auto 0.5*S_NH*S_O*X_BA/((S_NH + 1.0)...\n", + "decay_hetero 0.3*X_BH\n", + "decay_auto 0.05*X_BA\n", + "ammonification 0.05*S_ND*X_BH\n", + "hydrolysis 3.0*X_BH*X_S*(0.16*S_NO/((S_NO ...\n", + "hydrolysis_N 3.0*X_BH*X_ND*(0.16*S_NO/((S_NO..." + ] + }, + "execution_count": 34, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# Similarly for the rate equations\n", "asm1.rate_equations" @@ -748,10 +1572,21 @@ }, { "cell_type": "code", - "execution_count": 34, + "execution_count": 35, "id": "a872533e", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "array([0.278, 0.03 , 0.179, 0.3 , 0.05 , 0.05 , 2.515, 2.515])" + ] + }, + "execution_count": 35, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# More importantly, the `rate_function` attribute of a `CompiledProcesses` now outputs an array\n", "# with each element corresponding orderly to the individual processes\n", @@ -778,10 +1613,123 @@ }, { "cell_type": "code", - "execution_count": 35, + "execution_count": 36, "id": "81541f71", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
rate_of_production
S_I0
S_S-1.2*S_NH*S_NO*S_S*X_BH/((S_NH ...
X_I0
X_S0.046*X_BA - 3.0*X_BH*X_S*(0.16...
X_BH0.96*S_NH*S_NO*S_S*X_BH/((S_NH ...
X_BA0.5*S_NH*S_O*X_BA/((S_NH + 1.0)...
X_P0.004*X_BA + 0.024*X_BH
S_O-1.5*S_NH*S_O*S_S*X_BH/((S_NH +...
S_NO-0.084*S_NH*S_NO*S_S*X_BH/((S_N...
S_NH0.05*S_ND*X_BH - 0.076800000000...
S_ND-0.05*S_ND*X_BH + 3.0*X_BH*X_ND...
X_ND0.00376*X_BA - 3.0*X_BH*X_ND*(0...
S_ALK0.0428748384701607*S_ND*X_BH + ...
S_N20.084*S_NH*S_NO*S_S*X_BH/((S_NH...
H2O0
\n", + "
" + ], + "text/plain": [ + " rate_of_production\n", + "S_I 0\n", + "S_S -1.2*S_NH*S_NO*S_S*X_BH/((S_NH ...\n", + "X_I 0\n", + "X_S 0.046*X_BA - 3.0*X_BH*X_S*(0.16...\n", + "X_BH 0.96*S_NH*S_NO*S_S*X_BH/((S_NH ...\n", + "X_BA 0.5*S_NH*S_O*X_BA/((S_NH + 1.0)...\n", + "X_P 0.004*X_BA + 0.024*X_BH\n", + "S_O -1.5*S_NH*S_O*S_S*X_BH/((S_NH +...\n", + "S_NO -0.084*S_NH*S_NO*S_S*X_BH/((S_N...\n", + "S_NH 0.05*S_ND*X_BH - 0.076800000000...\n", + "S_ND -0.05*S_ND*X_BH + 3.0*X_BH*X_ND...\n", + "X_ND 0.00376*X_BA - 3.0*X_BH*X_ND*(0...\n", + "S_ALK 0.0428748384701607*S_ND*X_BH + ...\n", + "S_N2 0.084*S_NH*S_NO*S_S*X_BH/((S_NH...\n", + "H2O 0" + ] + }, + "execution_count": 36, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# This matrix operation is already streamlined for `CompiledProcesses` objects, you can see the \n", "# mathematical form of the rates of production as a function of component concentrations.\n", @@ -790,10 +1738,23 @@ }, { "cell_type": "code", - "execution_count": 36, + "execution_count": 37, "id": "b508e20f", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "array([ 0.000e+00, 2.131e+00, 0.000e+00, -2.193e+00, 7.407e-03,\n", + " 1.286e-01, 2.800e-02, -3.292e+00, 7.415e-01, -7.329e-01,\n", + " 2.465e+00, -2.489e+00, -1.264e+00, 2.593e-03, 0.000e+00])" + ] + }, + "execution_count": 37, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# To evaluate the rates of production for all components, all you need to\n", "# do is to call the `production_rates_eval` method.\n", @@ -868,10 +1829,18 @@ }, { "cell_type": "code", - "execution_count": 37, + "execution_count": 45, "id": "cad5a69b", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CompiledComponents([X_S, S_S, O2, CO2, X_B, H2O])\n" + ] + } + ], "source": [ "# Load the default set of components\n", "cmps_all = qs.Components.load_default()\n", @@ -899,10 +1868,21 @@ }, { "cell_type": "code", - "execution_count": 38, + "execution_count": 39, "id": "494eb3e8", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 39, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# Now we can check if their `measured_as` attributes are correctly set\n", "# X_S.measured_as == 'COD'\n", @@ -912,10 +1892,21 @@ }, { "cell_type": "code", - "execution_count": 39, + "execution_count": 40, "id": "2de7d166", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "-1.0" + ] + }, + "execution_count": 40, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# Then you can check relevant `i_` properties of the components\n", "# For example, O2 should have a negative COD content, or more specifically -1 gCOD/gO2\n", @@ -936,7 +1927,7 @@ }, { "cell_type": "code", - "execution_count": 40, + "execution_count": 47, "id": "7b264f76", "metadata": {}, "outputs": [], @@ -966,7 +1957,7 @@ }, { "cell_type": "code", - "execution_count": 41, + "execution_count": 48, "id": "1906bcd6", "metadata": {}, "outputs": [], @@ -985,7 +1976,7 @@ }, { "cell_type": "code", - "execution_count": 42, + "execution_count": 49, "id": "7b7437a4", "metadata": {}, "outputs": [], @@ -1014,7 +2005,7 @@ }, { "cell_type": "code", - "execution_count": 43, + "execution_count": 50, "id": "4f98d569", "metadata": {}, "outputs": [], @@ -1037,7 +2028,7 @@ }, { "cell_type": "code", - "execution_count": 44, + "execution_count": 51, "id": "c7ce74d2", "metadata": {}, "outputs": [], @@ -1058,10 +2049,24 @@ }, { "cell_type": "code", - "execution_count": 45, + "execution_count": 52, "id": "e7facea8", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Process: hydrolysis\n", + "[stoichiometry] X_S: -1\n", + " S_S: 1\n", + "[reference] X_S\n", + "[rate equation] X_S*k_hyd\n", + "[parameters] k_hyd: k_hyd\n", + "[dynamic parameters] \n" + ] + } + ], "source": [ "# Upon initiation, the parameters are stored as symbols. We still need to set values to them\n", "# before we can evalute process rate.\n", @@ -1070,10 +2075,28 @@ }, { "cell_type": "code", - "execution_count": 46, + "execution_count": 53, "id": "9d199cf0", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Process: growth\n", + "[stoichiometry] S_S: -1/y_B\n", + " O2: (y_B - 1.0)/y_B\n", + " CO2: 0.002*(160.0 - 183.0*y_B)/y_B\n", + " X_B: 1.00\n", + "[reference] X_B\n", + "[rate equation] S_S*X_B*mu_B/(K_S + S_S)\n", + "[parameters] y_B: y_B\n", + " mu_B: mu_B\n", + " K_S: K_S\n", + "[dynamic parameters] \n" + ] + } + ], "source": [ "# At this point, the initiation of process 3 should be quite straightforward.\n", "# Here shows an alternative way to input stoichiometry\n", @@ -1114,12 +2137,20 @@ }, { "cell_type": "code", - "execution_count": 47, + "execution_count": 54, "id": "18d7995b", "metadata": { "scrolled": true }, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CompiledProcesses([hydrolysis, growth, decay])\n" + ] + } + ], "source": [ "# Now the final step is to compile the individual processes into a biokinetic model\n", "bkm = qs.Processes([pc1, pc2, pc3])\n", @@ -1129,10 +2160,21 @@ }, { "cell_type": "code", - "execution_count": 48, + "execution_count": 55, "id": "0a4159a3", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "{'k_hyd': k_hyd, 'y_B': y_B, 'mu_B': mu_B, 'K_S': K_S, 'b': b}" + ] + }, + "execution_count": 55, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# Parameters in the stoichiometry and rate equations across all processes are compiled into\n", "# a shared dictionary.\n", @@ -1143,10 +2185,83 @@ }, { "cell_type": "code", - "execution_count": 49, + "execution_count": 56, "id": "6bec6d90", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
X_SS_SO2CO2X_BH2O
hydrolysis-110000
growth0-1.25-0.250.03410
decay1000-10
\n", + "
" + ], + "text/plain": [ + " X_S S_S O2 CO2 X_B H2O\n", + "hydrolysis -1 1 0 0 0 0\n", + "growth 0 -1.25 -0.25 0.034 1 0\n", + "decay 1 0 0 0 -1 0" + ] + }, + "execution_count": 56, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# After setting parameter values, the model will be ready\n", "bkm.set_parameters(k_hyd=3.0, y_B=0.8, mu_B=4.0, K_S=9.0, b=0.4)\n", @@ -1155,10 +2270,78 @@ }, { "cell_type": "code", - "execution_count": 50, + "execution_count": 57, "id": "f13307e3", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
rate_of_production
X_S0.4*X_B - 3.0*X_S
S_S-5.0*S_S*X_B/(S_S + 9.0) + 3.0*X_S
O2-1.0*S_S*X_B/(S_S + 9.0)
CO20.136*S_S*X_B/(S_S + 9.0)
X_B4.0*S_S*X_B/(S_S + 9.0) - 0.4*X_B
H2O0
\n", + "
" + ], + "text/plain": [ + " rate_of_production\n", + "X_S 0.4*X_B - 3.0*X_S\n", + "S_S -5.0*S_S*X_B/(S_S + 9.0) + 3.0*X_S\n", + "O2 -1.0*S_S*X_B/(S_S + 9.0)\n", + "CO2 0.136*S_S*X_B/(S_S + 9.0)\n", + "X_B 4.0*S_S*X_B/(S_S + 9.0) - 0.4*X_B\n", + "H2O 0" + ] + }, + "execution_count": 57, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "bkm.production_rates" ] @@ -1182,14 +2365,91 @@ }, { "cell_type": "code", - "execution_count": 51, + "execution_count": 58, "id": "1b8ebfca", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
X_SS_SO2CO2X_BH2OUnnamed: 7
hydrolysis-110000k_hyd*X_S
growth0(-1)/y_B??10mu_B*S_S/(K_S + S_S)*X_B
decay1000-10b*X_B
\n", + "
" + ], + "text/plain": [ + " X_S S_S O2 CO2 X_B H2O Unnamed: 7\n", + "hydrolysis -1 1 0 0 0 0 k_hyd*X_S\n", + "growth 0 (-1)/y_B ? ? 1 0 mu_B*S_S/(K_S + S_S)*X_B\n", + "decay 1 0 0 0 -1 0 b*X_B" + ] + }, + "execution_count": 58, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# Stoichiometry and rate equations are usually described in a table format\n", "from qsdsan.utils import load_data\n", - "df_bkm = load_data('assets/_bkm.tsv', index_col=0)\n", + "df_bkm = load_data('_bkm.tsv', index_col=0)\n", "df_bkm" ] }, @@ -1203,14 +2463,22 @@ }, { "cell_type": "code", - "execution_count": 52, + "execution_count": 59, "id": "8703f7bf", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CompiledProcesses([hydrolysis, growth, decay])\n" + ] + } + ], "source": [ "# The same amount of information is still required to create the model.\n", "bkm_batch = qs.Processes.load_from_file(\n", - " path='assets/_bkm.tsv', \n", + " path='_bkm.tsv', \n", " conserved_for=('COD', 'C'),\n", " parameters=('k_hyd', 'y_B', 'mu_B', 'K_S', 'b'),\n", " compile=True\n", @@ -1220,10 +2488,83 @@ }, { "cell_type": "code", - "execution_count": 53, + "execution_count": 60, "id": "4105ea73", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
X_SS_SO2CO2X_BH2O
hydrolysis-110000
growth0-1.0/y_B1.0*(y_B - 1.0)/y_B0.002*(160.0 - 183.0*y_B)/y_B1.000000000000000
decay1000-10
\n", + "
" + ], + "text/plain": [ + " X_S S_S O2 CO2 X_B H2O\n", + "hydrolysis -1 1 0 0 0 0\n", + "growth 0 -1.0/y_B 1.0*(y_B - 1.0)/y_B 0.002*(160.0 - 183.0*y_B)/y_B 1.00000000000000 0\n", + "decay 1 0 0 0 -1 0" + ] + }, + "execution_count": 60, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# You can see `bkm_batch` is equivalent to the `bkm` we created above\n", "bkm_batch.stoichiometry\n", @@ -1233,10 +2574,21 @@ }, { "cell_type": "code", - "execution_count": 54, + "execution_count": 61, "id": "ba36c5ed", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "'X_S'" + ] + }, + "execution_count": 61, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# The reference component of each process is inferred from its stoichiometry\n", "bkm_batch.decay.ref_component" @@ -1244,14 +2596,26 @@ }, { "cell_type": "code", - "execution_count": 55, + "execution_count": 62, "id": "57c80cc6", "metadata": {}, - "outputs": [], + "outputs": [ + { + "ename": "RuntimeError", + "evalue": "The following materials are unconserved by the stoichiometric coefficients. A positive value means the material is created, a negative value means the material is destroyed:\n C: -0.05", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[1;31mRuntimeError\u001b[0m Traceback (most recent call last)", + "\u001b[1;32m~\\AppData\\Local\\Temp\\ipykernel_8464\\355048950.py\u001b[0m in \u001b[0;36m\u001b[1;34m\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[1;31m# `conserved_for` now applies to all processes\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 2\u001b[1;33m \u001b[0mbkm_batch\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mdecay\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mcheck_conservation\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[1;32m~\\anaconda3\\envs\\tut\\lib\\site-packages\\qsdsan\\_process.py\u001b[0m in \u001b[0;36mcheck_conservation\u001b[1;34m(self, rtol, atol)\u001b[0m\n\u001b[0;32m 484\u001b[0m \u001b[0mmaterials\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_conserved_for\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 485\u001b[0m \u001b[0munconserved\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;33m[\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mmaterials\u001b[0m\u001b[1;33m[\u001b[0m\u001b[0mi\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mic_dot_v\u001b[0m\u001b[1;33m[\u001b[0m\u001b[0mi\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m)\u001b[0m \u001b[1;32mfor\u001b[0m \u001b[0mi\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mconserved\u001b[0m \u001b[1;32min\u001b[0m \u001b[0menumerate\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mconserved_arr\u001b[0m\u001b[1;33m)\u001b[0m \u001b[1;32mif\u001b[0m \u001b[1;32mnot\u001b[0m \u001b[0mconserved\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 486\u001b[1;33m raise RuntimeError(\"The following materials are unconserved by the \"\n\u001b[0m\u001b[0;32m 487\u001b[0m \u001b[1;34m\"stoichiometric coefficients. A positive value \"\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 488\u001b[0m \u001b[1;34m\"means the material is created, a negative value \"\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;31mRuntimeError\u001b[0m: The following materials are unconserved by the stoichiometric coefficients. A positive value means the material is created, a negative value means the material is destroyed:\n C: -0.05" + ] + } + ], "source": [ - "# `conserved_for` now applies to all processes,\n", - "# the following will trigger an error\n", - "# bkm_batch.decay.check_conservation()" + "# `conserved_for` now applies to all processes\n", + "bkm_batch.decay.check_conservation()" ] }, { @@ -1264,10 +2628,21 @@ }, { "cell_type": "code", - "execution_count": 56, + "execution_count": 63, "id": "cf5c08b4", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "{'X_S': 0.32, 'S_S': 0.32, 'O2': 0.0, 'CO2': 1.0, 'X_B': 0.366, 'H2O': 0.0}" + ] + }, + "execution_count": 63, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# `i_C` values for each component\n", "dict(zip(cmps_bkm.IDs, cmps_bkm.i_C))" @@ -1300,7 +2675,7 @@ }, { "cell_type": "code", - "execution_count": 57, + "execution_count": 64, "id": "29c15cb9", "metadata": {}, "outputs": [], @@ -1325,10 +2700,21 @@ }, { "cell_type": "code", - "execution_count": 58, + "execution_count": 65, "id": "7fe90104", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "{'k_hyd': 3.0, 'y_B': 0.8, 'mu_B': 4.0, 'K_S': 9.0, 'b': 0.4}" + ] + }, + "execution_count": 65, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# At this point, the value for `y_B` is not updated yet, since we haven't evalutated it\n", "# with input of component concentrations\n", @@ -1337,10 +2723,30 @@ }, { "cell_type": "code", - "execution_count": 59, + "execution_count": 66, "id": "9121e9cc", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Process: growth\n", + "[stoichiometry] S_S: -1/y_B\n", + " O2: (y_B - 1.0)/y_B\n", + " CO2: 0.002*(160.0 - 183.0*y_B)/y_B\n", + " X_B: 1.00\n", + "[reference] X_B\n", + "[rate equation] S_S*X_B*mu_B/(K_S + S_S)\n", + "[parameters] k_hyd: 3\n", + " y_B: 0.8\n", + " mu_B: 4\n", + " K_S: 9\n", + " b: 0.4\n", + "[dynamic parameters] \n" + ] + } + ], "source": [ "# But the list of dynamic parameters have been updated\n", "pc2.show()" @@ -1348,10 +2754,21 @@ }, { "cell_type": "code", - "execution_count": 60, + "execution_count": 67, "id": "248e0922", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "qsdsan._process.DynamicParameter" + ] + }, + "execution_count": 67, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# `y_B` is now a `DynamicParameter` object stored in the `_dyn_params` attribute of the process\n", "type(pc2._dyn_params['y_B'])" @@ -1359,12 +2776,23 @@ }, { "cell_type": "code", - "execution_count": 61, + "execution_count": 68, "id": "9dc27821", "metadata": { "scrolled": false }, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "{'k_hyd': 3.0, 'y_B': 0.5656854249492381, 'mu_B': 4.0, 'K_S': 9.0, 'b': 0.4}" + ] + }, + "execution_count": 68, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# Assuming component concentrations are all 1.\n", "state_bkm = np.ones(6)\n", @@ -1376,10 +2804,83 @@ }, { "cell_type": "code", - "execution_count": 62, + "execution_count": 69, "id": "d765098e", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
X_SS_SO2CO2X_BH2O
hydrolysis-110000
growth0-1.77-0.7680.210
decay1000-10
\n", + "
" + ], + "text/plain": [ + " X_S S_S O2 CO2 X_B H2O\n", + "hydrolysis -1 1 0 0 0 0\n", + "growth 0 -1.77 -0.768 0.2 1 0\n", + "decay 1 0 0 0 -1 0" + ] + }, + "execution_count": 69, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# Then the stoichiometry is updated accordingly\n", "bkm.stoichiometry" @@ -1408,10 +2909,21 @@ }, { "cell_type": "code", - "execution_count": 63, + "execution_count": 70, "id": "cb7c404e", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 70, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# The use of `Kinetics` to define process rate is also similar\n", "# For example, for the decay process\n", @@ -1443,10 +2955,21 @@ }, { "cell_type": "code", - "execution_count": 64, + "execution_count": 71, "id": "5fdddd88", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "array([1.732, 2.309, 0.369])" + ] + }, + "execution_count": 71, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# Define the function\n", "def rhos_eval(state_arr, params):\n", @@ -1478,9 +3001,9 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "Python [conda env:tut]", "language": "python", - "name": "python3" + "name": "conda-env-tut-py" }, "language_info": { "codemirror_mode": { @@ -1492,36 +3015,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" - }, - "varInspector": { - "cols": { - "lenName": 16, - "lenType": 16, - "lenVar": 40 - }, - "kernels_config": { - "python": { - "delete_cmd_postfix": "", - "delete_cmd_prefix": "del ", - "library": "var_list.py", - "varRefreshCmd": "print(var_dic_list())" - }, - "r": { - "delete_cmd_postfix": ") ", - "delete_cmd_prefix": "rm(", - "library": "var_list.r", - "varRefreshCmd": "cat(var_dic_list()) " - } - }, - "types_to_exclude": [ - "module", - "function", - "builtin_function_or_method", - "instance", - "_Feature" - ], - "window_display": false + "version": "3.9.0" } }, "nbformat": 4, diff --git a/docs/source/tutorials/11_Dynamic_Simulation.ipynb b/docs/source/tutorials/11_Dynamic_Simulation.ipynb index 57072991..aed3ea92 100644 --- a/docs/source/tutorials/11_Dynamic_Simulation.ipynb +++ b/docs/source/tutorials/11_Dynamic_Simulation.ipynb @@ -3,17 +3,13 @@ { "cell_type": "markdown", "id": "28c4658c", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, + "metadata": {}, "source": [ "# Dynamic Simulation \n", "\n", "- **Prepared by:**\n", " \n", - " - [Joy Zhang](https://qsdsan.readthedocs.io/en/latest/CONTRIBUTING.html)\n", + " - [Joy Zhang](https://qsdsan.readthedocs.io/en/latest/authors/Joy_Zhang.html)\n", " \n", "- **Covered topics:**\n", "\n", @@ -23,21 +19,17 @@ " \n", "- **Video demo:**\n", "\n", - " - [Yalin Li](https://qsdsan.readthedocs.io/en/latest/CONTRIBUTING.html)\n", + " - To be posted\n", " \n", - "To run tutorials in your browser, go to this [Binder page](https://mybinder.org/v2/gh/QSD-Group/QSDsan-env/main?urlpath=git-pull%3Frepo%3Dhttps%253A%252F%252Fgithub.com%252FQSD-group%252FQSDsan%26urlpath%3Dtree%252FQSDsan%252Fdocs%252Fsource%252Ftutorials%26branch%3Dmain).\n", + "To run tutorials in your browser, go to this [Binder page](https://mybinder.org/v2/gh/QSD-Group/QSDsan/main?filepath=%2Fdocs%2Fsource%2Ftutorials).\n", " \n", - "You can also watch a video demo on [YouTube](https://youtu.be/1Rr1QxUiE5k) (subscriptions & likes appreciated!)." + "You can also watch a video demo on YouTube (link to be posted) (subscriptions & likes appreciated!)." ] }, { "cell_type": "markdown", "id": "2bc790e7", - "metadata": { - "slideshow": { - "slide_type": "subslide" - } - }, + "metadata": {}, "source": [ "---\n", "From previous tutorials, we've covered how to use QSDsan's [SanUnit](https://qsdsan.readthedocs.io/en/latest/tutorials/5_SanUnit_advanced.html) and [WasteStream](https://qsdsan.readthedocs.io/en/latest/tutorials/3_WasteStream.html) classes to model the mass/energy flows throughout a system. You may have noticed, the simulation results generated by `SanUnit._run` are **static**, i.e., they don't carry time-related information. \n", @@ -49,17 +41,13 @@ "cell_type": "code", "execution_count": 1, "id": "3dc1138e", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, + "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "This tutorial was made with qsdsan v1.3.1 and exposan v1.3.1\n" + "This tutorial was made with qsdsan v1.2.5 and exposan v1.2.5\n" ] } ], @@ -71,29 +59,15 @@ { "cell_type": "markdown", "id": "b7f9ccfc", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, + "metadata": {}, "source": [ "## 1. Understanding dynamic simulation with QSDsan \n", "\n", "### 1.1. An example system\n", "Let's use [Benchmark Simulation Model no.1 (BSM1)](http://iwa-mia.org/benchmarking/#BSM1) as an example. BSM1 describes an activated sludge treatment process that can be commonly found in conventional wastewater treatment facilities. The full system has been implemented in [EXPOsan](https://github.com/QSD-Group/EXPOsan/tree/main/exposan/bsm1).\n", "\n", - "The activated sludge process is often characterized as a series of biokinetic reactions in parallel (recap on `Process` [here](https://qsdsan.readthedocs.io/en/latest/tutorials/10_Process.html)). The mathematical models of this kind cannot output mass flows or concentrations directly as a function of input. But rather, they describe the rates of change in state variables at any time as a function of the state variables (often concentrations). As a result, simulation of such systems involves solving a series of ordinary differential equations (ODEs). We have developed features in QSDsan for this specific purpose." - ] - }, - { - "cell_type": "markdown", - "id": "a8a07c91", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, - "source": [ + "The activated sludge process is often characterized as a series of biokinetic reactions in parallel (recap on `Process` [here](https://qsdsan.readthedocs.io/en/latest/tutorials/10_Process.html)). The mathematical models of this kind cannot output mass flows or concentrations directly as a function of input. But rather, they describe the rates of change in state variables at any time as a function of the state variables (often concentrations). As a result, simulation of such systems involves solving a series of ordinary differential equations (ODEs). We have developed features in QSDsan for this specific purpose.\n", + "\n", "#### 1.1.1. Running dynamic simulation" ] }, @@ -101,11 +75,7 @@ "cell_type": "code", "execution_count": 2, "id": "a1c82016", - "metadata": { - "slideshow": { - "slide_type": "fragment" - } - }, + "metadata": {}, "outputs": [ { "name": "stdout", @@ -113,7 +83,7 @@ "text": [ "System: bsm1_sys\n", "ins...\n", - "[0] wastewater \n", + "[0] wastewater\n", " phase: 'l', T: 293.15 K, P: 101325 Pa\n", " flow (kmol/hr): S_I 23.1\n", " S_S 53.4\n", @@ -124,10 +94,10 @@ " S_ND 0.381\n", " ... 4.26e+04\n", "outs...\n", - "[0] effluent \n", + "[0] effluent\n", " phase: 'l', T: 293.15 K, P: 101325 Pa\n", " flow: 0\n", - "[1] WAS \n", + "[1] WAS\n", " phase: 'l', T: 293.15 K, P: 101325 Pa\n", " flow: 0\n" ] @@ -145,236 +115,178 @@ "cell_type": "code", "execution_count": 3, "id": "61ef9ac7", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, + "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "A1\n", - "CSTR:c->A2\n", - "CSTR:c\n", - "\n", - "\n", + "A1CSTR:c->A2CSTR:c\n", + "\n", + "\n", "\n", - " ws1\n", + " ws1\n", "\n", "\n", "\n", - "\n", + "\n", "\n", - "A2\n", - "CSTR:c->O1\n", - "CSTR:c\n", - "\n", - "\n", + "A2CSTR:c->O1CSTR:c\n", + "\n", + "\n", "\n", - " ws3\n", + " ws3\n", "\n", "\n", "\n", - "\n", + "\n", "\n", - "O1\n", - "CSTR:c->O2\n", - "CSTR:c\n", - "\n", - "\n", + "O1CSTR:c->O2CSTR:c\n", + "\n", + "\n", "\n", - " ws5\n", + " ws5\n", "\n", "\n", "\n", - "\n", + "\n", "\n", - "O2\n", - "CSTR:c->O3\n", - "CSTR:c\n", - "\n", - "\n", + "O2CSTR:c->O3CSTR:c\n", + "\n", + "\n", "\n", - " ws7\n", + " ws7\n", "\n", "\n", "\n", - "\n", + "\n", "\n", - "O3\n", - "CSTR:c->A1\n", - "CSTR:c\n", - "\n", - "\n", + "O3CSTR:c->A1CSTR:c\n", + "\n", + "\n", "\n", - " RWW\n", + " RWW\n", "\n", "\n", "\n", - "\n", + "\n", "\n", - "O3\n", - "CSTR:c->C1\n", - "Flat bottom circular clarifier:c\n", - "\n", - "\n", + "O3CSTR:c->C1Flat bottom circular clarifier:c\n", + "\n", + "\n", "\n", - " treated\n", + " treated\n", "\n", "\n", "\n", - "\n", + "\n", "\n", - "C1\n", - "Flat bottom circular clarifier:c->A1\n", - "CSTR:c\n", - "\n", - "\n", + "C1Flat bottom circular clarifier:c->A1CSTR:c\n", + "\n", + "\n", "\n", - " RAS\n", + " RAS\n", "\n", "\n", "\n", - "\n", + "\n", "\n", - "C1\n", - "Flat bottom circular clarifier:c->121356496865:w\n", - "\n", + "C1Flat bottom circular clarifier:c-> effluent:w\n", + "\n", "\n", - " effluent\n", + " effluent\n", "\n", "\n", "\n", - "\n", + "\n", "\n", - "C1\n", - "Flat bottom circular clarifier:c->121356496705:w\n", - "\n", + "C1Flat bottom circular clarifier:c-> WAS:w\n", + "\n", "\n", - " WAS\n", + " WAS\n", "\n", "\n", "\n", - "\n", + "\n", "\n", - "121356497265:e->A1\n", - "CSTR:c\n", - "\n", - "\n", + " wastewater:e->A1CSTR:c\n", + "\n", + "\n", " wastewater\n", "\n", "\n", "\n", - "\n", + "\n", "\n", - "A1\n", - "CSTR\n", + "A1CSTR\n", "\n", - "\n", - "A1\n", - "CSTR\n", + "\n", + "A1CSTR\n", "\n", "\n", "\n", - "\n", + "\n", "\n", - "A2\n", - "CSTR\n", + "A2CSTR\n", "\n", - "\n", - "A2\n", - "CSTR\n", + "\n", + "A2CSTR\n", "\n", "\n", "\n", - "\n", + "\n", "\n", - "O1\n", - "CSTR\n", + "O1CSTR\n", "\n", - "\n", - "O1\n", - "CSTR\n", + "\n", + "O1CSTR\n", "\n", "\n", "\n", - "\n", + "\n", "\n", - "O2\n", - "CSTR\n", + "O2CSTR\n", "\n", - "\n", - "O2\n", - "CSTR\n", + "\n", + "O2CSTR\n", "\n", "\n", "\n", - "\n", + "\n", "\n", - "O3\n", - "CSTR\n", + "O3CSTR\n", "\n", - "\n", - "O3\n", - "CSTR\n", + "\n", + "O3CSTR\n", "\n", "\n", "\n", - "\n", + "\n", "\n", - "C1\n", - "Flat bottom circular clarifier\n", + "C1Flat bottom circular clarifier\n", "\n", - "\n", - "C1\n", - "Flat bottom circular clarifier\n", + "\n", + "C1Flat bottom circular clarifier\n", "\n", "\n", "\n", - "\n", + "\n", "\n", - "121356497265\n", + " wastewater\n", "\n", "\n", - "\n", + "\n", "\n", - "121356496865\n", - "\n", + " effluent\n", + "\n", "\n", - "\n", + "\n", "\n", - "121356496705\n", - "\n", + " WAS\n", + "\n", "\n", "\n", "" @@ -390,19 +302,15 @@ "source": [ "# The BSM1 system is composed of 5 CSTRs in series, \n", "# followed by a flat-bottom circular clarifier.\n", - "# sys.units\n", - "sys.diagram()" + "sys.diagram()\n", + "# sys.units" ] }, { "cell_type": "code", "execution_count": 4, "id": "03c7b593", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, + "metadata": {}, "outputs": [], "source": [ "# If we try to simulate it like we'd do for a \"static\" system\n", @@ -412,24 +320,16 @@ { "cell_type": "markdown", "id": "07f91f64", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, + "metadata": {}, "source": [ "We run into this error because QSDsan (essentially biosteam in the background) considers this system dynamic, and additional arguments are required for `simulate` to work." ] }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 4, "id": "b349b9a3", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, + "metadata": {}, "outputs": [ { "data": { @@ -437,7 +337,7 @@ "True" ] }, - "execution_count": 5, + "execution_count": 4, "metadata": {}, "output_type": "execute_result" } @@ -449,64 +349,52 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 5, "id": "43772dae", - "metadata": { - "slideshow": { - "slide_type": "slide" + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{: True,\n", + " : True,\n", + " : True,\n", + " : True,\n", + " : True,\n", + " : True}" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" } - }, - "outputs": [], + ], "source": [ "# This is because the system contains at least one dynamic SanUnit\n", - "# {u: u.isdynamic for u in sys.units}\n", + "{u: u.isdynamic for u in sys.units}\n", "\n", "# If we disable dynamic simulation, then `simulate` would work as usual\n", - "sys.isdynamic = False\n", - "sys.simulate()" + "# sys.isdynamic = False\n", + "# sys.simulate()" ] }, { "cell_type": "markdown", "id": "cc28e85f", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, + "metadata": {}, "source": [ "To perform a dynamic simulation of the system, we need to provide at least one additional keyword argument, i.e., `t_span`, as suggested in the error message. `t_span` is a 2-tuple indicating the simulation period.\n", "\n", - ">**Note**: Whether `t_span = (0,10)` means 0-10 days or 0-10 hours/minutes/months depends entirely on units of the parameters in the system's ODEs. For BSM1, it'd mean 0-10 days because all parameters in the ODEs express time in the unit of \"day\"." - ] - }, - { - "cell_type": "markdown", - "id": "0c111c81", - "metadata": { - "slideshow": { - "slide_type": "subslide" - } - }, - "source": [ + ">**Note**: Whether `t_span = (0,10)` means 0-10 days or 0-10 hours/minutes/months depends entirely on units of the parameters in the system's ODEs. For BSM1, it'd mean 0-10 days because all parameters in the ODEs express time in the unit of \"day\".\n", + "\n", "Other often-used keyword arguments include:\n", "\n", "- `t_eval`: a 1d array to specify the output time points\n", "- `method`: a string specifying the ODE solver\n", "- `state_reset_hook`: specifies how to reset the simulation\n", "\n", - "`t_span`, `t_eval`, and `method` are essentially passed to [scipy.integrate.solve_ivp](https://docs.scipy.org/doc/scipy/reference/generated/scipy.integrate.solve_ivp.html) function as keyword arguments. See [documentation](https://biosteam.readthedocs.io/en/latest/API/System.html#biosteam.System.dynamic_run) for a complete list of keyword arguments. You may notice that `scipy.integrate.solve_ivp` also requires input of `fun` (i.e., the ODEs) and `y0` (i.e., the initial condition). We'll learn later how `System.simulate` automates the compilation of these inputs." - ] - }, - { - "cell_type": "markdown", - "id": "9c8b4556", - "metadata": { - "slideshow": { - "slide_type": "subslide" - } - }, - "source": [ + "`t_span`, `t_eval`, and `method` are essentially passed to [scipy.integrate.solve_ivp](https://docs.scipy.org/doc/scipy/reference/generated/scipy.integrate.solve_ivp.html) function as keyword arguments. See [documentation](https://biosteam.readthedocs.io/en/latest/API/System.html#biosteam.System.dynamic_run) for a complete list of keyword arguments. You may notice that `scipy.integrate.solve_ivp` also requires input of `fun` (i.e., the ODEs) and `y0` (i.e., the initial condition). We'll learn later how `System.simulate` automates the compilation of these inputs.\n", + "\n", "---\n", "### Tip\n", "For systems that are expected to converge to some sort of \"steady state\", it is usually faster to simulate with implicit ODE solvers (e.g., `method = BDF` or `method = LSODA`) than with explicit ones. In case of one solver fails to complete integration through the entire specified simulation period, always try with alternative ones.\n", @@ -516,14 +404,9 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 6, "id": "45ef4032", - "metadata": { - "scrolled": true, - "slideshow": { - "slide_type": "slide" - } - }, + "metadata": {}, "outputs": [ { "name": "stdout", @@ -532,10 +415,10 @@ "System: bsm1_sys\n", "Highest convergence error among components in recycle\n", "streams {C1-1, O3-0} after 5 loops:\n", - "- flow rate 1.46e-11 kmol/hr (4.2e-14%)\n", + "- flow rate 7.28e-12 kmol/hr (7.6e-14%)\n", "- temperature 0.00e+00 K (0%)\n", "ins...\n", - "[0] wastewater \n", + "[0] wastewater\n", " phase: 'l', T: 293.15 K, P: 101325 Pa\n", " flow (kmol/hr): S_I 23.1\n", " S_S 53.4\n", @@ -546,7 +429,7 @@ " S_ND 0.381\n", " ... 4.26e+04\n", "outs...\n", - "[0] effluent \n", + "[0] effluent\n", " phase: 'l', T: 293.15 K, P: 101325 Pa\n", " flow (kmol/hr): S_I 22.6\n", " S_S 0.67\n", @@ -556,7 +439,7 @@ " X_BA 0.43\n", " X_P 1.3\n", " ... 4.17e+04\n", - "[1] WAS \n", + "[1] WAS\n", " phase: 'l', T: 293.15 K, P: 101325 Pa\n", " flow (kmol/hr): S_I 0.481\n", " S_S 0.0143\n", @@ -565,14 +448,12 @@ " X_BH 80.3\n", " X_BA 4.69\n", " X_P 14.1\n", - " ... 884\n" + " ... 885\n" ] } ], "source": [ "# Let's try simulating the BSM1 system from day 0 to day 50\n", - "# user shorter time or try changing method to 'RK23' (explicit solver) if it takes a long time\n", - "sys.isdynamic = True\n", "sys.simulate(t_span=(0, 50), method='BDF', state_reset_hook='reset_cache')\n", "sys.show()" ] @@ -580,25 +461,19 @@ { "cell_type": "markdown", "id": "972442da", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, + "metadata": {}, "source": [ + "[Back to top](#top)\n", + "\n", "#### 1.1.2. Retrieve dynamic simulation data\n", "The `show` method only displays the system's state at the end of the simulation period. How do we retrieve information on system dynamics? QSDsan uses [Scope](https://qsdsan.readthedocs.io/en/latest/api/utils/scope.html) objects to keep track of values of state variables during simulation." ] }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 7, "id": "3d7a8b0d", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, + "metadata": {}, "outputs": [ { "data": { @@ -606,7 +481,7 @@ "(, )" ] }, - "execution_count": 8, + "execution_count": 7, "metadata": {}, "output_type": "execute_result" } @@ -619,13 +494,9 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 8, "id": "5fedeb57", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, + "metadata": {}, "outputs": [ { "data": { @@ -633,7 +504,7 @@ "" ] }, - "execution_count": 9, + "execution_count": 8, "metadata": {}, "output_type": "execute_result" } @@ -650,17 +521,13 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 9, "id": "a7c7fa4d", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, + "metadata": {}, "outputs": [ { "data": { - "image/png": "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ERGRP2QFUJ1Oh27duok2GzfHas615Wu1LlFSe4vEdkRERETkJHvk9IknnqibNm2a/vTp08HoT9iTurRCVb0deUu2Y9WeoqD2h4iIiChSyF7H9fXXX9cdPHhQ1bVrV1NaWpojOjq6yfM7d+6sDljvwsiqPUV45pMC3G3bKumn2hnOWthnPinAyKwUlgwQERER+SA7uI4bN87qu1XH4tryVYADf9B/I+mYEpghAigqr8WWwlIM6cHSASIiIqLWyA6uzz77bH0wOhKuGm/5OlhVgFjB97qsrjVcXbiWKxEREZFvXB1AocZbvl6m+lHSMVsdveBo9KPnWq5EREREvskecbXZbJg7d65u+fLl2qNHjwpWq7VJcWZpaanvbaMiSOPR0ssEacG1C065v+darkRERETSyB5x/dvf/qZ/9dVXdTfccIO1oqJCeOihh+quueYaqyAI4vTp0zvUdq9A09HSGKFG0jGN243rl8qJWUREREQSyA6uy5Yt0/773/+unTp1ar1Go8Ett9xiXbRoUe1f//rX+u+//14djE6GMteWrwBwXOwk6ZjG7fJ3FcHukLqIFhEREVHHJTu4njhxQujbt68dAGJiYsSysjIBcK42sGrVKtmlB+FOrRIwrl8qAGC943xJxzRu51pVgIiIiIhaJzu4du3a1XH8+HEVAHTv3t3xxRdfaABgy5Ytap1O1+GGDu0OEfm7nJsInBSl1ao2b8dVBYiIiIh8kx1cx40bZ1u9erUaAP785z/XP/PMM/qePXvG3HnnnVF33HFHh1vjtfGqAglClaRjmrfjqgJEREREvkn+aP+VV17R3nHHHda5c+e6J2DdfPPNtrS0NMvGjRvVvXr1clx77bW24HQzdDUeLb1E2CPpmNNirPt7c7SWqwoQERERSSB5xHXGjBmGrl27mm666aaor776yj0J67LLLrM//vjj9R0xtAJnR0s1sOH36p2SjjmBs0G1zGLFVwXFwegaERERUUSRHFyLiooq//Wvf9UWFRUJubm50RkZGcYZM2boDh8+3KHXcsrJTIA5Wov71SsgSPhJVIhRTXbNEgA880kBVxYgIiIi8kFycI2Ojsadd95p/fbbby0///xz1c0332x9++23dT169DCOHDkyetmyZZr6+o67G+yD6nxJ7XRifZNds0RwZQEiIiIiKfza8rVnz57i7Nmz6woLC6s+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kSNH2nbt2lW9fft2923AgAGOm266ybp9+/bqnJwcvodhprKyEoWFharU1FQxVP8OslQggCZPnlx/1113RV188cX2QYMG2V9++WWdxWIR7r77bmt7941aqqysxP79+92/vB08eFD1ww8/qBITE8WMjAxx0qRJ9c8//7y+V69eju7duzumT5+uT01NFcePH88VBUJAXl6e4T//+Y/2f//7n8VkMonHjx8XAMBsNovR0dEwm82YOHGi9dFHHzUkJCTUxMbGig899JBh0KBBds5mDg2PPfaY/qqrrrKlp6c7KisrhSVLlmjXrVunXrlyZR3fv9AXGxsLV025S0xMjJiYmCi6Hud7GNomT56sHzt2rC0jI8Nx7Ngx1YwZM/QqlUq85ZZbrKH6d5DLYQXYK6+8ov373/+uP3HihNC3b1/7q6++WsffLEPT119/rR4xYkR088dvvfVW67vvvlvrcDgwffp0/YIFC7Tl5eXCkCFD7PPmzavt06cPSwVCgCAIJk+Pv/XWW7X33HOPFXAunj158mTDf//7X21dXR1GjBhhmzdvXm2XLl34MWUImDhxomHt2rWa4uJiITY2VszOznZMnTq1Ljc31w7w/QtHQ4cOjXYthwXwPQx1N9xwQ9SGDRvUpaWlQqdOncRLLrnEPnv27Npzzz1XBELz/WNwJSIiIqKwwBpXIiIiIgoLDK5EREREFBYYXImIiIgoLDC4EhEREVFYYHAlIiIiorDA4EpEREREYYHBlYiIiIjCAoMrEREREYUFBlciojZw2223GcaOHRvV1tedP3++VhAEkyAIpkmTJulba5uenm6cO3eurvF917FnzpwJfmeJiHzQtHcHiIjCnbftZ13++te/1r/22mu1otg+uyTGxsZi7969VUajUVYHtmzZUr1u3Tr1H/7whzYP3EREnjC4EhEpdOzYsSrX9++//7525syZ+r1797ofM5lMosnUarYNKkEQ4M/e4snJyWJCQgL3lCeikMFSASIihbp06SK6bnFxcaIrKLpuJpOpRanA0KFDox988EHDpEmT9PHx8aakpCTjvHnztFVVVbj99tsNJpPJ1KNHD+Onn36qbnyt3bt3q6688spoo9FoSkpKMt58882GkydPCnL7XFxcLFx11VVRUVFRpoyMDOM777zDgQwiCnkMrkRE7WTJkiXaxMREcfPmzdUPPvhg/aRJkwwTJkyIGjJkiH3btm3VI0aMsN1xxx1R1dXVAIAzZ87g97//fXT//v3tW7ZsqV65cqXlxIkTqhtuuEH2R/l33HGH4bffflOtXr3a8t///tcyb948nT8BmIioLTG4EhG1kwsuuMD+9NNP1/fu3dsxffr0eoPBgE6dOol5eXnW3r17O2bMmFFXWloq7Ny5Uw0Ar776qq5fv36OF198sS4rK8tx8cUXOxYtWlTz7bffqvft2yf53/N9+/apvvzyS82bb75Zc+mll9pzcnIcCxYsqK2pqQneiyUiCgB+NERE1E4uuOACh+t7jUaDhIQEMTs72/1YSkqKCAAlJSUCAOzevVu9bt06tdFobFEwe+DAAaFPnz6SrltQUKDSaDQYOHCg+1pZWVkOs9ns/4shImoDDK5ERO1Eq9U2mfgkCAK0Wq37vkrlHER1OJz5sqqqSrjqqqtsL774Ym3zc/kz+YqIKNwwuBIRhYkLL7zQ/tFHH2kyMzPFxgFXrvPOO89hs9mwdetW1eDBgx0AsHfvXlVZWVmgukpEFBSscSUiChOTJk2qP3PmjHDjjTdGbd68WfXLL78IK1euVN9+++0Gm80m+TznnXeeY+TIkfYHHnggauPGjeotW7ao7rnnHkNUFJdrJaLQxuBKRBQmzjnnHHHDhg0Wu92O0aNHx/Tr1884efJkg9lsFl1lBVK9/fbbNampqY7hw4dHX3/99dH33nuvtXPnziw3IKKQJoiiWNHenSAiouCYP3++9tFHHzWUlZVV+nP8119/rR4xYkR0aWlpZXx8fKC7R0QkC0dciYgiXHl5OYxGo+kvf/mLXs5x5513XsyYMWOig9UvIiK5OOJKRBTBKioqUFxcLABAfHw85JQDFBYWClarFQDQo0cPUa1W+ziCiCi4GFyJiIiIKCywVICIiIiIwgKDKxERERGFBQZXIiIiIgoLDK5EREREFBYYXImIiIgoLDC4EhEREVFYYHAlIiIiorDA4EpEREREYeH/AcWk35QFvrqKAAAAAElFTkSuQmCC\n", + "image/png": "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8m+//Xb9n3/+qVq1apXlk08+scyZM0fXkgBMRNSaGFyJiIKkd+/e9qeffrq2Z8+e4tSpU2v1ej06dOgg5ebmWnv27ClOmzatpqSkRNi5c6caAGbNmqXr27ev+NJLL9VkZmaK559/vvjee+9VrVu3Tr1//37Z/57v379f9e2332reeeedqsGDB9uzs7PFefPmVVdVVQXuN0tE5Af8aIiIKEh69+4tOn+t0WgQHx8vZWVl1T+WnJwsAUBxcbEAALt371avX79ebTAYmhXM/v7770KvXr1kvW5eXp5Ko9FgwIAB9a+VmZkpmkymlv9miIhaAYMrEVGQaLXaRhc+CYIArVZbf1+lciyiiqIjX1ZUVAiXXnqp7aWXXqpueq6WXHxFRBRuGFyJiMLEeeedZ//88881GRkZUsOA66tzzjlHtNls2LZtm+qCCy4QAWDfvn2q0tJSf02ViCggWONKRBQmHnjggdrTp08L119/feSWLVtUv/32m7BixQr1bbfdprfZbLLPc84554gjR460T5gwIXLTpk3qrVu3qu6++259ZCTbtRJRaGNwJSIKE2eddZa0ceNGi91ux+jRo6P79u1rmDhxot5kMknOsgK53n///aqUlBRx+PDhUddcc03UPffcY+3YsSPLDYgopAmSJJUFexJERBQYc+fO1T7yyCP60tLS8pYc//3336tHjBgRVVJSUh4XF+fv6RER+YQrrkREbZzZbIbBYDD+85//jPDluHPOOSf68ssvjwrUvIiIfMUVVyKiNqysrAyFhYUCAMTFxcGXcoD8/HzBarUCALp16yap1WovRxARBRaDKxERERGFBZYKEBEREVFYYHAlIiIiorDA4EpEREREYYHBlYiIiIjCAoMrEREREYUFBlciIiIiCgsMrkREREQUFhhciYiIiCgs/D9iX3QJzFy/3AAAAABJRU5ErkJggg==\n", "text/plain": [ "
" ] @@ -676,97 +543,91 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 10, "id": "51cc75b4", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, + "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "array([], shape=(0, 1), dtype=float64)" + "array([[3.000e+01, 5.000e+00, 1.000e+03, ..., 4.152e-11, 9.379e-05,\n", + " 9.223e+04],\n", + " [3.000e+01, 5.000e+00, 1.000e+03, ..., 4.194e-09, 9.473e-03,\n", + " 9.223e+04],\n", + " [3.000e+01, 5.000e+00, 1.000e+03, ..., 8.346e-09, 1.885e-02,\n", + " 9.223e+04],\n", + " ...,\n", + " [3.000e+01, 2.811e+00, 1.146e+03, ..., 2.500e+01, 9.986e+05,\n", + " 9.223e+04],\n", + " [3.000e+01, 2.810e+00, 1.147e+03, ..., 2.501e+01, 9.986e+05,\n", + " 9.223e+04],\n", + " [3.000e+01, 2.810e+00, 1.148e+03, ..., 2.501e+01, 9.986e+05,\n", + " 9.223e+04]])" ] }, - "execution_count": 11, + "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Raw time-series data are stored in\n", - "# A1.scope.record\n", - "A2 = sys.flowsheet.unit.A2\n", - "A2.scope\n", - "A2.scope.record" + "A1.scope.record" ] }, { "cell_type": "markdown", "id": "8b8727df", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, + "metadata": {}, "source": [ "Each row in the `record` attribute is values of `A1`'s state variables at a certain time point." ] }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 11, "id": "cab34aab", "metadata": { - "scrolled": true, - "slideshow": { - "slide_type": "slide" - } + "scrolled": true }, "outputs": [ { "data": { "text/plain": [ - "array([0.000e+00, 5.096e-10, 1.019e-09, 6.115e-09, 1.121e-08, 6.217e-08,\n", - " 1.131e-07, 3.165e-07, 5.198e-07, 7.231e-07, 1.403e-06, 2.082e-06,\n", - " 2.762e-06, 8.671e-06, 1.458e-05, 2.049e-05, 3.168e-05, 4.286e-05,\n", - " 5.405e-05, 6.524e-05, 1.049e-04, 1.446e-04, 1.842e-04, 2.239e-04,\n", - " 3.090e-04, 3.941e-04, 4.793e-04, 5.644e-04, 6.495e-04, 8.358e-04,\n", - " 1.022e-03, 1.208e-03, 1.395e-03, 1.581e-03, 1.767e-03, 2.185e-03,\n", - " 2.602e-03, 2.895e-03, 3.189e-03, 3.398e-03, 3.567e-03, 3.736e-03,\n", - " 3.905e-03, 4.038e-03, 4.171e-03, 4.304e-03, 4.438e-03, 4.571e-03,\n", - " 4.704e-03, 4.849e-03, 4.993e-03, 5.138e-03, 5.283e-03, 5.427e-03,\n", - " 5.572e-03, 5.832e-03, 6.093e-03, 6.353e-03, 6.613e-03, 6.874e-03,\n", - " 7.332e-03, 7.790e-03, 8.248e-03, 8.706e-03, 9.407e-03, 1.011e-02,\n", - " 1.081e-02, 1.151e-02, 1.273e-02, 1.396e-02, 1.519e-02, 1.641e-02,\n", - " 1.848e-02, 2.055e-02, 2.195e-02, 2.335e-02, 2.476e-02, 2.616e-02,\n", - " 2.857e-02, 3.097e-02, 3.218e-02, 3.338e-02, 3.458e-02, 3.578e-02,\n", - " 3.699e-02, 3.949e-02, 4.116e-02, 4.283e-02, 4.450e-02, 4.616e-02,\n", - " 5.025e-02, 5.433e-02, 5.842e-02, 6.250e-02, 6.937e-02, 7.624e-02,\n", - " 7.709e-02, 7.795e-02, 7.881e-02, 7.967e-02, 8.006e-02, 8.045e-02,\n", - " 8.084e-02, 8.182e-02, 8.280e-02, 8.378e-02, 8.420e-02, 8.462e-02,\n", - " 8.503e-02, 8.548e-02, 8.593e-02, 8.620e-02, 8.646e-02, 8.712e-02,\n", - " 8.778e-02, 9.300e-02, 9.822e-02, 1.096e-01, 1.110e-01, 1.124e-01,\n", - " 1.138e-01, 1.144e-01, 1.150e-01, 1.156e-01, 1.171e-01, 1.185e-01,\n", - " 1.200e-01, 1.208e-01, 1.216e-01, 1.224e-01, 1.229e-01, 1.233e-01,\n", - " 1.236e-01, 1.239e-01, 1.255e-01, 1.262e-01, 1.270e-01, 1.328e-01,\n", - " 1.386e-01, 1.519e-01, 1.652e-01, 1.668e-01, 1.685e-01, 1.702e-01,\n", - " 1.717e-01, 1.733e-01, 1.842e-01, 1.856e-01, 1.870e-01, 1.878e-01,\n", - " 1.886e-01, 1.891e-01, 1.896e-01, 1.899e-01, 1.902e-01, 1.933e-01,\n", - " 1.965e-01, 2.170e-01, 2.375e-01, 2.581e-01, 2.592e-01, 2.603e-01,\n", - " 2.614e-01, 2.723e-01, 2.832e-01, 3.164e-01, 3.496e-01, 3.828e-01,\n", - " 4.503e-01, 5.178e-01, 5.852e-01, 6.527e-01, 7.282e-01, 8.037e-01,\n", - " 8.791e-01, 9.546e-01, 1.105e+00, 1.256e+00, 1.406e+00, 1.557e+00,\n", - " 1.810e+00, 2.063e+00, 2.317e+00, 2.570e+00, 3.003e+00, 3.436e+00,\n", - " 3.869e+00, 4.302e+00, 4.915e+00, 5.528e+00, 6.142e+00, 6.755e+00,\n", - " 7.995e+00, 9.236e+00, 1.048e+01, 1.172e+01, 1.341e+01, 1.511e+01,\n", - " 1.680e+01, 1.850e+01, 2.118e+01, 2.386e+01, 2.654e+01, 2.923e+01,\n", - " 3.427e+01, 3.932e+01, 4.437e+01, 4.942e+01, 5.000e+01])" + "array([0.000e+00, 5.092e-10, 1.018e-09, 6.110e-09, 1.120e-08, 6.212e-08,\n", + " 1.130e-07, 3.163e-07, 5.195e-07, 7.227e-07, 1.402e-06, 2.081e-06,\n", + " 2.761e-06, 8.668e-06, 1.458e-05, 2.048e-05, 3.167e-05, 4.285e-05,\n", + " 5.404e-05, 6.522e-05, 1.049e-04, 1.445e-04, 1.842e-04, 2.238e-04,\n", + " 3.089e-04, 3.940e-04, 4.791e-04, 5.642e-04, 6.493e-04, 8.356e-04,\n", + " 1.022e-03, 1.208e-03, 1.394e-03, 1.581e-03, 1.767e-03, 2.184e-03,\n", + " 2.602e-03, 2.895e-03, 3.188e-03, 3.398e-03, 3.567e-03, 3.736e-03,\n", + " 3.905e-03, 4.038e-03, 4.171e-03, 4.304e-03, 4.437e-03, 4.571e-03,\n", + " 4.704e-03, 4.848e-03, 4.993e-03, 5.138e-03, 5.282e-03, 5.427e-03,\n", + " 5.571e-03, 5.832e-03, 6.092e-03, 6.352e-03, 6.612e-03, 6.872e-03,\n", + " 7.331e-03, 7.790e-03, 8.248e-03, 8.707e-03, 9.408e-03, 1.011e-02,\n", + " 1.081e-02, 1.151e-02, 1.274e-02, 1.396e-02, 1.519e-02, 1.642e-02,\n", + " 1.849e-02, 2.056e-02, 2.196e-02, 2.336e-02, 2.476e-02, 2.616e-02,\n", + " 2.858e-02, 3.100e-02, 3.221e-02, 3.342e-02, 3.463e-02, 3.583e-02,\n", + " 3.704e-02, 3.955e-02, 4.122e-02, 4.290e-02, 4.457e-02, 4.625e-02,\n", + " 5.036e-02, 5.447e-02, 5.858e-02, 6.270e-02, 6.959e-02, 7.649e-02,\n", + " 7.735e-02, 7.821e-02, 7.907e-02, 7.994e-02, 8.083e-02, 8.173e-02,\n", + " 8.262e-02, 8.299e-02, 8.335e-02, 8.367e-02, 8.399e-02, 8.436e-02,\n", + " 8.473e-02, 8.827e-02, 8.872e-02, 8.916e-02, 9.138e-02, 9.359e-02,\n", + " 1.004e-01, 1.072e-01, 1.228e-01, 1.248e-01, 1.267e-01, 1.287e-01,\n", + " 1.291e-01, 1.296e-01, 1.300e-01, 1.344e-01, 1.388e-01, 1.432e-01,\n", + " 1.597e-01, 1.762e-01, 1.927e-01, 1.936e-01, 1.945e-01, 1.954e-01,\n", + " 2.041e-01, 2.128e-01, 2.413e-01, 2.698e-01, 2.983e-01, 2.990e-01,\n", + " 2.997e-01, 3.005e-01, 3.012e-01, 3.083e-01, 3.154e-01, 3.225e-01,\n", + " 3.622e-01, 4.019e-01, 4.416e-01, 5.179e-01, 5.941e-01, 6.703e-01,\n", + " 7.466e-01, 8.335e-01, 9.205e-01, 1.007e+00, 1.094e+00, 1.280e+00,\n", + " 1.466e+00, 1.652e+00, 1.838e+00, 2.148e+00, 2.457e+00, 2.767e+00,\n", + " 3.076e+00, 3.591e+00, 4.106e+00, 4.621e+00, 5.136e+00, 5.874e+00,\n", + " 6.613e+00, 7.351e+00, 8.090e+00, 9.540e+00, 1.099e+01, 1.244e+01,\n", + " 1.389e+01, 1.586e+01, 1.782e+01, 1.979e+01, 2.176e+01, 2.373e+01,\n", + " 2.790e+01, 3.207e+01, 3.624e+01, 4.041e+01, 4.689e+01, 5.000e+01])" ] }, - "execution_count": 12, + "execution_count": 11, "metadata": {}, "output_type": "execute_result" } @@ -779,11 +640,7 @@ { "cell_type": "markdown", "id": "c14d81b0", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, + "metadata": {}, "source": [ "The tracked time-series data can be exported to a file in two ways." ] @@ -792,11 +649,7 @@ "cell_type": "code", "execution_count": 13, "id": "c126483f", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, + "metadata": {}, "outputs": [], "source": [ "# sys.scope.export('bsm1_time_series.xlsx')\n", @@ -814,24 +667,16 @@ { "cell_type": "markdown", "id": "5b93411d", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, + "metadata": {}, "source": [ "We can also (re-)define which unit or stream to track during dynamic simulation." ] }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 12, "id": "b818bfbf", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, + "metadata": {}, "outputs": [ { "data": { @@ -839,7 +684,7 @@ "(, )" ] }, - "execution_count": 14, + "execution_count": 12, "metadata": {}, "output_type": "execute_result" } @@ -854,18 +699,15 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 13, "id": "f6b35327", "metadata": { - "scrolled": false, - "slideshow": { - "slide_type": "slide" - } + "scrolled": false }, "outputs": [ { "data": { - "image/png": "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4Ofnp7569SqvIukFgMOHDwssLCzg5eWlBQBfX19NUlKS3lKPxMREQc+ePakCBCGEENICdfL1Q/CM+ZVmgs1btUbwjPno5OvXLOOKiorCnDlzcOLECWRkZOCXX37BhAkToFKpEBAQAAB4++238dVXX+H8+fO4c+cOTpw4gQ8++ACurq5wcys/Cfajjz7CuXPn8Nlnn+GPP/7Azp078Z///AcffPBBk9xHREQEtmzZgm+//Ra///47li1bht9++003U6xWq3Hw4MEq1//ev38fqampeo9nJzONQZ3XABcWFuKDDz4w/f7774UAcP369YKOHTuy4eHhpm3atNEuWLCgjGtfs2fPFg0ZMkTt6Oioffz4MfP9998LT506xf/5559LLS0tERoaqpo1a5aptbV1sYWFBTtt2jRTX19fjb+/vwYAgoKCNG5ubtqxY8eKV65cWZKdnc0sXrxY9P7775eZPtnRGR4eXrZp0yaTGTNmiMLCwlSJiYn83bt3CxISEqqsAEEIIYQQ49fJ1w8uPr64f+0qCvLzILW0Qlt3z2ad+e3bty82bNiAcePGIScnB1ZWVujevTuOHDmCzp07AwACAwOxa9cuLF++HAqFAnK5HAMGDEBUVBQEgvK0zMfHB3v27MG8efPw6aefwtnZGatXr8aYMWOa5D7GjBmD27dvY9asWSgpKUFISAhCQ0Nx4cIFAMDJkychlUrx0ksvVXrvF198gS+++EKvbceOHbrDMowGy7LKujw++OCD0u7du2tOnDhRaGZmxv7++++PWZZV7t69u8jb21tTl77ee++9MgcHB62JiQnbunVrbb9+/dQHDx4srHi9qKhI+f7775dZWVmxEomEDQ4OVt2/f//x033cvn378aBBg1RisZht1aqVNjIysrSsrEzvOomJiYXe3t4aExMT1tnZWfvNN98U12Wc+fn5SgCsQqFgCSGEENIwxcXFbHp6OltcXNzcQ3luLF68mO3atWuj9f/qq6+yY8eOZVmWZSMiItipU6fWq5+GjrOmfzsKhYIFwObn59ea29V5BjghIUG4a9euYn9/f83TC8e9vLw0GRkZdVpSsX379pKaXheLxdi0aVPJpk2bqo1zdnZmDx8+XFxTPwMHDtT8+uuv1S++IYQQQghp4a5cuQKpVIqVK1dWquJQF0VFRdi0aRMCAwPB5/Oxa9cuJCYm4ujR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EaWwiRMnqh1/GwwG5ObmahMmTAguczqdGgDU1tYKAPj888/lLVu2yHa7vccBxQcPHhRjx46NKO/+/fslg8GAkpKSYK7x48er2dnZ/R8MEVGcsAAmIkphRqOxyxfMhBAwGo3B25IU2KmrqoE6taWlRZSVlfmXLl3q7h6rP19yIyJKRSyAiYjSyHnnnaf84x//MIwYMUILLZSjNW7cONXv92P37t3S9OnTVQCoqqqSGhoaYtVVIqIBw2OAiYjSSEVFhbe+vl5cd9111p07d0rffPONWL9+vXzLLbdY/H5/xHHGjRunzp07V7nzzjut27dvl3ft2iXdfvvtFquVp/slouTHApiIKI0MHTpU27ZtW5uiKLjiiitskydPti9evNiSnZ2tdRwuEalnn33WVVBQoF5yySUZP/3pTzPuuOMOX15eHg+jIKKkJzRNa0p0J4iIKHmtWbPGeP/991saGhqa+9N+48aN8pw5czJOnTrVnJOTE+vuERFFjXuAiYhIV2NjI+x2u+O+++4zR9Nu3LhxtiuvvDJjoPpFRNQf3ANMRER9ampqQnV1tQCAnJwcRHOYw5EjR4TP5wMAjBo1SpNlWacFEdHAYwFMRERERGmFh0AQERERUVphAUxEREREaYUFMBERERGlFRbARERERJRWWAATERERUVphAUxEREREaYUFMBERERGlFRbARERERJRW/j82g11cyBFxEgAAAABJRU5ErkJggg==\n", + "image/png": "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JjExUW+pR0JCgqB79+7UAYIQQghpgTr4+SNkxvxKM8FmrVojZMZ8dPDzb5a8oqKiMGfOHJw8eRJpaWn45ZdfMH78eKhUKgQGBgIAhg0bhi+++AI///wz7t69i5MnT+K9996Dm5sb3N3dAQAffPABzp8/j08++QS///47du7cif/+97947733muR9TJ06FVu2bMHXX3+N27dvY9myZfjtt990M8VqtRqHDh2qcv3vgwcPcOXKFb3bs5OZxqDOa4ALCgrw3nvvmXz77bdCALhx48aT9u3bsxERESb29vbaBQsWlHIda/bs2eJBgwapnZyctPn5+cy3334rPH36NP/HH38ssbCwQFhYmGrWrFkmVlZWRebm5uy0adNM/Pz8NAEBARoACA4O1ri7u2vHjBkjWblyZXFmZiazePFi8bvvvltq8nRHZ0REROnGjRtFM2bMEIeHh6sSEhL4u3fvFsTHx1fZAYIQQgghxq+Dnz9cff3w4Po1PMnNgczCEm08vJp15rd3795Yv349xo4di6ysLFhaWqJr1644evQoOnbsCAAICgrCrl27sHz5ciiVSigUCvTr1w9RUVEQCMrKMl9fX+zZswfz5s3Dxx9/DBcXF8TExGD06NFN8j5Gjx6NP/74A7NmzUJxcTFCQ0MRFhaGCxcuAABOnToFmUyGl156qdJzP/vsM3z22Wd613bs2KE7LMNosCybV5fbe++9V9K1a1fNyZMnC0xNTdnbt2/nsyybt3v37kIfHx9NXcZ65513Sh0dHbUikYht3bq1tk+fPupDhw4VlD9eWFiY9+6775ZaWlqyUqmUDQkJUT148CC/4hh//PFH/oABA1QSiYRt1aqVNjIysqS0tFTvdRISEgp8fHw0IpGIdXFx0X711VdFdckzNzc3DwCrVCpZQgghhDRMUVERm5qayhYVFTV3Ks+NxYsXs507d2608V999VV2zJgxLMuy7NSpU9kpU6bUa5yG5lnTz45SqWQBsLm5ubXWdnWeAY6Pjxfu2rWrKCAgQFNx4bi3t7cmLS2tTksqtm/fXlzT4xKJBBs3bizeuHFjtXEuLi7skSNHimoap3///ppff/21+sU3hBBCCCEt3NWrVyGTybBy5cpKXRzqorCwEBs3bkRQUBD4fD527dqFhIQEHDt2DADg7e2Nnj171mnMe/fuwdPTE6WlpZU2+DWHOhfA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euP8pT89zyF/BEdEREREGYV7gImIiIgoo7AAJiIiIqKMwgKYiIiIiDIKC2AiIiIiyigsgHVo+fLlpuHDhzusVquztLTUvn37ds6TTm3atMlQXl5uKy4udgghnK+99lrExWUURcG9995rKSoqcthsNuf5559v//zzzzmfOvGrX/3KfM4552Q5nU5nQUGBY968ebbq6uqI+Wlvb8fixYuteXl5DofD4bz88sttx48fF6nKmSKtWLHCNHHixCyXy+V0uVzOGTNm2N98801DaDnnL7089NBDZiGEs7KyMnzaAM6h/lVVVVmEEM6u/8aMGZMVWq7HOeQbsc68+OKLxrvvvttaVVXl3b17t3vy5MlKeXl5Vk1NDZ/sOuR2uzF58mRlxYoVnmjLH330UfPKlSvNK1eu9Gzfvt2dlZWlXnrppfb29vZkp0pRbN682VhRUeHbtm2b+5133mnz+/245JJL7K2treE+t912m/Wtt94yrl27tn3jxo3u48ePi/nz59tSmDZ1MXToUPXRRx/17tq1y71z5073eeedJ8+fP9/+6aefSgDnL53s2LFDWr16tXnixIlK13bOYXoYP368cvTo0dbQvw8//LAttEyPc8jToOlMaWmpfdq0acqqVas8ACDLMoYOHeq45ZZbfFVVVb5U50e9E0I4X3311fYFCxYEgODe35KSEsftt9/uu+eee3wA0NjYiKKiIueaNWvar7nmmkBqM6buamtrRVFRkWPjxo1t559/vtzY2IjBgwc7n3vuufaFCxcGAGD//v3SmWeembV169a22bNny6nOmXrKy8tzPvroo56FCxf6OX/poaWlBVOnTs164oknPA8//LBlypQp8ooVK7x8DqaHqqoqyxtvvGHct2+fu/syvc4h9wDriNfrxd69ew1z5swJF0YGgwEXXHBBYMeOHYZYY0l/vv76a1FbWysuuuii8Hzm5OSgtLRU3r59O+dTh5qamgAA+fn5KgDs2rXL4Pf7cfHFF4fncMKECcrQoUPVbdu2cQ51JhAI4IUXXjC63W7Mnj1b5vylj4qKCuvcuXMDl1xySUQxxDlMH1999ZVUXFzsGDlypOOqq66yffvttwLQ7xwatbtQstTV1QlZllFYWKh2bR88eLDK40bTz/HjxyUAKCoq6jGfNTU1nE+dkWUZt912m7WsrEyePHmyAgA1NTXCbDYjNzc3om/HHPKwJJ345JNPpNmzZ2d5PB44HA68+uqr7RMnTlT27t1r5Pzp3wsvvGDcu3evYffu3T32HvI5mB5mzpwZWLNmjTxu3Djl2LFj4sEHH7Sce+65WZ999lmrXueQBTAREYJ7oPbv32/YsmVLjzdh0rdx48Ype/bsaW1sbBSvvPKK6ac//an1/fffb9MeSal26NAhsWTJEuv69evbbDYe1puuLrvssvCe+7POOgtlZWVtI0aMcL700ksmu92uxhqbKtwLpSMFBQWqwWBAbW1txCeiEydOiO57hUn/iouLw3sRu7afOHFCFBUVKdFHUSpUVFRY161bZ9y4caN72LBh4edaUVGR6vP50NDQENG/Yw75nNQJi8WCMWPGqNOnT1d+85vfeCdNmqQsW7bMzPnTv927dxvq6urEtGnTsoxGo9NoNDq3bNliePLJJ81Go9FZWFjIOUxDubm5GD16tHLw4EFJr89DFsA6YrFYcPbZZ8sbNmwI75mXZRmbNm0yzpw5kwf6p5nTTz9dLSwsVN97773wfDY1NWHXrl2GsrIyzqcOKIqCiooK61//+lfjhg0b2kaNGhXxYlxaWiqbTCa8++674Tmsrq6WvvvuOzFr1izOoU6pqgqv18v5SwMXXXRR4JNPPnHv2bMn/G/q1KnKVVdd5d+zZ497+vTpnMM01NLSgm+++UYqLi5W9fo85CEQOrNkyRLfokWLbNOmTZNnzJghL1261NzW1iZ+9rOf+VOdG/XU0tKCL774IvxB8uuvv5Y++ugjKT8/Xx0xYoRaWVnp+/Wvf20ZM2aMcvrppytVVVWW4uJidf78+TwDhA5UVFRY165da3r99dfbnE6neuzYMQEAOTk5qt1uR05ODm688Ub/XXfdZc3Ly2t3uVzqrbfeap0xY4bMX5/rw89//nNLeXl5YPjw4UpLS4t4/vnnTZs3bzasW7fOy/nTP5fLhdAx9yFZWVlqfn6+GmrnHOrfkiVLLPPmzQuMGDFCOXr0qPTAAw9YJElSr7nmGr9en4c8DZoOLVu2zPT4449bamtrxeTJk+Xly5d7+UlXnzZs2GCYM2eOvXv7tdde63/uuec8iqKgqqrKsmbNGlNTU5MoKyuTV61a5Rk3bhwPgdABIYQzWvsf//hHz0033eQHgidwX7JkifXll182eb1ezJkzJ7Bq1SpPSUkJv37VgRtvvNG6adMmY01NjXC5XOrEiROVX/ziF95LL71UBjh/6ejcc8+1h06DBnAO08EVV1xh27p1q6G+vl4MGjRInTVrlvzII494zjjjDBXQ5xyyACYiIiKijMJjgImIiIgoo7AAJiIiIqKMwgKYiIiIiDIKC2AiIiIiyigsgImIiIgoo7AAJiIiIqKMwgKYiIiIiDIKC2AiIiIiyigsgImI0sR1111nnTdvni3Z6129erVJCOEUQjgrKystsfoOHz7c8dvf/tbc9e/Q2IaGhsQnS0QUB2OqEyAiot4vyxxy3333+VasWOFR1dRcOdTlcqG6urrV4XD0KYGdO3e6N2/ebLjyyiuTXrgTEfWGBTARkQ4cPXq0NfT/l156yfTggw9aqqurw21Op1N1OmPWyAklhEBJSUmfq+/CwkI1Ly8vNVU7EVEveAgEEZEOlJSUqKF/2dnZaqjgDP1zOp09DoE499xz7bfccou1srLSkpub6xw8eLBj1apVptbWVlx//fVWp9PpHDVqlOPNN980dF3Xvn37pIsvvtjucDicgwcPdlx99dXWuro60deca2pqRHl5uc1mszlHjBjhePbZZ7lThYjSAgtgIqI09vzzz5vy8/PVHTt2uG+55RZfZWWldcGCBbaysjJ59+7d7jlz5gRuuOEGm9vtBgA0NDTgwgsvtJ911lnyzp073evWrWurra2Vrrjiij4fonDDDTdYjxw5Ir333nttL7/8ctuqVavM/SmkiYiSjQUwEVEamzRpkvzLX/7SN3bsWKWqqspntVoxaNAgtaKiwj927FjlgQce8NbX14uPP/7YAADLly83T5kyRXnssce8EyZMUKZNm6b86U9/av/ggw8MBw4ciPs94cCBA9L69euNTz31VPvs2bPl6dOnK2vWrPG0t7cn7s4SEQ0Qfl1FRJTGJk2apIT+bzQakZeXp06cODHcVlRUpALAiRMnBADs27fPsHnzZoPD4ehxQPHBgwfFuHHj4lrv/v37JaPRiNLS0vC6JkyYoOTk5PT/zhARJQkLYCKiNGYymSJ+YCaEgMlkCv8tScGduooSrFNbW1tFeXl54LHHHvN0j9WfH7kREaUjFsBERBnk7LPPlv/85z8bR44cqXYtlPtq/PjxSiAQwK5du6SZM2cqAFBdXS01NjYOVKpERAnDY4CJiDJIZWWlr6GhQSxcuNC2Y8cO6csvvxTr1q0zXH/99dZAIBB3nPHjxysXXXSRvHjxYtu2bdsMO3fulG666SarzcbT/RKR/rEAJiLKIKeddpq6devWNlmWMXfu3KwpU6Y4lixZYs3JyVFDh0vE6+mnn24vLi5WLrjgAvuPf/xj+8033+wvKCjgYRREpHtCVdXmVCdBRET6tXr1atNdd91lbWxsbOnP+A0bNhjmzJljr6+vb8nNzR3o9IiI+ox7gImISFNTUxMcDofzzjvvtPRl3Pjx47Muu+wye6LyIiLqD+4BJiKimJqbm1FTUyMAIDc3F305zOGbb74Rfr8fADBq1CjVYDBojCAiSjwWwERERESUUXgIBBERERFlFBbARERERJRRWAATERERUUZhAUxEREREGYUFMBERERFlFBbARERERJRRWAATERERUUZhAUxEREREGeX/AzDBTK6xE8BAAAAAAElFTkSuQmCC\n", "text/plain": [ "
" ] @@ -876,25 +718,21 @@ ], "source": [ "# Need to rerun the simulation before retrieving results\n", - "# user shorter time or try changing method to 'RK23' (explicit solver) if it takes a long time\n", "sys.simulate(t_span=(0, 50), method='BDF', state_reset_hook='reset_cache')\n", "fig, ax = C1.scope.plot_time_series([f'TSS{i}' for i in range(1,11)])" ] }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 14, "id": "68dcbad5", "metadata": { - "scrolled": false, - "slideshow": { - "slide_type": "slide" - } + "scrolled": false }, "outputs": [ { "data": { - "image/png": "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NlLl9Wq1GrGxsXJeXp5Qvo0iISGhyjF8x/Sd40Jz5szRms1mo+8jOTnZGLwrIyIiIqJQCtsAHEozZ850WiyWYt/HsWPHiuvfi4iIiIiaA1X9m4RGUlKSBHh7edu1a1fRg1tYWCj07NlTBLw9uadPn64S4t1uN86dOyckJSXJ5dtIBQUFQuVt8vPzhcrnuJBOp4NOpwvuBRERERFRWAjbHuBLLrlETkhIkDds2FAR0q1WK7Kzs5X9+/cXAWDgwIGixWJBVlZWxXVs2LBBKUkSrrnmGhEArrnmGnH79u0ql8tVceyvv/5addlll0lxcXFNeEVEREREFA5CGoCLi4vx/fffK77//nsFAPz222+K77//XnH06FFBoVAgMzPTNW/ePO0XX3yh2r9/v2LMmDH6pKQkefTo0R4A6N69uzRkyBDxz3/+s37nzp2KLVu2KB977DHdnXfe6Wnfvr0MAGPHjnWr1Wr5gQce0B08eFDxwQcfqN566y3NlClTXHW1jYiIiIhappCWQdu4caNy8ODBhguXjxkzxr18+XKHJEmYOXOmdunSpWqr1Sr0799fXLx4saNr164VQxfOnj2LSZMm6desWaNSKBS4/fbb3W+++abDaDz/3Nq+ffsUf/nLX3Tff/+9slWrVvKkSZNcM2bM8DsAswwaERERUXgLpAxa2NQBDmcMwEREREThrUXUASYiIiIiagwMwEREREQUURiAiYiIiCiiMAATERERUURhACYiIiKiiMIATEREREQRhQGYiIiIiCIKAzARERERRRQGYCIiIiKKKAzARERERBRRGICJiIiIKKIwABMRERFRRGEAJiIiIqKIwgBMRERERBGFAZiIiIiIIgoDMBERERFFFAZgIiIiIoooDMBEREREFFEYgImIiIgoojAAExEREVFEYQAmIiIioojCAExEREREEYUBmIiIiIgiCgMwEREREUUUBmAiIiIiiigMwEREREQUURiAiYiIiCiiqELdAKpB0Ung7QGAswTQRgN/3gHEtQt1q4iIiIhaBAbgcPNCG0Bynf/aYQH+kQooNMBzp0PWLCIiIqKWgkMgwsmF4bcyyeVdT0REREQXhQE4XBSdrD38+kgu73ZERERE1GAMwOHirT7B3Y6IiIiIasQAHC7E0uBuR0REREQ1YgAmIiIioojCAExEREREEYVl0IiImjtJBH7fAZQU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\n", + "image/png": "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R48e0rRp03TLli2z5uTkCAsWLNA8+OCDNq3WGZBmz55tW7VqVcjcuXM1M2fOtG/dulX5ySefqNavX+9RBQhqXoM7RcCoVcFirXuyixjO9EZERERXwedVIOqzfPly6/jx4x233367fsSIEaHR0dHyp59+WuZar1KpsGHDhlKlUilff/31odOnT9dNmzbNvmTJkoqL6rp06SKvX7++dNu2bar+/fuHvvrqq5pVq1ZZWQPYP/2UU4RSW+2nhjO9ERERUWPweR3gQMA6wM2j1ObAxNd248T5EiS0MeJCiQ05lS6IizVpsWBiPGd6IyIiohq8qQPsFzPBEQHAoi8ycOJ8CaIMGvzfvYNh1odwpjciIiJqdAzA5BOVpzmOMmiRZ7Hig/TTEATg1an9EBmmAQAM7RLp45YSERFRS8MATM2utmmOXf26s2/sgmFdW/mmYURERBQUGICpWbmmOa4+j5vrcc9YjrEmIiKipuXXVSCoZXE3zTEALPkyk9McExERUZNiAKZm426aY4DTHBMREVHTYwCmZsNpjomIiMgfMABTs+E0x0REROQPGICp2QzuFIFYU93hVoBzsgtOc0xERERNiQGYmo1SISB1Qs9a13GaYyIiImouLINGTa7ypBfpJ50XuAlAlWoQMZzmmIiIiJoJAzA1qdomvQCAOwe3x8S+bTnNMRERETU7jwLwZ5995nVQHjt2rEOv13vfImox6pr0AgDeTzuN4d1b45Z+bZu9XURERBTcPAq2t912m86bgwqCgJ9++qm4a9eunNEgSHky6cVzX2RgdHwMe36JiIioWXl8Edy5c+eKJUkq8uTGnl9yN+mFDE56QURERL7hUQCeNm2aXa/Xe9ybe+edd9pNJlPDW0UBj5NeEBERkb/yaAjEO++841VKefPNN5lqghwnvSAiIiJ/1Wh1gDMyMhTdunULbazjUWDjpBdERETkrxotAFutVvz222+cWIMAOCe9uH1g+1rXcdILIiIi8iUGVmoSoiRj8w85AAB9iLLKuhiTFiunDeCkF0REROQTnAiDmsRH+0/jx5wimHRqbH/sD/g5t5iTXhAREZFfYACmRuOa8vhUfgle3JgJAHhkZDdEhmkwNEzj49YREREROXkcgMPDww2CUHevncPhaJQGUWCqbcpjpUJAlJHBl4iIiPyLxwH473//O0ubUa3qmvJYlGSkvHcIKoXA8b5ERETkNzwOwMOHD3dwamOqjlMeExERUaDxuApEv379wuLj40P/+te/avbu3cvqEQSAUx4TERFR4PE4yJ4/f77ohRdeKD9//rxw66236mNiYsLuvfde7f/+9z9VWVlZU7aR/BinPCYiIqJA43EA1ul0uPXWWx1vv/22NTs7u/i///1vWUREhPzkk09qWrdubZg4caLuzTffVOfm5vLv3EGEUx4TERFRoGnQUAaFQoEbbrhBfPnll8t//PHHkgMHDpRcf/314jvvvKOOi4sLW758ubqxG0r+iVMeExERUaBplLG811xzjfTEE0/Ydu/eXXr27NnipKQksTGOS/5PqRBw93Udal3HKY+JiIjIH3k9EcZnn31W6z6CIECr1crdu3eXrrnmGunqm0aBIu2k8wI3nVqJMvuV7z4xJi0WTIxnCTQiIiLyK14H4Ntuu00nCAJkuWrhK9cyQRAwbNgw8fPPPy+NiOCfvVu6g6cu4ZufzkOpEPDlw9cj11LOKY+JiIjIr3k9BGLTpk2l1157rbhp06bSgoKCooKCgqJNmzaVDho0SPz888/LduzYUXrx4kVh7ty5vOqphRIlGXtPXMTnh89i4fofAACT+7dF59ZhGNolErf0a4uhXSIZfomIiMgved0D/Oijj2r/9a9/WW+44YaKv3WPGTNG1Gq15Q8++KA2MzOz5JVXXrHOnDlT17hNJX9Q25THANCnndk3DSIiIiLyktcBOCsrS2EymWpM/GUymeSTJ08qAKB79+7SxYsX2f3XwtQ15TEAPPv5cbQ2hHC8LxEREfk9r4dA9O/fX5w3b562cr3f3Nxc4fHHH9dee+21IgD8/PPPinbt2vFCuBbE0ymPRYmzZRMREZF/8zoAr1mzxnry5EkhLi4urEuXLmFdunQJi4uLC/v999+F1atXWwGguLhYePrpp22N31zyFU55TERERC2F10MgevbsKWVmZpZs2rRJ+dNPPykBoEePHuLYsWNFpVIJALjtttscjdxO8jFOeUxEREQthdcBGACUSiUmTJggTpgwgRNeBAlOeUxEREQtRYMC8L59+xTbt29XnT9/XpCkqkN9ly9fXt4oLSO/4pryuK5hEAKcE19wymMiIiLyd14H4EWLFoUsXLhQ061bNyk6OloWhCvFHirfp5ZFqRCwYGI8Zr17sMY6TnlMREREgcTrALxi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Onr0aInFYhFSUlJ4FVYDiXLDvzew/BkRERGR57zuAd6yZYtq8+bNJb169aoY8pCQkCCtWLGibNy4caGN27zgcb6ovMH7svwZERERkee87gGWJAnVS58BgFqthiRxGHBDSXLD3zuWPyMiIiLynNcB+A9/+IPj0Ucf1Z45c6bib+6nT58W5s6dq73xxhsbPpA1yOVZGlZcQyEA13YIb+TWEBEREbVcXgfg119/3WqxWITOnTuHuW5dunQJs1gswooVK6xN0chgEG3SNGg/SQYO/H6pkVtDRERE1HJ5PQa4Q4cO8qFDh0q+/vprZWZmpgIA4uPjpbFjx4qN37zgEW1oWAAGgLwifu8gIiIi8lSD6gArFAokJSWJSUlJDL2NxKgNafC+HANMRERE5DmPh0Bs2bJF2aNHj9DCwsIa6woKCtCzZ8/Qb775RtmorQsi2Q2cCtmsV7MGMBEREZEXPA7Ar776ash9991nN5lMNdaZzWbcf//99n/84x8N78YMcueLG1YGbcbQjqwBTEREROQFjwPwsWPHlOPHj6+zykNSUpLj0KFD7AFuIKutYaNJBnVk7y8RERGRNzwOwHl5eYJara5zujKVSiVfuHCBXZENZLU3LABfKGn4BBpEREREwcjjANymTRv52LFjdfbwHjlyRBkTE9Pw+XyDXEGpvUH78QI4IiIiIu94HICTkpIczz77rKasrObFWqWlpVi4cKFm/PjxDUtxhNIGDIEQAF4AR0REROQlj8ugPfPMM+X/+9//Qrt37x6WnJxs69GjhwQAmZmZilWrVoWIoojU1NSGTWdGKLF5P4meSiHwAjgiIiIiL3kcgGNjY+XvvvuuZNasWdpnnnlGI8vO0Q6CIGDUqFGON954wxobG8shEA1kl7zfp3WYuvEbQkRERNTCeTURRqdOneTNmzeX5efn4+eff1bIsoxrrrlGiojgn+F94S83dfV1E4iIiIgCToNmgouIiMB1113XgD5LakydWxt93QQiIiKigOPxRXDUtG7p3car7c06zgBHRERE1BAMwH6ibaTOq+17tTHyAjgiIiKiBmAA9hMmnXcXtJ28UNJELSEiIiJq2RiA/URBmXcV5IrLvS+bRkREREQMwH4ju8Dq1fZtzZwBjoiIiKghGID9RKyXgfbx0T2aqCVERERELRsDsJ8w60K82n54z6gmagkRERFRy8YA7CcsVrvH26oEsAIEERERUQMxAPsJ2YtJpEVOOE1ERETUYAzAfkJQeD6xHvMvERERUcMxAPuJ//vulK+bQERERBQUGID9hNUu+roJREREREGBAdhPmL2YCe7vt/ZqwpYQERERtWwMwH5iQ8pwj7e9dXCHJmwJERERUcvGAOwnYsxa6NTuT8eqaQNYAo2IiIjoKjAA+5HM58fVG4JXTRuApITYZmwRERERUcuj8nUDqKrM58chp8CKCf/8FoVWB3RqJZZO7ouk3jHs+SUiIiJqBAzAfijGrMWBZ8f6uhlERERELRKHQBARERFRUGEAJiIiIqKgwgBMREREREGFAZiIiIiIggoDMBEREREFFQZgIiIiIgoqDMBEREREFFQYgImIiIgoqPg0AO/YsUM5fvx4XWxsbJggCIZPPvmkysQckiTh6aef1sTExITpdDrDiBEj9D/99FOVNl+8eBF33HGHzmg0Gsxms2HGjBnaoqKiKs9z+PBhRWJiol6r1RratWsX9sILL4Q0w8sjIiIiIj/k0wBcUlKCPn36SK+99pq1tvUvvvhiyBtvvBHyxhtvWPfu3VsSGhoqJyUl6cvKyiq2ufPOO/UZGRmKTZs2lX7++eelu3fvVs6cOVPnWl9YWIixY8fq4+LipPT09JKXXnrJunjxYs0bb7yhboaXSERERER+RpBl2eLrRgCAIAiGjz/+uOy2225zAM7e3zZt2oQ9+uijtieffNIGAAUFBYiJiTGsWbOm7K677nL88MMPioSEhNB9+/aVDBkyRAKAL7/8Ujlx4kT9qVOnitu1aye/9tpr6gULFmizs7OLNBoNAGDevHma9evXq37++ecST9pWWFgIs9lsKCwshNFobKJ3gIiIiIgaymKxwGQyoaCgoMhkMtW7rd+OAf7tt9+E3NxcYfTo0Q7XMrPZjEGDBol79+5VAsB3332nNJvNcIVfABgzZoyoUCiwb98+JQDs27dPmZiY6HCFXwBISkpy/PLLL4r8/Pxan9tqtaKwsLDiZrH4xXcEIiIiImoEfhuAs7OzFQAQExMjV14eFRUl5+TkKAAgJydHaN26tVR5vVqtRnh4uJydnS1c3kYRHR1d5RiuY7qeo7rFixdrzGazwXWLi4szNN4rIyIiIiJf8tsA7EupqanlBQUFRa7bqVOnitzvRURERESBQOV+E9+IjY2VAGcvb9u2bSt6cPPy8oS+ffuKgLMn9/z581VCvN1ux6VLl4TY2Fj58jZSbm6uUHmbnJwcofJzVKfVaqHVahv3BRERERGRX/DbHuDOnTvL0dHR8tatWytCemFhIdLT05VDhw4VASAxMVEsKChAWlpaxevYunWrUpIkXHfddSIAXHfddeJ3332nstlsFcf++uuvVd26dZMiIiKa8RURERERkT/waQAuKirCgQMHFAcOHFAAwG+//aY4cOCA4uTJk4JCoUBKSopt6dKlms8++0x15MgRxbRp03SxsbHy5MmTHQDQq1cvafTo0eIDDzyg27t3r2Lnzp3Khx9+WDtlyhRHu3btZAC4++677Wq1Wr7nnnu0x44dU7z33nuq119/PeTRRx+11dc2IiIiImqZfFoGbdu2bcpRo0bpqy+fNm2afe3atVZJkpCamqpZs2aNurCwUBg6dKi4cuVKa48ePSqGLly8eBGzZ8/Wbdy4UaVQKHDrrbfaV6xYYTUYrly3dvjwYcVf/vIX7YEDB5SRkZHy7NmzbfPnz/c4ALMMGhEREZF/86YMmt/UAfZnDMBERERE/q1F1AEmIiIiImoKDMBEREREFFQYgImIiIgoqDAAExEREVFQYQAmIiIioqDCAExEREREQYUBmIiIiIiCCgMwEREREQUVBmAiIiIiCioMwEREREQUVBiAiYiIiCioMAATERERUVBhACYiIiKioMIATERERERBhQGYiIiIiIIKAzARERERBRUGYCIiIiIKKgzARERERBRUGICJiIiIKKgwABMRERFRUGEAJiIiIqKgwgBMREREREGFAZiIiIiIggoDMBEREREFFQZgIiIiIgoqDMBEREREFFQYgImIiIgoqKh83QCqRf5Z4M1hQHkxoAkDHtgDRLT1dauIiIiIWgQGYH+zqDUg2a48thYA/4wHFCHAs+d91iwiIiKiloJDIPxJ9fBbmWRzriciIiKiq8IA7C/yz9Ydfl0km3M7IiIiImowBmB/8fqgxt2OiIiIiGrFAOwvxJLG3Y6IiIiIasUATERERERBhQGYiIiIiIIKy6ARETU1SQR+3wMU5wJh0UCHYYBC6b/HbQ5su2+w7S2bP71H/tSWWjAAEwW7QAxngdTmjPXApicAy7kry4xtgKSXgPhJ/nfc5sC2+wbb3rL503vkT22pgyDLssXXjfB3hYWFMJvNhsLCQhiN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\n", "text/plain": [ "
" ] @@ -910,55 +748,27 @@ { "cell_type": "markdown", "id": "0eb92bf1", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, + "metadata": {}, "source": [ "So far we've learned how to simulate any dynamic system developed with QSDsan. \n", - "A complete list of existing unit operations within QSDsan is available [here](https://qsdsan.readthedocs.io/en/latest/api/sanunits/_index.html). The column \"Dynamic\" indicates whether the unit is enabled for dynamic simulations. Any system composed of the enabled units can be simulated dynamically as we learned above." - ] - }, - { - "cell_type": "markdown", - "id": "497d72b8", - "metadata": { - "slideshow": { - "slide_type": "skip" - } - }, - "source": [ + "A complete list of existing unit operations within QSDsan is available [here](https://qsdsan.readthedocs.io/en/latest/api/sanunits/_index.html). The column \"Dynamic\" indicates whether the unit is enabled for dynamic simulations. Any system composed of the enabled units can be simulated dynamically as we learned above.\n", + "\n", "[Back to top](#top)" ] }, { "cell_type": "markdown", "id": "3d13e036", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, - "source": [ - "### 1.2. When is a system \"dynamic\"?\n", - "It's ultimately the user's decision whether a system should be run dynamically. This section will cover the essentials to switch to the dynamic mode for system simulation." - ] - }, - { - "cell_type": "markdown", - "id": "94eab6a5", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, + "metadata": {}, "source": [ + "### 1.2. What makes a system \"dynamic\"?\n", + "It's ultimately the user's decision whether a system should be run dynamically. This section will cover the essentials to switch to the dynamic mode for system simulation.\n", + "\n", "#### `System.isdynamic` vs. `SanUnit.isdynamic` vs. `SanUnit.hasode` \n", "\n", "- Simply speaking, when the `.isdynamic == True`, the program will attempt dynamic simulation. Users can directly enable/disable the dynamic mode by setting the `isdynamic` property of a `System` object.\n", "\n", - "- The program will set the value of `.isdynamic` when it's not specified by users. `.isdynamic` is considered `True` in all cases except when `.isdynamic == False` for all units.\n", + "- The program will deduct the value of `.isdynamic` when it's not specified by users. `.isdynamic` is considered `True` in all cases except when `.isdynamic == False` for all units.\n", "\n", "- Setting `.isdynamic = True` does not gaurantee the unit can be simulated dynamically. Just like how the `_run` method must be defined for static simulation, a series of additional methods must be defined to enable dynamic simulation.\n", "\n", @@ -967,13 +777,9 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 15, "id": "c130f36f", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, + "metadata": {}, "outputs": [ { "data": { @@ -986,7 +792,7 @@ " : True}" ] }, - "execution_count": 17, + "execution_count": 15, "metadata": {}, "output_type": "execute_result" } @@ -996,14 +802,407 @@ "{u: u.hasode for u in sys.units}" ] }, + { + "cell_type": "code", + "execution_count": 16, + "id": "b6a0612a", + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + "M1Mixer:e->A1CSTR:c\n", + "\n", + "\n", + "\n", + " ws26\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "A1CSTR:c->A2CSTR:c\n", + "\n", + "\n", + "\n", + " ws11\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "A2CSTR:c->O1CSTR:c\n", + "\n", + "\n", + "\n", + " ws13\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "O1CSTR:c->O2CSTR:c\n", + "\n", + "\n", + "\n", + " ws15\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "O2CSTR:c->O3CSTR:c\n", + "\n", + "\n", + "\n", + " ws17\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "O3CSTR:c->M1Mixer:c\n", + "\n", + "\n", + "\n", + " RWW\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "O3CSTR:c->C1Flat bottom circular clarifier:c\n", + "\n", + "\n", + "\n", + " treated\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "C1Flat bottom circular clarifier:c->M1Mixer:c\n", + "\n", + "\n", + "\n", + " RAS\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "C1Flat bottom circular clarifier:c->J1ASMto ADM:c\n", + "\n", + "\n", + "\n", + " WAS\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "C1Flat bottom circular clarifier:c-> effluent:w\n", + "\n", + "\n", + " effluent\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "J1ASMto ADM:c->AD1Anaerobic CSTR:c\n", + "\n", + "\n", + "\n", + " ws21\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "AD1Anaerobic CSTR:c->J2ADMto ASM:c\n", + "\n", + "\n", + "\n", + " ad eff\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "AD1Anaerobic CSTR:c-> biogas:w\n", + "\n", + "\n", + " biogas\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "J2ADMto ASM:c->M1Mixer:c\n", + "\n", + "\n", + "\n", + " ws25\n", + "\n", + "\n", + "\n", + "\n", + "\n", + " wastewater:e->M1Mixer:c\n", + "\n", + "\n", + " wastewater\n", + "\n", + "\n", + "\n", + "\n", + "\n", + " filler0:e->A1CSTR:c\n", + "\n", + "\n", + " filler0\n", + "\n", + "\n", + "\n", + "\n", + "\n", + " filler1:e->A1CSTR:c\n", + "\n", + "\n", + " filler1\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "M1Mixer\n", + "\n", + "\n", + "M1Mixer\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "A1CSTR\n", + "\n", + "\n", + "A1CSTR\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "A2CSTR\n", + "\n", + "\n", + "A2CSTR\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "O1CSTR\n", + "\n", + "\n", + "O1CSTR\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "O2CSTR\n", + "\n", + "\n", + "O2CSTR\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "O3CSTR\n", + "\n", + "\n", + "O3CSTR\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "C1Flat bottom circular clarifier\n", + "\n", + "\n", + "C1Flat bottom circular clarifier\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "J1ASMto ADM\n", + "\n", + "\n", + "J1ASMto ADM\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "AD1Anaerobic CSTR\n", + "\n", + "\n", + "AD1Anaerobic CSTR\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "J2ADMto ASM\n", + "\n", + "\n", + "J2ADMto ASM\n", + "\n", + "\n", + "\n", + "\n", + "\n", + " wastewater\n", + "\n", + "\n", + "\n", + "\n", + " effluent\n", + "\n", + "\n", + "\n", + "\n", + " biogas\n", + "\n", + "\n", + "\n", + "\n", + " filler0\n", + "\n", + "\n", + "\n", + "\n", + " filler1\n", + "\n", + "\n", + "\n", + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Units without ODEs can also be simulated dynamically as long as \n", + "# the fundamental methods are defined. Here is an example.\n", + "from exposan import interface as inter\n", + "inter.load()\n", + "inter.sys.diagram()" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "f2a81479", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{: False,\n", + " : True,\n", + " : True,\n", + " : True,\n", + " : True,\n", + " : True,\n", + " : True,\n", + " : False,\n", + " : True,\n", + " : False}" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "{u: u.hasode for u in inter.sys.units}\n", + "# inter.sys.isdynamic" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "be199e8d", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "C:\\Users\\joy_c\\Dropbox\\PhD\\Research\\QSD\\codes_developing\\QSDsan\\qsdsan\\sanunits\\_junction.py:573: UserWarning: Ignored dissolved H2 or CH4.\n", + " warn('Ignored dissolved H2 or CH4.')\n" + ] + }, + { + "data": { + "text/plain": [ + "(,\n", + " ,\n", + " ,\n", + " ,\n", + " )" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "uf = inter.sys.flowsheet.unit\n", + "inter.sys.simulate(t_span=(0,3), method='BDF', state_reset_hook='reset_cache')\n", + "inter.sys.scope.subjects" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "ff2ea29e", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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0tsMBoOtsnqqqDrn/cYbb7QtWbLE/MUXX5j++9//FtpsNtx8880hZ8+e5Xn3EqPRKERHR5f5HsXExEi5ubkwmdhB7C2NGzeW3n33XfPnn39u+vzzzwvj4uJsvXr10u3du5d/N6pJFEVMnDhR0717dzExMdFWWR1f3z1L7nnn67tnHD58WKHX60M1Gk3o448/rl2zZk1h+/btnZ53f/pZ5yB9IjfcfPPN4s033yyW+rqwTZs2+oULFwbPmjXL4su2EXlSmzZtbG3atCn5Q3XLLbeIp06dUsybNy941apV/Ni+GsaNG6c5duyYctu2bQWuq8lT5J53vr57xo033mg7cOBAfnZ2tvD5558HPfzww5qtW7eaKgvF/oLv9P1AVFSUpFQqUf4dUWZmplDZBSwGg0HKyMgo8/0zGo0KXvAiX3XOe3nBwcHo0KGD+Mcff/B3yUsMBoOUmZlZ5nuUkZEhhIWFQafT+apZAalLly78Wa+mcePGaTZs2KDavHlzQdOmTat8feHru+e4c97L4+t79ajVarRu3Vrq2rWrbe7cuZaEhATb22+/Heys1p9+1vlN9gNqtRo33XSTmJ6eXtJjL4oitmzZourWrZvTcTfJycni5s2by4yxSU9PV1ZWTxVV57yXZ7Va8csvvyhiY2P9+p1vXXb1Z73Mp1lpaWmqrl278me9lh0+fFgZExPDUOYGm82GcePGab766itVenq6qWXLli7PH1/fa6465708vr57hiRJsFicd7D70886h0z4iaeeeqpo1KhR2i5duojJycnivHnzgk0mkzB69OhiABg+fLimcePG0ty5cy0AMHHixKKUlBTdnDlzggcOHGhdtWpV0IEDB5SLFy/mR5lucPe8T506Nbh79+5iq1atbFeuXBHmzp2rPnfunGLMmDEVrp4l5/Ly8nDy5MmSN+OnTp1S7N+/XxEZGSk1a9ZMmjRpkvr8+fPCypUrzQAwfvz4okWLFgU//fTT6kceeaQ4LS1NuXbtWtW6des4gNgN7p73N954I7hFixa29u3bi2azWViyZEnQDz/8oNy4cSPPuxvGjRun+fTTT4P+97//mUJDQyXHPM4RERGS4xMOvr57XnXOO1/fa27SpEnqAQMGWOPj4215eXnCihUrgn788Uflhg0bLIB//6wzEPuJBx54wJqZmWmePn262mg0ComJieL69etNsbGxEgCcO3dOoVAoyoznW758eeHUqVPVU6dOVbds2dK2du3awqouGKCK3D3v2dnZwqOPPqo1Go1CRESEdNNNN4nbtm0r8PexUf5kz549yt69e5eMdZg8ebIagHrEiBHFy5cvN2dkZAjnzp0rCW4tW7aU1q1bZ3r66ac1//nPf4KbNGkiLVq0yDxgwAD2lrnB3fNeVFSESZMmac6fPy9otVopISHBtmnTJlPv3r153t2wePHiIADo1atXmfE9S5YsMT/yyCPFAF/fvaE6552v7zWXlZUljBw5Unt1WJvUvn1724YNG0z9+/cXAf/+Wec8xEREREQU0DiGmIiIiIgCGgMxEREREQU0BmIiIiIiCmgMxEREREQU0BiIiYiIiCigMRATERERUUBjICYiIiKigMZATEREREQBjYGYiKgO+/e//60ZOHCgtraPu3Tp0iBBEEIFQQidMGGCuqra+Ph4/RtvvBFc+mvHY69cueL9xhIRucClm4mI/JQgCKFV3f/iiy8WLViwwCxJUm01qYywsDAcP348X6/Xu9WAPXv2FPz444/Ke++9t9aDPBGRMwzERER+6u+//853/PuTTz4JeuWVV9THjx8v2RYaGiqFhlaZmb1KEAQ0btzY7TRuMBikhg0b+ibFExE5wSETRER+qnHjxpLjFh4eLjkCqOMWGhpaYcjErbfeqhs/frxmwoQJ6gYNGoRGR0frFy5cGJSfn48HH3xQExoaGtqyZUv9N998oyx9rCNHjij69u2r0+v1odHR0foHHnhAk5WVJbjb5oyMDGHAgAFarVYb2qxZM/1///tfdrwQkd9jICYiqmdWrFgRFBkZKe3atatg/PjxRRMmTNAMGTJE2717d3Hfvn0FvXv3tj700EPagoICAMCVK1fQq1cvXceOHcU9e/YUbNiwwWQ0GhXDhg1ze0jDQw89pPnrr78UaWlpps8++8y0cOHC4OoEayKi2sRATERUzyQkJIjTp08vuuGGG2ypqalFGo0GjRo1ksaNG1d8ww032KZNm2a5fPmycOjQISUAzJ8/P7hDhw62119/3dK2bVtbly5dbB9++GHhDz/8oPz1119l/5349ddfFd99951q8eLFhT179hS7du1q++CDD8yFhYXee7JERB7Aj7KIiOqZhIQEm+PfKpUKDRs2lNq3b1+yLSYmRgKAzMxMAQCOHDmi/PHHH5V6vb7CgOTff/9duPHGG2Ud99ixYwqVSoWkpKSSY7Vt29YWERFR/SdDRFQLGIiJiOqZoKCgMhesCYKAoKCgkq8VCnunr81mz635+fnCgAEDrK+//rq5/L6qc9EcEVFdw0BMRBTgbrrpJvGLL75QNW/eXCodnN3Vpk0bm9Vqxd69exXdunWzAcDx48cV2dnZnmoqEZFXcAwxEVGAmzBhQtGVK1eEf/3rX9pdu3YpfvvtN2HDhg3KBx98UGO1WmXvp02bNrY+ffqIjz32mHbHjh3KPXv2KB555BGNVsvphonIvzEQExEFuOuuu07avn27SRRF3HnnnSEdOnTQP/XUU5qIiAjJMbxCro8++qgwNjbWlpKSohs6dKhuzJgxxVFRURx2QUR+TZAkKdfXjSAiorpl6dKlQc8++6wmOzs7rzqPT09PV/bu3Vt3+fLlvAYNGni6eUREbmEPMRERVUtOTg70en3oM888o3bncW3atAn5xz/+ofNWu4iI3MUeYiIicltubi4yMjIEAGjQoAHcGRZx+vRpobi4GADQsmVLSalUungEEZF3MRATERERUUDjkAkiIiIiCmgMxEREREQU0BiIiYiIiCigMRATERERUUBjICYiIiKigMZATEREREQBjYGYiIiIiAIaAzERERERBbT/B5G9HGMhttiTAAAAAElFTkSuQmCC\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "fig, ax = uf.AD1.scope.plot_time_series(('S_ch4_gas', 'S_h2_gas', 'S_IC_gas'))" + ] + }, { "cell_type": "markdown", "id": "7839f0e2", - "metadata": { - "slideshow": { - "slide_type": "skip" - } - }, + "metadata": {}, "source": [ "[Back to top](#top)" ] @@ -1011,11 +1210,7 @@ { "cell_type": "markdown", "id": "33a3d638", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, + "metadata": {}, "source": [ "## 2. Writing a dynamic `SanUnit` \n", "\n", @@ -1025,11 +1220,7 @@ { "cell_type": "markdown", "id": "220c984a", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, + "metadata": {}, "source": [ "### 2.1. Basic structure \n", "\n", @@ -1037,57 +1228,39 @@ "\n", "In comparison, during dynamic simulations, all information are stored as `_state` and `_dstate` attributes of the relevant `SanUnit` obejcts as well as `state` and `dstate` properties of `WasteStream` objects. These information won't be translated to mass or energy flows until dynamic simulation is completed.\n", "\n", - "- `WasteStream.state` is a 1d `numpy.array` of length $n+1$, $n$ is the length of the components associated with the `thermo`. Each element of the array represents value of one state variable." - ] - }, - { - "cell_type": "markdown", - "id": "b1529db3", - "metadata": { - "slideshow": { - "slide_type": "subslide" - } - }, - "source": [ - "---\n", - "#### Tip\n", + "- `WasteStream.state` is a 1d `numpy.array` of length $n+1$, $n$ is the length of the components associated with the `thermo`. Each element of the array represents value of one state variable.\n", "\n", - "Typically for a liquid `WasteStream`, the first $n$ element represents the component concentrations \\[mg/L\\], while the last element represents the total volumetric flow \\[m3/d\\]. For a gaseous `WasteStream`, the first $n$ state variables can simply be the mass flows \\[g/d\\] of the components if the last element is fixed at 1. This is because after completing dynamic simulations, the `WasteStream`'s mass flow is defined as the first $n$ element of this array multiplied by the last element.\n", + " ---\n", + " #### Tip\n", + " \n", + " Typically for a liquid `WasteStream`, the first $n$ element represents the component concentrations \\[mg/L\\], while the last element represents the total volumetric flow \\[m3/d\\]. For a gaseous `WasteStream`, the first $n$ state variables can simply be the mass flows \\[g/d\\] of the components if the last element is fixed at 1. This is because after completing dynamic simulations, the `WasteStream`'s mass flow is defined as the first $n$ element of this array multiplied by the last element.\n", "\n", - "---" + " ---" ] }, { "cell_type": "markdown", "id": "d997b05d", - "metadata": { - "slideshow": { - "slide_type": "subslide" - } - }, + "metadata": {}, "source": [ "- `WasteStrem.dstate` is an array of the exact same shape as `WasteStream.state`, storing values of the time derivatives (i.e., the rates of change) of the state variables." ] }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 20, "id": "a8ae235a", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, + "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([3.000e+01, 8.899e-01, 4.389e+00, 1.886e-01, 9.784e+00, 5.720e-01,\n", - " 1.722e+00, 4.897e-01, 1.038e+01, 1.747e+00, 6.884e-01, 1.349e-02,\n", - " 4.954e+01, 2.751e+01, 9.978e+05, 1.806e+04])" + " 1.722e+00, 4.898e-01, 1.038e+01, 1.747e+00, 6.884e-01, 1.349e-02,\n", + " 4.954e+01, 2.751e+01, 9.986e+05, 1.806e+04])" ] }, - "execution_count": 18, + "execution_count": 20, "metadata": {}, "output_type": "execute_result" } @@ -1099,23 +1272,19 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 21, "id": "ab1496fd", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, + "metadata": {}, "outputs": [ { "data": { "text/plain": [ "sparse([3.000e+01, 8.899e-01, 4.389e+00, 1.886e-01, 9.784e+00, 5.720e-01,\n", - " 1.722e+00, 4.897e-01, 1.038e+01, 1.747e+00, 6.884e-01, 1.349e-02,\n", - " 4.954e+01, 2.751e+01, 9.981e+05])" + " 1.722e+00, 4.898e-01, 1.038e+01, 1.747e+00, 6.884e-01, 1.349e-02,\n", + " 4.954e+01, 2.751e+01, 9.988e+05])" ] }, - "execution_count": 19, + "execution_count": 21, "metadata": {}, "output_type": "execute_result" } @@ -1127,13 +1296,9 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 22, "id": "825050c1", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, + "metadata": {}, "outputs": [ { "data": { @@ -1141,7 +1306,7 @@ "True" ] }, - "execution_count": 20, + "execution_count": 22, "metadata": {}, "output_type": "execute_result" } @@ -1152,14 +1317,9 @@ }, { "cell_type": "markdown", - "id": "eb706d47", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, + "id": "eb706d47", + "metadata": {}, "source": [ - "\n", "- `SanUnit._state` is also a 1d `numpy.array`, but the length of the array is not assumed, because the state variables relevant for a `SanUnit` is entirely dependent on the unit operation itself. Therefore, there is no predefined units of measure or order for state variables of a unit operation.\n", "\n", "- `SanUnit._dstate`, similarly, must have the exact same shape as the `_state` array, as each element corresponds to the time derivative of a state variable." @@ -1167,13 +1327,9 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 23, "id": "956dbc0f", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, + "metadata": {}, "outputs": [ { "data": { @@ -1181,48 +1337,43 @@ "False" ] }, - "execution_count": 21, + "execution_count": 23, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "C1._state.shape == A1._state.shape\n", - "# C1._state.shape == C1._dstate.shape" + "C1._state.shape == A1._state.shape" ] }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 24, "id": "561a5589", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, + "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'S_I': 30.0,\n", - " 'S_S': 2.8098296544615704,\n", - " 'X_I': 1147.8970757884535,\n", - " 'X_S': 82.14996504835973,\n", - " 'X_BH': 2551.1712941951987,\n", - " 'X_BA': 148.18576250649838,\n", - " 'X_P': 447.1086242830684,\n", - " 'S_O': 0.004288622012845044,\n", - " 'S_NO': 5.33892893863284,\n", - " 'S_NH': 7.928812844268634,\n", - " 'S_ND': 1.216680910568711,\n", - " 'X_ND': 5.285760801254182,\n", - " 'S_ALK': 59.158219028756534,\n", - " 'S_N2': 25.008073542375985,\n", - " 'H2O': 997794.331078558,\n", + " 'S_S': 2.8098364831332874,\n", + " 'X_I': 1147.9022739122495,\n", + " 'X_S': 82.1499821192233,\n", + " 'X_BH': 2551.1711914474663,\n", + " 'X_BA': 148.18618671733967,\n", + " 'X_P': 447.1254556866469,\n", + " 'S_O': 0.004289034729931556,\n", + " 'S_NO': 5.338805118093289,\n", + " 'S_NH': 7.929128379209027,\n", + " 'S_ND': 1.2166810265512678,\n", + " 'X_ND': 5.285761329880132,\n", + " 'S_ALK': 59.15859597894533,\n", + " 'S_N2': 25.007887255081272,\n", + " 'H2O': 998557.4809730583,\n", " 'Q': 92229.99999999996}" ] }, - "execution_count": 22, + "execution_count": 24, "metadata": {}, "output_type": "execute_result" } @@ -1236,11 +1387,7 @@ { "cell_type": "markdown", "id": "68f067f1", - "metadata": { - "slideshow": { - "slide_type": "skip" - } - }, + "metadata": {}, "source": [ "[Back to top](#top)" ] @@ -1248,25 +1395,11 @@ { "cell_type": "markdown", "id": "b6a928f2", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, + "metadata": {}, "source": [ "### 2.2. Fundamental methods\n", - "In addition to proper `__init__` and `_run` methods ([recap](https://qsdsan.readthedocs.io/en/latest/tutorials/5_SanUnit_advanced.html#2.1.-Fundamental-methods)), a few more methods are required in a `SanUnit` subclass for dynamic simulation. Users typically won't interact with these methods but they will be called by `System.simulate` to manipulate the values of the arrays mentioned [above](#s2.1) (i.e., `._state`, `._dstate`, `.state`, and `.dstate`)." - ] - }, - { - "cell_type": "markdown", - "id": "976dabeb", - "metadata": { - "slideshow": { - "slide_type": "subslide" - } - }, - "source": [ + "In addition to proper `__init__` and `_run` methods ([recap](https://qsdsan.readthedocs.io/en/latest/tutorials/5_SanUnit_advanced.html#2.1.-Fundamental-methods)), a few more methods are required in a `SanUnit` subclass for dynamic simulation. Users typically won't interact with these methods but they will be called by `System.simulate` to manipulate the values of the arrays mentioned [above](#s2.1) (i.e., `._state`, `._dstate`, `.state`, and `.dstate`).\n", + "\n", "- `_init_state`, called after `_run` to generate an initial condition for the unit, i.e., defining shape and values of the `_state` and `_dstate` arrays. For example:\n", "```python\n", "import numpy as np\n", @@ -1275,18 +1408,9 @@ " self._state = np.ones(len(inf.components)+1)\n", " self._dstate = self._state * 0.\n", "```\n", - "This method (not saying it makes sense) assumes $n+1$ state variables and gives an initial value of 1 to all of them. Then it also sets the initial time derivatives to be 0. " - ] - }, - { - "cell_type": "markdown", - "id": "3a3de71d", - "metadata": { - "slideshow": { - "slide_type": "subslide" - } - }, - "source": [ + "This method (not saying it makes sense) assumes $n+1$ state variables and gives an initial value of 1 to all of them. Then it also sets the initial time derivatives to be 0. \n", + "\n", + "\n", "- `_update_state`, to update effluent streams' state arrays based on current state (and maybe dstate) of the SanUnit. For example:\n", "```python\n", "def _update_state(self):\n", @@ -1294,18 +1418,8 @@ " eff, = self.outs # assuming this SanUnit has one outlet only\n", " eff.state[:] = arr # assume arr has the same shape as WasteStream.state\n", "```\n", - "The goal of this method is to update the values in `.state` for each `WasteStream` in `.outs`." - ] - }, - { - "cell_type": "markdown", - "id": "8deec2a2", - "metadata": { - "slideshow": { - "slide_type": "subslide" - } - }, - "source": [ + "The goal of this method is to update the values in `.state` for each `WasteStream` in `.outs`.\n", + "\n", "- `_update_dstate`, to update effluent streams' `dstate` arrays based on current `_state` and `_dstate` of the SanUnit. The signiture and often the algorithm are similar to `_update_state`.\n", "\n", "\n", @@ -1316,18 +1430,7 @@ " if self._ODE is None:\n", " self._compile_ODE()\n", " return self._ODE \n", - "```" - ] - }, - { - "cell_type": "markdown", - "id": "a431142f", - "metadata": { - "slideshow": { - "slide_type": "subslide" - } - }, - "source": [ + "```\n", "```python\n", "def _compile_ODE(self):\n", " _dstate = self._dstate\n", @@ -1336,36 +1439,14 @@ " _dstate[:] = some_algorithm(t, y_ins, y, dy_ins)\n", " _update_dstate()\n", " self._ODE = dy_dt\n", - "```" - ] - }, - { - "cell_type": "markdown", - "id": "83c50a89", - "metadata": { - "slideshow": { - "slide_type": "subslide" - } - }, - "source": [ + "```\n", "```python\n", "@property\n", "def AE(self):\n", " if self._AE is None:\n", " self._compile_AE()\n", " return self._AE\n", - "```" - ] - }, - { - "cell_type": "markdown", - "id": "dd66c263", - "metadata": { - "slideshow": { - "slide_type": "subslide" - } - }, - "source": [ + "```\n", "```python\n", "def _compile_AE(self):\n", " _state = self._state\n", @@ -1378,18 +1459,8 @@ " _update_state()\n", " _update_dstate()\n", " self._AE = y_t\n", - "```" - ] - }, - { - "cell_type": "markdown", - "id": "a144502d", - "metadata": { - "slideshow": { - "slide_type": "subslide" - } - }, - "source": [ + "```\n", + "\n", "> **Note**: Within the `dy_dt` or `y_t` functions, `._state[:] = ` rather than `._state = ` because it's generally faster to update values in an existing array than overwriting this array with a newly created array.\n", "\n", "We'll learn more about these two methods in the next subsections." @@ -1398,11 +1469,7 @@ { "cell_type": "markdown", "id": "7cb3c766", - "metadata": { - "slideshow": { - "slide_type": "skip" - } - }, + "metadata": {}, "source": [ "[Back to top](#top)" ] @@ -1410,11 +1477,7 @@ { "cell_type": "markdown", "id": "afd475f2", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, + "metadata": {}, "source": [ "### 2.3. Making a simple MixerSplitter (`_compile_AE`)\n", "\n", @@ -1423,13 +1486,9 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 25, "id": "c38b235a", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, + "metadata": {}, "outputs": [], "source": [ "# Typically if implemented as a static SanUnit, it'd be pretty simple\n", @@ -1461,13 +1520,9 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 26, "id": "9b5ce52d", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, + "metadata": {}, "outputs": [ { "name": "stdout", @@ -1485,37 +1540,34 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 27, "id": "12aa03d9", "metadata": { - "scrolled": false, - "slideshow": { - "slide_type": "slide" - } + "scrolled": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "WasteStream: ws12\n", - "phase: 'l', T: 298.15 K, P: 101325 Pa\n", - "flow (g/hr): S_S 3e+03\n", - " S_NH 2.1e+03\n", - " H2O 8e+05\n", + "WasteStream: ws28\n", + " phase: 'l', T: 298.15 K, P: 101325 Pa\n", + " flow (g/hr): S_S 3e+03\n", + " S_NH 2.1e+03\n", + " H2O 8e+05\n", " WasteStream-specific properties:\n", " pH : 7.0\n", " Alkalinity : 2.5 mg/L\n", - " COD : 3711.8 mg/L\n", - " BOD : 2661.3 mg/L\n", - " TC : 1187.8 mg/L\n", - " TOC : 1187.8 mg/L\n", - " TN : 2598.2 mg/L\n", + " COD : 3711.6 mg/L\n", + " BOD : 2661.2 mg/L\n", + " TC : 1187.7 mg/L\n", + " TOC : 1187.7 mg/L\n", + " TN : 2598.1 mg/L\n", " TP : 37.1 mg/L\n", " Component concentrations (mg/L):\n", - " S_S 3711.8\n", - " S_NH 2598.2\n", - " H2O 989803.5\n" + " S_S 3711.6\n", + " S_NH 2598.1\n", + " H2O 989764.3\n" ] } ], @@ -1528,82 +1580,79 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 28, "id": "4ff4c667", "metadata": { - "scrolled": true, - "slideshow": { - "slide_type": "slide" - } + "scrolled": true }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "MixerSplitter1: M1\n", + "MixerSplitter1: M2\n", "ins...\n", - "[0] ws11\n", - "phase: 'l', T: 298.15 K, P: 101325 Pa\n", - "flow (g/hr): S_O 5e+03\n", - " H2O 1e+06\n", + "[0] ws27\n", + " phase: 'l', T: 298.15 K, P: 101325 Pa\n", + " flow (g/hr): S_O 5e+03\n", + " H2O 1e+06\n", " WasteStream-specific properties:\n", " pH : 7.0\n", - "[1] ws12\n", - "phase: 'l', T: 298.15 K, P: 101325 Pa\n", - "flow (g/hr): S_S 3e+03\n", - " S_NH 2.1e+03\n", - " H2O 8e+05\n", + "[1] ws28\n", + " phase: 'l', T: 298.15 K, P: 101325 Pa\n", + " flow (g/hr): S_S 3e+03\n", + " S_NH 2.1e+03\n", + " H2O 8e+05\n", " WasteStream-specific properties:\n", " pH : 7.0\n", - " COD : 3711.8 mg/L\n", - " BOD : 2661.3 mg/L\n", - " TC : 1187.8 mg/L\n", - " TOC : 1187.8 mg/L\n", - " TN : 2598.2 mg/L\n", + " COD : 3711.6 mg/L\n", + " BOD : 2661.2 mg/L\n", + " TC : 1187.7 mg/L\n", + " TOC : 1187.7 mg/L\n", + " TN : 2598.1 mg/L\n", " TP : 37.1 mg/L\n", "outs...\n", - "[0] ws13\n", - "phase: 'l', T: 298.15 K, P: 101325 Pa\n", - "flow (g/hr): S_S 1e+03\n", - " S_O 1.67e+03\n", - " S_NH 700\n", - " H2O 6e+05\n", + "[0] ws29\n", + " phase: 'l', T: 298.15 K, P: 101325 Pa\n", + " flow (g/hr): S_S 1e+03\n", + " S_O 1.67e+03\n", + " S_NH 700\n", + " H2O 6e+05\n", " WasteStream-specific properties:\n", " pH : 7.0\n", - " COD : 1650.8 mg/L\n", - " BOD : 1183.6 mg/L\n", - " TC : 528.3 mg/L\n", - " TOC : 528.3 mg/L\n", - " TN : 1155.6 mg/L\n", + " COD : 1645.9 mg/L\n", + " BOD : 1180.1 mg/L\n", + " TC : 526.7 mg/L\n", + " TOC : 526.7 mg/L\n", + " TN : 1152.1 mg/L\n", " TP : 16.5 mg/L\n", - "[1] ws14\n", - "phase: 'l', T: 298.15 K, P: 101325 Pa\n", - "flow (g/hr): S_S 1e+03\n", - " S_O 1.67e+03\n", - " S_NH 700\n", - " H2O 6e+05\n", + "[1] ws30\n", + " phase: 'l', T: 298.15 K, P: 101325 Pa\n", + " flow (g/hr): S_S 1e+03\n", + " S_O 1.67e+03\n", + " S_NH 700\n", + " H2O 6e+05\n", " WasteStream-specific properties:\n", " pH : 7.0\n", - " COD : 1650.8 mg/L\n", - " BOD : 1183.6 mg/L\n", - " TC : 528.3 mg/L\n", - " TOC : 528.3 mg/L\n", - " TN : 1155.6 mg/L\n", + " COD : 1645.9 mg/L\n", + " BOD : 1180.1 mg/L\n", + " TC : 526.7 mg/L\n", + " TOC : 526.7 mg/L\n", + " TN : 1152.1 mg/L\n", " TP : 16.5 mg/L\n", - "[2] ws15\n", - "phase: 'l', T: 298.15 K, P: 101325 Pa\n", - "flow (g/hr): S_S 1e+03\n", - " S_O 1.67e+03\n", - " S_NH 700\n", - " H2O 6e+05\n", + "[2] ws31\n", + " phase: 'l', T: 298.15 K, P: 101325 Pa\n", + " flow (g/hr): S_S 1e+03\n", + " S_O 1.67e+03\n", + " S_NH 700\n", + " H2O 6e+05\n", " WasteStream-specific properties:\n", " pH : 7.0\n", - " COD : 1650.8 mg/L\n", - " BOD : 1183.6 mg/L\n", - " TC : 528.3 mg/L\n", - " TOC : 528.3 mg/L\n", - " TN : 1155.6 mg/L\n", + " COD : 1645.9 mg/L\n", + " BOD : 1180.1 mg/L\n", + " TC : 526.7 mg/L\n", + " TOC : 526.7 mg/L\n", + " TN : 1152.1 mg/L\n", " TP : 16.5 mg/L\n" ] } @@ -1616,14 +1665,9 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 31, "id": "72151a1e", - "metadata": { - "scrolled": true, - "slideshow": { - "slide_type": "slide" - } - }, + "metadata": {}, "outputs": [], "source": [ "# Obviously, it's not ready for dynamic simulation\n", @@ -1635,59 +1679,30 @@ { "cell_type": "markdown", "id": "0c4eb0cd", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, + "metadata": {}, "source": [ "Since the mixer-splitter mixes and splits instantly, we can express this process with a set of algebraic equations (AEs). Assume its array of state variables follow the \"concentration-volumetric flow\" convention. In mathematical forms, state variables of the mixer-splitter ($C_m$, component concentrations; $Q_m$, total volumetric flow) follow:\n", "$$Q_m = \\sum_{i \\in ins} Q_i \\tag{1}$$\n", "$$Q_mC_m = \\sum_{i \\in ins} Q_iC_i$$\n", - "$$\\therefore C_m = \\frac{\\sum_{i \\in ins} Q_iC_i}{Q_m} \\tag{2}$$" - ] - }, - { - "cell_type": "markdown", - "id": "a37f98d9", - "metadata": { - "slideshow": { - "slide_type": "subslide" - } - }, - "source": [ + "$$\\therefore C_m = \\frac{\\sum_{i \\in ins} Q_iC_i}{Q_m} \\tag{2}$$\n", "Therefore, the time derivatives $\\dot{Q_m}$ follow:\n", "$$\\dot{Q_m} = \\sum_{i \\in ins} \\dot{Q_i} \\tag{3}$$\n", "$$Q_m\\dot{C_m} + C_m\\dot{Q_m} = \\sum_{i \\in ins} (Q_i\\dot{C_i} + C_i\\dot{Q_i})$$\n", - "$$\\therefore \\dot{C_m} = \\frac{1}{Q_m}\\cdot(\\sum_{i \\in ins}Q_i\\dot{C_i} + \\sum_{i \\in ins}C_i\\dot{Q_i} - C_m\\dot{Q_m}) \\tag{4}$$" - ] - }, - { - "cell_type": "markdown", - "id": "7578a12e", - "metadata": { - "slideshow": { - "slide_type": "subslide" - } - }, - "source": [ + "$$\\therefore \\dot{C_m} = \\frac{1}{Q_m}\\cdot(\\sum_{i \\in ins}Q_i\\dot{C_i} + \\sum_{i \\in ins}C_i\\dot{Q_i} - C_m\\dot{Q_m}) \\tag{4}$$\n", "For any effluent `WasteStream` $j$:\n", "$$Q_j = \\frac{Q_m}{n_{outs}} \\tag{5}$$\n", "$$C_j = C_m \\tag{6}$$\n", "$$\\therefore \\dot{Q_j} = \\frac{\\dot{Q_m}}{n_{outs}} \\tag{7}$$\n", "$$\\dot{C_j} = \\dot{C_m} \\tag{8}$$\n", - "Now, let's try to implement this algorithm in methods for dynamic simulation." + "Now, let's try to implement this algorithm in methods for dynamic simulation.\n", + "\n" ] }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 29, "id": "38abf7cb", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, + "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", @@ -1743,11 +1758,7 @@ { "cell_type": "markdown", "id": "da258438", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, + "metadata": {}, "source": [ ">**Note**: \n", ">1. All `SanUnit._AE` must take exactly these three postional arguments (`t`, `y_ins`, `dy_ins`). `t` is time as a `float`. Both `y_ins` and `dy_ins` are **2d** `numpy.array` of the same shape `(m, n+1)`, where $m$ is the number of inlets, $n+1$ is the length of the `state` or `dstate` array of a `WasteStream`.\n", @@ -1757,13 +1768,9 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 30, "id": "ba8c9001", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, + "metadata": {}, "outputs": [], "source": [ "# Now let's see if this works\n", @@ -1775,17 +1782,13 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 31, "id": "a4f65bf6", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, + "metadata": {}, "outputs": [ { "data": { - "image/png": "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aQHg8ccfVz300ENqAAgPDxdzc3OFsv05Zn4dM8GVtQkICIBara7t0yMiIiIiD+LWAGy32xEfH6/csmWLLDU11dS0adNyyxReffVV87FjxwqPHDlS+gCA9957r2T16tVFANC9e3frqVOnJI7QCwDbt2+XBQQEoE2bNnYAiImJsaWlpZWb7U5JSZF16dLFBiIiIiLyKW5dAhEfH6/cuHGj/OuvvzZptVrxypUrAgDodDpRrVajfv36YkUXvjVu3NjuCMtxcXG2li1b2kePHq1atGhRcXZ2tpCQkKCYNGmS2bGEYcqUKeaVK1f6zZw5UzF+/HhLSkqKdPPmzbKkpKSKFwATERER0V3LrTPAH3/8sdxoNKJPnz7qBg0aaByP9evXO32TCplMhq1bt5qkUqn4wAMP+D/zzDOq0aNHW956663SPcyaNm0qJiUlmVJTU2X333+//5IlSxQrV64sHjRoEGeAiYiIiHyMR+0D7MmMRiN0Oh33ASYiIiLyUF65DzARERERUW1jACYiIiIin8IATEREREQ+hQGYiIiIiHwKAzARERER+RQGYCIiIiLyKQzARERERORTGICJiIiIyKcwABMRERGRT2EAJiIiIiKfwgBMRERERD6FAZiIiIiIfAoDMBERERH5FAZgIiIiIvIpDMBERERE5FMYgImIiIjIpzAAExEREZFPYQAmIiIiIp/CAExEREREPoUBmIiIiIh8CgMwEREREfkUBmAiIiIi8ikMwERERETkUxiAiYiIiMinMAATERERkU9hACYiIiIin8IATEREREQ+hQGYiIiIiHyKWwPwggUL/Dp27Oiv1Wq1oaGhmiFDhqhOnz5dWtO1a9cwZcoUZfPmzf1VKpW2UaNGmhdeeEFhMBjK9XPx4kUhLi5OpVartaGhoZoZM2YoLBZLuTapqanS9u3b+ysUCm3Tpk01q1atktfJSRIRERGRR3FrAN69e7csPj7evH///sLt27ebLBYLBgwYoC4oKAAA/PHHH5IrV64IixYtKjlx4kTBp59+WvTDDz/InnvuOZWjD6vVisGDB6vNZrOwZ8+ewtWrVxetXbtWPnfuXIWjzblz54ShQ4eqe/bsaT1y5Ejh1KlTSyZPnqxMTk6WuuG0iYiIiMiNBFEU89xdhENOTo6g1+s1aWlppl69etkqarNhwwbZs88+qyooKMiXy+XYunWrdNiwYerLly8XREREiACwbNky+dy5c5W5ubn5CoUCL730kuL777+XZWRkFDr6GTlypMpgMAg//vijyZnajEYjdDqd1mg0IiAgoGZOmIiIiIhqTF5eHgIDA2EwGPIDAwMrbedRa4CNRiMAICQkRKysjcFgELRarSiX31zBsH//flnr1q3tjvALAAMHDrTm5eXh5MmTEgD4+eefpX369LGW7ad///7WAwcOVDoDXFxcDKPRWPrIy/OY3xOIiIiI6A54TAC22WyYPn26slu3brZ27drZK2pz9epVYeHChYpx48aVLvDNyckRwsLCygVmvV4vAkB2drZwuzZ5eXkwmSqeAE5MTFTodDqt4xEZGam9w1MkIiIiIg9wRwG4qKiopupAfHy8MiMjQ7phw4YKOzUajRg0aJCqZcuW9gULFpTU2BtXYt68eSUGgyHf8cjMzMyv7fckIiIiotrncgC22WxISEjwq1+/vkar1Wp/++03AQBeffVVxT//+c9q7awQHx+vTE5OlqWlpRVGRkbesvwhLy8PAwYMUGs0GmzZssXk5+dX+lp4eLiYm5srlG3vmPl1zARX1iYgIABqtbrCmpRKJQIDA0sfXPdLREREdHdwOQD/7W9/81u7dq3fO++8U1w2iLZt29b26aefuhSA7XY74uPjlVu2bJGlpqaamjZtekv4NRqN6Nevn9rPzw9bt241qVSqcq93797deurUKYkj9ALA9u3bZQEBAWjTpo0dAGJiYmxpaWmysselpKTIunTpUuGFdkRERER093I5AK9bt85v5cqVRc8884xVKv3zGrL27dvbz5w549K2YvHx8covv/xS/sUXXxRptVrxypUrwpUrVwTHulxH+C0sLBT+9a9/FRmNRsHRxmq9eU1bXFycrWXLlvbRo0erjhw5IklOTpYmJCQoJk2aZFYqlQCAKVOmmC9evCiZOXOmIiMjQ/KPf/xDvnnzZtmMGTNqfSkFEREREXkWWdVNyrty5YrQrFmzWy5Ss9vt+OvNJ6ry8ccfywGgT58+5dYhfPLJJ8Xjx4+3HDp0SHrw4EEpADRv3lxTts25c+cKmjRpIspkMmzdutU0efJk5QMPPOCvVqvFMWPGWN56663ScNu0aVMxKSnJNHPmTOVHH33k16BBA3HlypXFgwYN4gwwERERkY9xOQC3bNnSvnv3bll0dHS5tPvVV1/J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v34d169fBwCEhobeUUAvq85mgB3MZjPUarX2yJEjhe3ateO6XyIiIqIqDB06FDExMRW+die3FL6TcPpXAwYMwIABVe+17Irg4GAEB1e937I7uPSrgp+fHxo1aiTW1MLmBQsW+HXs2NFfq9VqQ0NDNUOGDFGdPn26XE1FRUWYPHmyMjg4WKPRaLSPPPKIKisrq9xai4sXLwpxcXEqtVqtDQ0N1cyYMUPh+I3IITU1Vdq+fXt/hUKhbdq0qWbVqlXefRNrIiIi8gparRb33HNPhY+aDLHkPJfnyufMmVPy2muvKa5du3bHb757925ZfHy8ef/+/YXbt283WSwWDBgwQF1QUFDaZvr06crvvvtOtnHjxqK0tLTCrKwsYfjw4SrH61arFYMHD1abzWZhz549hatXry5au3atfO7cuaUX6507d04YOnSoumfPntYjR44UTp06tWTy5MnK5ORk3rmOiIiIyMe4vAb4vvvu8z9//rzEYrEgMjLSrlary71+7Nixau/YnJOTI+j1ek1aWpqpV69eNoPBgLCwMO3atWuLnnjiCSsAZGRkSFq3bu2/d+9eU2xsrG3r1q3SYcOGqS9fvlwQEREhAsCyZcvkc+fOVebm5uYrFAq89NJLiu+//16WkZFRWtvIkSNVBoNB+PHHHytcmV1cXIySkj83tMjLy0NkZCTXABMREdWiu3ENMNWsOl8DDABDhw61VN2qeoxGIwAgJCREBICDBw9KLRYL+vfvX7rmolWrVvZGjRqJ+/fvl8bGxtr2798va926td0RfgFg4MCB1qlTp+LkyZOSTp062X/++Wdpnz59yq3b6N+/v3XWrFmVfrISExMVb731ll+NnyQRERERuZXLAXjBggXm2ijEZrNh+vTpym7dutkcF9hlZ2cLfn5+CAoKKtc2LCxMzM7OFoCbs8ZhYWHl7kCn1+tFx/G3a5OXlweTyYS/zmIDwLx580pefvnl0ilgxwxwzZwtEREREbnLHd8JrqbEx8crMzIypHv27Kn9m147QalU8k8vRERERHchly+Cs1qteOedd/w6derkHx4ergkODtaWfVSniPj4eGVycrIsLS2tMDIysnSmVq/Xi2azGTdu3CjXPjc3V3DM8oaHh4u5ubnldoVwzPxW1SYgIKDC2V8iIiIib9GzZ08IggBBEHDs2DF3l3OLNWvWlNZXG3etqw6X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9e7du6g231+pVKLsWmMiIiIiujt47AxwaGioKJPJcO+999rKPt+yZUv777//LgA3Z3LNZjNu3LhR7tjc3FzBMcsbHh4u5ubmlpvpdcz8OtoQERERke/w2ACsUCjQsWNH25kzZ8rVePbsWUlkZKQIAJ07d7bJ5XL8+OOPpTPZp0+flvz+++9C9+7dbQDQvXt366lTpyRllzts375dFhAQgDZt2tjr6nyIiIiIyDO4dQlEfn4+ygbc8+fPSw4fPiwJCQkRo6KixFmzZpmfeuop1YMPPmjr06ePNTk5WZacnCxLTU01AYBOp8PYsWMts2bNUgYHBxcFBASI06ZNU8bExNhiY2NtABAXF2dr2bKlffTo0apFixYVZ2dnCwkJCYpJkyaZucSBiIiIyPe4dRu01NRUad++fdV/fX706NGWtWvXFgPAxx9/LF+0aJHfH3/8IWnWrJl9/vz5JcOHDy/d9aGoqAgzZsxQfvXVV/KSkhL07dvXumLFiuL69euXLm+4cOGCMHnyZOWePXtkarVaHDNmjGXRokUlcrnc6Vp5JzgiIiIiz8ZbIdcwBmAiIiIiz+b1+wATEREREdUGBmAiIiIi8ikMwERERETkUxiAiYiIiMinMAATERERkU9hACYiIiIin8IATEREREQ+hQGYiIiIiHwKAzARERER+RQGYCIiIiLyKQzARERERORTGICJiIiIyKcwABMRERGRT2EAJiIiIiKfwgBMRERERD6FAZiIiIiIfAoDMBERERH5FAZgIiIiIvIpDMBERERE5FMYgImIiIjIpzAAExEREZFPYQAmIiIiIp/CAExEREREPoUBmIiIiIh8CgMwEREREfkUBmAiIiIi8ikMwERERETkUxiAiYiIiMinuDUA79ixQzpo0CBVRESERhAE7ebNm2WVtZ0wYYJSEATt4sWL/co+f+3aNYwaNUoVEBCg1el02rFjxyrz8/PLHXvs2DFJbGysWqlUahs2bKhZuHBhuT6IiIiIyHe4NQAXFhaiXbt29qVLlxbfrt2mTZtkP//8szQiIkL862tPPvmkOiMjQ7Jt2zbTli1bTHv37pWOHz9e5XjdaDRiwIAB6sjISPvBgwcL33333eLExETF8uXL5bVxTkRERETk2Sqdca0LDz/8sO3hhx+23a7N77//LkyfPl25bds208MPP6wu+9qpU6ckP/74ozQ9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LytEdcxms2H69OnKbt262dq1a8c/wXqR48ePS2JjY/2Li4uh0WiwadOmIv4Z3XusW7dOdvToUemhQ4c8dgaRKte1a1frv/71L1vLli3tV65cEd58803FQw895P9///d/BQEBAe4uDwADMBFRpeLj45UZGRnSPXv28H/CXqZly5b2I0eOFBgMBuHf//63/LnnnlPu3LnTxBDs+S5duiTMmDFD+cMPP5hUKpW7y6FqePjhh0tn7du3b49u3bqZoqKitF9++aXcU5YhMQB7kNDQUFEqlSInJ6fcnwlyc3OFv84KE1Htio+PVyYnJ8t27dpVGBkZyc+fl1EoFGjevLkIQOzSpUvJoUOHpEuWLPFbtWoVL071cIcOHZJevXpV6NSpU+muATabDXv37pWuWLHCr7i4OF8mY3zxJkFBQbjnnnvsv/32m8f8NdtjCqGb/8G+//77bampqaWfbJvNhh07dsi6du3KNVBEdcButyM+Pl65ZcsWWWpqqqlp06YMv3cBURRRUsLrp7xBv379rMePHy88cuRI6aNDhw72UaNGWY4cOVLI8Ot98vPzceHCBUlERITH/PeUP0UeZsaMGebnn39e1alTJ1tMTIztgw8+8DOZTMK4ceM84k8GVLX8/HycOXOm9JfL8+fPSw4fPiwJCQkRo6KiPObDTxWLj49Xbty4Uf7111+btFqteOXKFQEAdDqdqFar3V0eOeHll19WDBo0yNq4cWN7fn6+8MUXX8h3794tTU5OZgL2AgEBAfjrmnt/f38xJCRE5Fp87zBjxgzFkCFDrFFRUfY//vhDkpCQoJBIJOLTTz/tMVmGAdjDPPXUU9bc3Nzi+fPnK3JycoR27drZvvvuO5Mn/dZEt3fgwAFp3759S5PSK6+8ogCgGD16tGXt2rX886uH+/jjj+UA0KdPn3Jp95NPPikeP368x/zHmyp39epVYezYsars7GwhICBAbNOmjT05OdkUFxfHv6QR1YHLly9Lnn76adX169eFevXqid27d7f99NNPhZ60nJP7ABMRERGRT+EaYCIiIiLyKQzARERERORTGICJiIiIyKcwABMRERGRT2EAJiIiIiKfwgBMRERERD6FAZiIiIiIfAoDMBERERH5FAZgIiIvMWbMGOWQIUNUdf2+q1atkguCoBUEQTt16lTF7do2btxYs3jxYr+yXzuOvXHjRu0XS0TkBN4KmYjIAwiCoL3d63PnzjUvXbq0WBTdcyfRgIAAnD59ukCj0bhUwIEDBwp3794tffzxx+s8uBMRVYYBmIjIA/zxxx8Fjn//8ssv5W+++abi9OnTpc9ptVpRq71tRq5VgiCgfv36Lqfv8PBwMTg42D2pnYioElwCQUTkAerXry86HoGBgaIjcDoeWq32liUQDz30kHrKlCnKqVOnKoKCgrRhYWGaFStWyAsKCvDMM88otVqttmnTppqtW7dKy77XiRMnJP3791drNBptWFiY5qmnnlJevXpVcLXm7OxsYdCgQSqVSqWNiorSfP7555xUISKvwABMROTFvvjiC3lISIiYnp5eOGXKFPPUqVOVI0aMUHXr1s126NChwr59+1qfffZZVWFhIQDgxo0b6NOnj7p9+/a2AwcOFCYnJ5tycnIkI0eOdHmJwrPPPqu8fPmyJCUlxfTVV1+ZVqxY4VedIE1EVNcYgImIvFjbtm1t8+fPN7do0cI+b948s1KpRL169cT4+HhLixYt7AkJCSXXr18Xjh07JgWADz/80O++++6zL1q0qKRVq1b2Tp062VevXl20a9cu6S+//OL0/xN++eUXyQ8//CD7+OOPi2JjY21dunSx/+tf/youKiqqvZMlIqoh/HMVEZEXa9u2rd3x7zKZDMHBwWKbNm1Kn9Pr9SIA5ObmCgBw4sQJ6e7du6UajeaWBcW//fab0LJlS6feNyMjQyKTydC5c+fS92rVqpVdp9NV/2SIiOoIAzARkReTy+XlLjATBAFyubz0a4nk5qSu3X4zpxYUFAiDBg2yLlq0qPivfVXnIjciIm/EAExE5EPuv/9+2zfffCOLjo4WywZlV9177712q9WKgwcPSrp27WoHgNOnT0sMBkNNlUpEVGu4BpiIyIdMnTrVfOPGDeGJJ55QpaenS86ePSskJydLn3nmGaXVanW6n3vvvdfer18/2+TJk1X79++XHjhwQDJ+/HilSsXtfonI8zEAExH5kIYNG4p79+412Ww2DBw40P++++7TzJgxQ6nT6UTHcglnrVmzpigiIsLeu3dv9WOPPaaeMGGCJTQ0lMsoiMjjCaIo5rm7CCIi8lyrVq2Sz5o1S2kwGPKrc3xqaqq0b9++6uvXr+cHBQXVdHlERC7jDDAREVXJaDRCo9FoX3rpJYUrx917773+Dz/8sLq26iIiqg7OABMR0W3l5eUhOztbAICgoCC4sszhwoULgsViAQA0bdpUlEqlVRxBRFT7GICJiIiIyKdwCQQRERER+RQGYCIiIiLyKQzARERERORTGICJiIiIyKcwABMRERGRT2EAJiIiIiKfwgBMRERERD6FAZiIiIiIfMr/B7EmmpyMM1FGAAAAAElFTkSuQmCC\n", + "image/png": "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qoceekgNACEhIWJhYaFwbX/2mV/7TPDN2vj5+UGtVldbk1KphL+/f+WN636JiIiI7gwuDcA2mw2JiYnKjRs3ytLT041RUVFVlim8+eabpkOHDpUeOHCg8gYAH3zwQcXKlSvLAKB79+6Wo0ePSuyhFwC2bNki8/PzQ/v27W0AEBsba83IyKgy252WliaLiYmxgoiIiIi8ikuXQCQmJiq/+eYb+b///W+jVqsVc3NzBQDQ6XSiWq1G48aNxeo++Na8eXObPSwnJCRYW7dubRs+fLhqwYIF5fn5+UJycrLipZdeMimVSgDAhAkTTMuXL/eZOnWqYsyYMea0tDTphg0bZCkpKQ7tAEFEREREdw6XzgB/+umncoPBgN69e6ubNGmisd/Wrl3r8CEVMpkMmzZtMkqlUvGBBx7wff7551XDhw83z507t3L9blRUlJiSkmJMT0+X3Xfffb6LFi1SLF++vHzAgAGcASYiIiLyMm61D7A74z7ARERERO7NI/cBJiIiIiKqbwzARERERORVGICJiIiIyKswABMRERGRV2EAJiIiIiKvwgBMRERERF6FAZiIiIiIvAoDMBERERF5FQZgIiIiIvIqDMBERERE5FUYgImIiIjIqzAAExEREZFXYQAmIiIiIq/CAExEREREXoUBmIiIiIi8CgMwEREREXkVBmAiIiIi8ioMwERERETkVRiAiYiIiMirMAATERERkVdhACYiIiIir8IATERERERehQGYiIiIiLwKAzAREREReRUGYCIiIiLyKgzARERERORVGICJiIiIyKswABMRERGRV2EAJiIiIiKv4tIAPHv2bJ/777/fV6vVaoOCgjQDBw5UHTt2rLKmS5cuYcKECcp77rnHV6VSaZs1a6Z5+eWXFXq9vko/Z86cERISElRqtVobFBSkmTJlisJsNldpk56eLu3YsaOvQqHQRkVFaVasWCFvkIskIiIiIrfi0gC8Y8cOWWJiomn37t2lW7ZsMZrNZsTHx6tLSkoAAH/++ackNzdXWLBgQcXhw4dLPv/887KffvpJNmrUKJW9D4vFgkceeURtMpmEzMzM0pUrV5atXr1aPnPmTIW9zcmTJ4VBgwape/ToYTlw4EDpxIkTK8aPH69MTU2VuuCyiYiIiMiFBFEUi1xdhF1BQYEQGhqqycjIMPbs2dNaXZuvv/5a9sILL6hKSkqK5XI5Nm3aJB08eLD6woULJWFhYSIALFmyRD5z5kxlYWFhsUKhwKuvvqr48ccfZTk5OaX2foYNG6bS6/XCzz//bHSkNoPBAJ1OpzUYDPDz86ubCyYiIiKiOlNUVAR/f3/o9fpif3//m7ZzqzXABoMBABAYGCjerI1erxe0Wq0ol19dwbB7925Zu3btbPbwCwD9+/e3FBUV4ciRIxIA2Lt3r7R3796Wa/vp16+fJTs7+6YzwOXl5TAYDJW3oiK3+T2BiIiIiG6D2wRgq9WKyZMnK7t162aNjo62Vdfm4sWLwrx58xSjR4+uXOBbUFAgBAcHVwnMoaGhIgDk5+cLt2pTVFQEo7H6CeA5c+YodDqd1n4LDw/X3uYlEhEREZEbcJsAnJiYqMzJyZF+/fXXZdU9bjAYMGDAAFXr1q1ts2fPrqjvepKSkir0en2x/Xbu3Lni+n5NIiIiIqp/stt5cllZGVQqVc0Na5CYmKhMTU2Vbd++vTQ8PPyG5Q9FRUWIj49XazQabNy40ejj41P5WEhIiLhv3z7h2vb2mV/7THBISIhYWFh4Qxs/Pz+o1epqa1IqlVAqlbd9bURERETkXpyeAbZarUhOTvZp3LixRqvVav/44w8BAN58803FP/7xD6e2FrPZbEhMTFRu3LhRlp6eboyKiroh/BoMBvTt21ft4+ODTZs2Ga8P3N27d7ccPXpUYg+9ALBlyxaZn58f2rdvbwOA2NhYa0ZGRpWwn5aWJouJian2g3ZEREREdOdyOgC/8847PqtXr/Z5//33y6+die3QoYP1888/dyoAJyYmKtetWyf/6quvyrRarZibmyvk5uYK9nW59vBbWloq/POf/ywzGAyCvY3FcvUzbQkJCdbWrVvbhg8frjpw4IAkNTVVmpycrHjppZdM9hncCRMmmM6cOSOZOnWqIicnR/L3v/9dvmHDBtmUKVPqfSkFEREREbkXp7dBi4qK0ixbtqysX79+Vq1Wqz148GDJ3XffLebk5Eji4uJ8r1y54vBaWUEQqv1g2WeffVY+ZswYc3p6urRPnz7VrlE4efJkSYsWLUQAOH36tDB+/HhlZmamTK1WiyNGjDAvWLCgwr5TBHD1IIypU6cqf/vtN0mTJk3EGTNmVIwZM8ZcXd/V4TZoRERERO7N0W3QnF4DnJubK7Rs2fKGXRpsNhuuP32tJqIo3jIs9+7d21pTGwCIjIwUt2zZUu2H567t69dffy29VRsiIiIiuvM5vQSidevWth07dtwQnL/99lvZvffeyzW1REREROTWnJ4BfuuttypefPFF1YULFwSbzYb169fLjx8/LlmzZo1848aNDp2qRkRERETkKk7PAA8ZMsTy/fffG9PT02VqtVp89913Fb/99pvk+++/NyYkJHAGmIiIiIjcWq32Ae7Ro4e1R48enO0lIiIiIo/jNifBERERERE1BIdmgBs1aqQVBKHmhgAuX77MI4OJiIiIyG05FID/9re/ldd3IUREREREDcGhAPziiy86t8EvEREREZGbqtWH4CwWCzZs2CDLycmRAEC7du1sjz/+uOXak9eIiIiIbofVanX6kC26s8nlckil0tvux+mjkI8cOSIZPHiwuqCgoPJEuD/++ENy1113iSkpKcbo6OgbTom7E/AoZCIiooYhiiLy8/Oh1+tdXQq5IZ1Oh9DQUFT3+bR6Owp5zJgxyjZt2lh/+eWXsoCAAADA5cuX8fzzz6vGjRunzMrK4vZoREREVGv28BscHAy1Wl1t0CHvI4oijEYjCgsLAQBhYWG17svpAHz48GFpdnZ2qT38AkBAQADmzZtXERsb61vrSoiIiMjrWa3WyvAbGBjo6nLIzahUKgBAYWEhgoODa70cwul9gO+++25bfn7+Db+KFRQUCC1atLgjlz8QERFRw7Cv+VWr1S6uhNyV/WfjdtaHOzQDbDAYKv993rx5FZMnT1YmJydXdOvWzQoAe/bskc6ePVvx/vvvV9S6EiIiIqL/4bIHupm6+Nmo1UEYoijimWeeUdnvE0URAPDYY4+prFYrD8IgIiIiIrflUABOS0vjB9uIiIjIY1htIrJPX0ZhcTmCtUrERAZAKrmzZpUjIiJw9uxZAMCVK1eg0+lcW9B1Zs2ahXfeeQcAsHDhQrzyyiuuLegaDgXgXr16Weu7ECIiIqK6sPn/8vDOf3KQZ/jrINswfyWSB7ZFQvva7xxwKxcvXsTbb7+NH374AQUFBWjUqBHuvfdevP3224iLi6vx+b/++iveeustZGVloaioCKGhoYiNjcXixYsRHBx80+e9++67GDt2LG615Vddi4yMxGeffQaZTIaePXveNHxPmzYN48ePR5cuXRqsNkfV6iAMACgtLcXZs2clJpOpyv0dO3bkB+GIiIjIJTb/Xx4S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GeCgoCBRJpOhTZs21mvvb926te38+fMCcHUm12Qy4cqVK1WeW1hYKNhneUNCQsTCwsIqM732mV97GyIiIiLyHm4bgBUKBe6//37r8ePHq9R44sQJSXh4uAgAXbp0scrlcvz888+VM9nHjh2TnD9/XujevbsVALp37245evSo5NrlDlu2bJH5+fmhffv2toa6HiIiIiJyDy5dAlFcXIxrA+6pU6ck+/fvlwQGBooRERHitGnTTM8++6zqwQcftPbu3duSmpoqS01NlaWnpxsBQKfTYeTIkeZp06YpAwICyvz8/MRJkyYpY2NjrXFxcVYASEhIsLZu3do2fPhw1YIFC8rz8/OF5ORkxUsvvWTiEgciIiIi7+PSbdDS09Olffr0UV9///Dhw82rV68uB4BPP/1UvmDBAp8///xT0rJlS9usWbMqhgwZUrnrwzUHYcgrKipgPwijcePGlcsbTp8+LYwfP16ZmZkpU6vV4ogRI3gQBhEREdEdxiNOgvMkDMBERERE7s3j9wEmIiIiIqoPDMBERERE5FUYgImIiIjIqzAAExEREZFXYQAmIiIiIq/CAExEREREXoUBmIiIiIi8CgMwEREREXkVBmAiIiIi8ioMwERERETkVRiAiYiIiMirMAATERERkVdhACYiIiIir8IATERERERehQGYiIiIiLwKAzAREREReRUGYCIiIiLyKgzARERERORVGICJiIiIyKswABMRERGRV2EAJiIiIiKvwgBMRERERF6FAZiIiIiIvAoDMBERERF5FQZgIiIiIvIqDMBERERE5FUYgImIiIjIqzAAExEREZFXcWkA3rp1q3TAgAGqsLAwjSAI2g0bNshu1nbs2LFKQRC0H374oc+191+6dAlPP/20ys/PT6vT6bQjR45UFhcXV3nuoUOHJHFxcWqlUqlt2rSpZt68eVX6ICIiIiLv4dIAXFpaiujoaNvixYvLb9Vu/fr1sr1790rDwsLE6x975pln1Dk5OZLNmzcbN27caNy5c6d0zJgxKvvjBoMB8fHx6vDwcNu+fftK58+fXz5nzhzF0qVL5fVxTURERETk3m4649oQHn30Ueujjz5qvVWb8+fPC5MnT1Zu3rzZ+Oijj6qvfezo0aOSn3/+WZqVlVUaGxtrA4CPP/64fODAgeq//e1vQtOmTcUvv/xSbjabhVWrVpUrFAp06NDBdvDgQdOiRYt8JkyYYK7P6yMiIiIi9+PWa4CtVitGjBihmjp1qqlDhw626x/ftWuXVKfTwR5+AaBfv35WiUSCrKwsKQBkZWVJ4+LiLAqFovJ5CQkJlhMnTkguX75809cuLy+HwWCovBUVFdXtxRERERGRS7h1AJ43b56PVCrFlClTTNU9np+fLwQFBVUJxnK5HI0aNRLz8vKE/7WRhISEVFk6ERoaKgJAXl7eTa9/zpw5Cp1Op7XfwsPDtbd/RURERETkam4bgLOzsyVLlizx+eKLL8okkoYvMykpqUKv1xfbb+fOnSuu+VlERERE5O7cNgDv2LFDdvHiRSEiIkIjk8m0MplMe+7cOWH69OmK5s2ba4CrM7kXL16scg1msxlXrlwR7B+YCw0NtRUUFAjXtsnPzxcAICws7IZlFXZKpRL+/v6VNz8/v7q/SCIiIiJqcG4bgEeOHGk+dOhQ6YEDBypvYWFh4pQpU0ybN282AkBcXJxVr9cjOzu78jrS0tKkNpsNXbt2tQJA165drbt27ZKZTH+tovjpp59kLVu2tAUEBDT4dRERERGRa7l0F4ji4mIcP368MryeOnVKsn//fklgYKAYEREh3nXXXVXW7srlcoSGhopt2rSxAUC7du1sffv2tY4bN061bNmyMrPZLEyaNEk5bNgwS9OmTUUAGDFihHnOnDmKUaNGKd944w3TkSNHJJ988onPBx98cMut14iIiIjozuTSAJydnS3t06dP5dZmr7/+ugKAYvjw4ebVq1c7FFDXrVtnnDBhgqpfv36+EokEjz32mHnJkiWVz9XpdNiyZYvx5ZdfVnbp0sU3MDBQnDFjRgW3QCMiIiLyToIoitzfywEGgwE6nU5rMBi4HpiIiIjIDRUVFcHf3x96vb7Y39//pu3cdg0wEREREVF9YAAmIiIiIq/CAExEREREXoUBmIiIiIi8CgMwEREREXkVBmAiIiIi8ioMwERERETkVRiAiYiIiMirMAATERERkVdhACYiIiIir8IATERERERehQGYiIiIiLwKAzAREREReRUGYCIiIiLyKgzARERERORVGICJiIiIyKswABMRERGRV2EAJiIiIiKvwgBMRERERF6FAZiIiIiIvAoDMBERERF5FQZgIiIiIvIqDMBERERE5FUYgImIiIjIqzAAExEREZFXYQAmIiIiIq/CAExERERE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\n", "text/plain": [ "
" ] @@ -1803,11 +1806,7 @@ { "cell_type": "markdown", "id": "22788b98", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, + "metadata": {}, "source": [ "Many commonly used unit operations, such as [Pump](https://qsdsan.readthedocs.io/en/latest/api/sanunits/pumping.html#qsdsan.sanunits.Pump), [Mixer](https://qsdsan.readthedocs.io/en/latest/api/sanunits/abstract.html#mixer), [Splitter](https://qsdsan.readthedocs.io/en/latest/api/sanunits/abstract.html#splitter), and [HydraulicDelay](https://qsdsan.readthedocs.io/en/latest/api/sanunits/pumping.html#hydraulicdelay), have implemented the fundamental methods to be used in a dynamic system. You can always refer to the source codes of these units to learn more about how they work." ] @@ -1815,11 +1814,7 @@ { "cell_type": "markdown", "id": "1f5d8d3f", - "metadata": { - "slideshow": { - "slide_type": "skip" - } - }, + "metadata": {}, "source": [ "[Back to top](#top)" ] @@ -1827,11 +1822,7 @@ { "cell_type": "markdown", "id": "a04a8ab5", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, + "metadata": {}, "source": [ "### 2.4. Making an inactive CompleteMixTank (`_compile_ODE`)" ] @@ -1839,11 +1830,7 @@ { "cell_type": "markdown", "id": "21dca6ff", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, + "metadata": {}, "source": [ "As you can see above, it's not very impressive to dynamically simulate a system without any ODEs. So let's make a simple inactive complete mix tank. Assume the reactor has a fixed liquid volume $V$, and thus the effluent volumetric flow rate changes instantly with influents. The mass balance of this type of reactor can be described as:\n", "$$Q = \\sum_{i \\in ins} Q_i \\tag{9}$$\n", @@ -1855,13 +1842,9 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": 32, "id": "c4706ed2", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, + "metadata": {}, "outputs": [], "source": [ "class CompleteMixTank(qs.SanUnit):\n", @@ -1925,11 +1908,7 @@ { "cell_type": "markdown", "id": "472f1577", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, + "metadata": {}, "source": [ ">**Note**: \n", ">1. All `SanUnit._ODE` must take exactly these four postional arguments: `t`, `y_ins`, and `dy_ins` are the same as the ones in `SanUnit._AE`. `y` is a **1d** `numpy.array`, because it is equal to the `_state` array of the unit.\n", @@ -1939,13 +1918,9 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": 33, "id": "493239c1", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, + "metadata": {}, "outputs": [], "source": [ "# Let's see if it works\n", @@ -1962,28 +1937,24 @@ }, { "cell_type": "code", - "execution_count": 33, + "execution_count": 34, "id": "c3df8f02", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, + "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(
,\n", - " )" + " )" ] }, - "execution_count": 33, + "execution_count": 34, "metadata": {}, "output_type": "execute_result" }, { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ "
" ] @@ -1999,11 +1970,7 @@ { "cell_type": "markdown", "id": "3970aeaa", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, + "metadata": {}, "source": [ "Many commonly used unit operations described by ODEs have been implemented in QSDsan, such as [CSTR](https://qsdsan.readthedocs.io/en/latest/api/sanunits/suspended_growth_bioreactors.html#cstr), [BatchExperiment](https://qsdsan.readthedocs.io/en/latest/api/sanunits/suspended_growth_bioreactors.html#batchexperiment), and [FlatBottomCircularClarifier](https://qsdsan.readthedocs.io/en/latest/api/sanunits/clarifiers.html)." ] @@ -2011,11 +1978,7 @@ { "cell_type": "markdown", "id": "9d9a485a", - "metadata": { - "slideshow": { - "slide_type": "skip" - } - }, + "metadata": {}, "source": [ "[Back to top](#top)" ] @@ -2023,11 +1986,7 @@ { "cell_type": "markdown", "id": "b085b491", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, + "metadata": {}, "source": [ "## 3. Other convenient features \n", "### 3.1. `ExogenousDynamicVariable`\n", @@ -2036,75 +1995,42 @@ }, { "cell_type": "code", - "execution_count": 34, + "execution_count": 35, "id": "6e8b6a32", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, + "metadata": {}, "outputs": [], "source": [ "# Check out the documentation\n", "from qsdsan.utils import ExogenousDynamicVariable as EDV\n", - "# EDV?" + "EDV?" ] }, { "cell_type": "markdown", "id": "d0365c64", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, + "metadata": {}, "source": [ "There are generally two ways to create an `ExogenousDynamicVariable`.\n", "\n", "1. __Define the variable as a function of time.__ Let's say we want to create a variable to represent the changing reaction temperature. Assume the temperature value \\[K\\] can be expressed as $T = 298.15 + 5\\cdot \\sin(t)$, indicating that the temperatue fluctuacts around $25^{\\circ}C$ by $\\pm 5^{\\circ}C$. Then simply,\n", "```python\n", "T = EDV('T', function=lambda t: 298.15+5*np.sin(t))\n", - "```" - ] - }, - { - "cell_type": "markdown", - "id": "e2885b32", - "metadata": { - "slideshow": { - "slide_type": "subslide" - } - }, - "source": [ + "```\n", "2. __Provide time-series data to describe the dynamics of the variable.__ For demonstration purpose, we'll just make up the data. In practice, this is convenient if you have real data.\n", "```python\n", "t_arr = np.linspace(0, 5)\n", "y_arr = 298.15+5*np.sin(t_arr)\n", "T = EDV('T', t=t_arr, y=y_arr)\n", - "```" - ] - }, - { - "cell_type": "markdown", - "id": "a89fa738", - "metadata": { - "slideshow": { - "slide_type": "subslide" - } - }, - "source": [ + "```\n", + "\n", "For convenience, `ExogenousDynamicVariable` also has a `classmethod` that enables batch creation of multiple variables at once. We just need to provide a file of the time-series data, including a column `t` for time points and additional columns of the variable values. See the [documentation](https://qsdsan.readthedocs.io/en/latest/api/utils/dynamics.html#qsdsan.utils.ExogenousDynamicVariable.batch_init) of `ExogenousDynamicVariable.batch_init` for detailed usage." ] }, { "cell_type": "code", - "execution_count": 35, + "execution_count": 39, "id": "b6401d1c", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, + "metadata": {}, "outputs": [], "source": [ "# EDV.batch_init?" @@ -2113,11 +2039,7 @@ { "cell_type": "markdown", "id": "8a5bd5b7", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, + "metadata": {}, "source": [ "Once created, these `ExogenousDynamicVariable` objects can be incorporated into any `SanUnit` upon its initialization or through the `SanUnit.exo_dynamic_vars` property setter. " ] @@ -2126,24 +2048,12 @@ "cell_type": "code", "execution_count": 36, "id": "2639a8b7", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, + "metadata": {}, "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "All impact indicators have been removed from the registry.\n", - "All impact items have been removed from the registry.\n" - ] - }, { "data": { "text/plain": [ - "(,)" + "(, )" ] }, "execution_count": 36, @@ -2153,26 +2063,22 @@ ], "source": [ "# Let's see an example\n", - "from exposan.metab import create_system\n", - "sys_mt = create_system()\n", + "from exposan.metab_mock import create_systems\n", + "sys_mt, = create_systems(which='A')\n", "uf_mt = sys_mt.flowsheet.unit\n", - "uf_mt.R1.exo_dynamic_vars" + "uf_mt.R1A.exo_dynamic_vars" ] }, { "cell_type": "code", "execution_count": 37, "id": "a7e11837", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, + "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "[295.15]" + "[308.15, 5.8]" ] }, "execution_count": 37, @@ -2183,17 +2089,13 @@ "source": [ "# The evaluation of these variables during unit simulation is done through \n", "# the `eval_exo_dynamic_vars` method\n", - "uf_mt.R1.eval_exo_dynamic_vars(t=0.1)" + "uf_mt.R1A.eval_exo_dynamic_vars(t=0.1)" ] }, { "cell_type": "markdown", "id": "0d1b7290", - "metadata": { - "slideshow": { - "slide_type": "skip" - } - }, + "metadata": {}, "source": [ "[Back to top](#top)" ] @@ -2201,11 +2103,7 @@ { "cell_type": "markdown", "id": "d8205f55", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, + "metadata": {}, "source": [ "### 3.2. `DynamicInfluent`\n", "The [DynamicInfluent](https://qsdsan.readthedocs.io/en/latest/api/sanunits/DynamicInfluent.html) is a `SanUnit` subclass for generating dynamic influent streams from user-defined time-series data. The use of this class is, to some extent, similar to an `ExogenousDynamicVariable`." @@ -2215,11 +2113,7 @@ "cell_type": "code", "execution_count": 38, "id": "3ea577e7", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, + "metadata": {}, "outputs": [], "source": [ "from qsdsan.sanunits import DynamicInfluent as DI\n", @@ -2230,18 +2124,13 @@ "cell_type": "code", "execution_count": 39, "id": "7c2c9521", - "metadata": { - "scrolled": false, - "slideshow": { - "slide_type": "slide" - } - }, + "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(
,\n", - " )" + " )" ] }, "execution_count": 39, @@ -2250,7 +2139,7 @@ }, { "data": { - "image/png": "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vaB169blktGePXsalUoltm7dak2wT58+LUtJSRH69OnT+JbX85QJERERUaUkrbjOmDFDvX79euWPP/5YoNVqzVevXhUAwMvLy+zm5gYvLy9MmTJFP3v2bLW3t3ehh4eH+dlnn1X36tXLWNFEgUaBp0yIiIiIKiTpOCxBECpc8b948WLdtGnT9IC4AcHMmTPV3377rbKoqAiDBw82LFq0SBcUFGRTq0B2dja8vLwcfxwWERERURNl6zgsh5rjWh+YuBIRERE5Nqec40pEREREVBkmrkRERETkFJi4EhEREZFTYOJKRERERE6BiSsREREROQUmrkRERETkFJi4EhEREZFTkHTnrIZgNov7FOTkNOpxtUREREROy5KnWfK2yjT6xDU3NxcAEBwcLHEkRERERFSV3NxceHl5Vfr9Rr9zlsFgQGpqKjQaDWSy+u+MyMnJQUhIiDY5OTmXO3U5J/4MnR9/hs6NPz/nx5+h82von6HJZEJeXh4CAwOhUFReV230FVeFQiFJtdXDwwNVbVlGjo8/Q+fHn6Fz48/P+fFn6Pwa8mfYrFmzao/h4iwiIiIicgpMXImIiIjIKTBxrWMuLi547bXXil1cXKQOhWqIP0Pnx5+hc+PPz/nxZ+j8HPVn2OgXZxERERFR48CKKxERERE5BSauREREROQUmLgSERERkVNg4kpEREREToGJax1buHChMjQ0VKNWq7U9e/Z0i4uL4/+xE3j77bdV3bt3d9dqtVpfX1/NAw884Hr69Gn+7JzYO++8oxIEQfvMM8841pJYqlJKSorw6KOPqr29vTWurq7ajh07uh84cIB/i07CYDDg1VdfdQkLC9O4urpqW7VqpXnjjTdUJpNJ6tCoAjt37pTfe++9roGBgRpBELQ//PBDuY2pTCYT5syZ4xIQEKBxdXXVDhw40O3s2bOS/j3yyaAOff3114qXXnpJPXfu3KLDhw/nd+7c2XTvvfe6p6WlCVLHRlXbvXu3YsaMGcWxsbH5v//+e4Fer8fQoUPd8vLypA6NamD//v2yJUuWqKKiovhq6UQyMzPRr18/d6VSid9++63g5MmTeQsWLNB5e3ubpY6NbDNv3jzVV199pfzkk090p06dynvvvfd0H374ocvHH3+skjo2ul1+fj46d+5s+vTTT3UVff+9995Tff7556rPP/9cFxcXl+/u7m4eNmyY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\n", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAq4AAAGZCAYAAABIaLOOAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjYuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8o6BhiAAAACXBIWXMAAA9hAAAPYQGoP6dpAACOrUlEQVR4nO3dd3yT9fYH8M+T1bRN2tLSCV3MUsqQUSggCIKAAxEEVKaICvdeB4gL8XpdiF6uiv4Ulb0U3KACypLVsmQJZdMFdAClu2mzfn88TdpCR9L1JO3n/Xrl1TZ9khxom5yc53zPVzCbzTkgIiIiInJwMqkDICIiIiKyBRNXIiIiInIKTFyJiIiIyCkwcSUiIiIip8DElYiIiIicAhNXIiIiInIKTFyJiIiIyCkopA6gvhkMBqSmpkKj0UAmY55ORERE5GhMJhPy8vIQGBgIhaLy9LTRJ66pqakICQnRSh0HEREREVUtOTk5Nzg4uNLvN/rEVasVc9aUlBR4eHhIHA0RERER3SonJwfBwcHWvK0yjT5xFQQBAODh4cHElYiIiMiBWfK2yrDpk4iIiIicAhNXIiIiInIKTFyJiIiIyCk0+h5XIiIialhGoxF6vV7qMMiBKJVKyOXyWt8PE1ciIiKqE2azGWlpacjKypI6FHJAXl5eCAgIqHYBVlWYuBIREVGdsCStfn5+cHNzq1WCQo2H2WxGQUEBMjIyAACBgYE1vi8mrkRERFRrRqPRmrT6+PhIHQ45GFdXVwBARkYG/Pz8atw2wMVZREREVGuWnlY3NzeJIyFHZfndqE3/MyuuTsJoMuNgQiYycnXw06oRHe4NuYynYIiIyLGwPYAqUxe/G0xcncCWk6l485d4pGbrrNcFeqrxxgORGBZV8z4RIiIiImfCVgEHt+VkKmasOVIuaQWAtGwdZqw5gi0nUyWKjIiIiKhhMXF1YEaTGW/+Eg9zBd+zXPfmL/Ewmio6goiIyDkZTWbEXbyBDceuIO7ijUb5OhcWFgZBECAIgkOOD/vPf/5jje/jjz+WOhwrJq4O7GBC5m2V1rLMAFKzdTiYkNlwQREREdWjLSdT0e/9HXh08X48t+4YHl28H/3e31GvZxivXbuGGTNmICQkBC4uLggICMDQoUOxb98+m25//PhxjBgxAn5+flCr1QgLC8O4ceOs458q89ZbbyE1NRWenp518c+wSXh4OLZt24Y///yzyqR59uzZSE1NRcuWLRssNlswcXVgGbmVJ63ljsux7TgiIiJHJlV73OjRo3H06FGsXLkS586dw8aNG3HXXXfhxo0b1d722rVruPvuu+Ht7Y3ff/8dp0+fxvLlyxEUFIT8/Pwqb6vVams9kN8eJ06cwM2bNzFgwIBqj9VoNAgICKiT3a7qEhNXB+anVdt03Cc7zmPT36mN8lQKERE5L7PZjIJig02XXJ0eb2w8VWV73H82xiNXp7fp/sxm214Ts7KysGfPHrz//vsYOHAgQkNDER0djVdffRUjRoyo9vb79u1DdnY2lixZgjvuuAPh4eEYOHAgPvroI4SHh9v+nwVgxYoV8PLywq+//or27dvDzc0NDz/8MAoKCrBy5UqEhYWhWbNmePbZZ2E0Gq23S01NxX333QdXV1eEh4fj66+/RlhY2G2n+Dds2IBhw4ZBqVTaFZcj4VQBBxYd7o1ATzXSsnUV/iFbXLyWj3+sPYJQHzdM6xeOh7sHw1XlWO+QiIio6SnUGxH579/r5L7MANJydOj0nz9sOj7+raFwU1Wf5mg0Gmg0Gvz888/o3bs3XFxc7IorICAABoMBP/30Ex5++OFaV08LCgrwySefYN26dcjNzcWoUaPw0EMPwcvLC5s2bcKlS5cwevRo9O3bF+PGjQMATJo0CdevX8eff/4JpVKJWbNmVdimsHHjRsyaNatW8UmNFVcHJpcJeOOBSADArX8GQsnl/dGd8cygNvByUyLpRgFe33AKfd/fgY+2nsONvKKGDpmIiMipKBQKrFixAitXroSXlxf69u2LOXPm4MSJEzbdvnfv3pgzZw4ee+wxNG/eHMOHD8d///tfpKen1ygevV6PRYsW4Y477kD//v3x8MMPY+/evVi6dCkiIyNx//33Y+DAgdi5cycA4MyZM9i2bRsWL16MXr16oVu3bliyZAkKCwvL3e+VK1dw4sQJDB8+vEZxOQpWXB3csKhALJrQ7bY5rgG3zHGdcVdrfHsoBUv2JuDyzUIs3H4eX+y6iIe7t8STd7ZCWHN3qf4JRETURLkq5Yh/a6hNxx5MyMSU5YeqPW7F4z0RHe5t02PbavTo0bjvvvuwZ88e7N+/H5s3b8YHH3yAJUuWYMqUKdXe/t1338WsWbOwY8cOHDhwAF988QXmzZuH3bt3o1OnTjbHAYi7S7Vu3dr6tb+/P8LCwqDRaMpdZ6monj17FgqFAt26dbN+v02bNmjWrFm5+924cSP69esHLy8vu+JxNKy4OoFhUYHY+/IguCnFH9eCMV2w9+VB5TYfcFMpMKVvOP6cfRf+77E70LmlJ4oMJqw9kIyB//sT01f/hSPJN6X6JxARURMkCALcVAqbLne29UWgp/q2M4zW+4K4+c6dbX1tuj97T9mr1WoMGTIEr7/+OmJjYzFlyhS88cYbNt/ex8cHY8aMwYIFC3D69GkEBQVhwYIFdsUA4Lb+U0EQKrzOZDLZdb8bN260qWfX0TFxdRJymQBzyZ9zryq2e1XIZbi/cxA2/LMvvnmyNwa294XZDGw5lYZRn8dizBex2BqfDhMXchERkQOprj0OAN54ILLBtjuPjIysdipAZVQqFVq3bl3j29ujffv2MBgMOHr0qPW6Cxcu4ObN0mJVXl4edu7ciQcffLDe46lvbBVwIsVG8d2VUl79+w1BEBDT2gcxrX1wLj0Xi3dfws/HruBQ4k0cSjyMVr7uePLOVnjojhZQ23E6hYiIqL7Y2h5Xl27cuIExY8Zg6tSp6Ny5M7RaLQ4fPowPPvjApkTv119/xbp16/DII4+gXbt2MJvN+OWXX7Bp0yYsX768zuO9VUREBAYPHoynnnoKixYtglKpxAsvvABXV1dr1XnLli1o164dwsLCbrv933//Da1Wa/1aEAR06dKl3uOuKSauTsJoMlvHXakU9hXK2/lr8d8xXTB7aHss35eItQeScOlaPl798W/8749zmNInFBN6h8LLTVXu8Q4mZCIjVwc/rRrRVVR5iYiI6sqwqEAMiQxosNcgjUaDXr164aOPPsLFixeh1+sRHByMJ598EnPmzKn29pGRkXBzc8MLL7yAlJQUuLi4oG3btliyZAkmTpxYLzHfatWqVXjiiSfQv39/BAQE4L333sOpU6egVotjNTds2FBpm0D//v3LfS2Xy2EwGOo95poSzGZzjtRB1Kfs7Gx4eXlps7Oz4eHhIXU4NabTGxHx+hYAwMk3h0LjUvP3HHlFBqw7mIxlexNwteQdrZtKjrE9gvFEv3Ccupp927vdwHp8t0tERM5Pp9MhISEB4eHh1oSJKhcWFobnn38ezz//fJ3f9+XLlxEcHIxt27ZhwIAB8Pf3x+bNmxEdHS1pnFX9juTk5MDT0xNZWVm5Ve0kxh5XJ1FkKG3CVspr965T46LAtDtbYddLA/HRuC7oEOiBgmIjVsQmYsB/d2K6BLuWEBERNTUvv/wyNBoNsrOza3U/O3bswMaNG5GQkIDY2Fg88sgjCAsLQ//+/ZGZmYmZM2eiZ8+edt3nvHnzoNFokJycXKvY6hpbBZyE3liauKps6HG1hVIuw0N3tMTIri2w98J1fLnrIvZeqHh7OzPE5vg3f4nHkMgAtg0QEVGTsHbtWjz99NMVfi80NBSnTp2q0f3u2rULer0eAMr1mNaEXq/HnDlzcOnSJWi1WvTp0wdr166FUqmEn58f5s6da/d9Tp8+HWPHjgUA+Pr61iq+usTE1UkUGywLs4Q639NYEATc2dYXCpms0sQVEJPX1GwdDiZkIqa1T53GQHWLPcpERHVjxIgR6NWrV4Xfq83WqaGhoTW+7a2GDh2KoUNtm5drK29vb3h7Vz8vt6ExcXUSloprXVVbK5KRq6v+IDuOI2lsOZnKHmUiojqi1WprXRGlusMeVydhqbjaO1HAHn5a25rpbT2OGt6Wk6mYwR5lIiJqpJi4Ookig+0zXGsqOtzbpl1LbNlqjxqe0WTGm7/Eo6KtJSzXvflLvHWsGhERkbNh4uokrK0C9VhxdbRdS8g+BxMyb6u0llW2R5mIiMgZMXF1Eg3RKgCU7loS4Fm+HSDAU41FE7qxR9KBsUeZiIgaOy7OchJ6Y8muWfXYKmBh2bXks50X8OHWc2jr544tzw9gpdXB+WldbDyOPcpEROScWHF1EsVGI4D6r7hayGUCuoc2AyCOy2LS6tiMJjN+PnalymPYo0xETsNkBBL2AH9/L340GaWOSHJhYWEQBHEkZlZWltTh3OY///mPNb6PP/643h6HiauTKG6AxVm3ci/ZVja/iE8YjkxvNGHm+mNYf+iytRe5orcZZrBHmYicQPxG4OMoYOX9wA9PiB8/jhKvryfXrl3DjBkzEBISAhcXFwQEBGDo0KHYt2+fTbe3JJX79+8vd/3zzz+Pu+66y/r1f/7zH3Tt2vW22ycmJkIQBBw7dqzKx3nrrbeQmpqKqrZErWvh4eHYtm0b/vzzzyqT5tmzZyM1NRUtW7as13iYuDqJ4gZsFbDQlCSueUWGBntMso9Ob8SMNUew8fhVKGQC/u+xbviigh5lAOjb2oc9ykTk2OI3At9OAnKulr8+J1W8vp6S19GjR+Po0aNYuXIlzp07h40bN+Kuu+7CjRuVb8pzK7VajZdffrle4rPQarUICAio842IKnPixAncvHkTAwYMqPZYjUaDgIAAyOXyeo2JiauTaKjFWWWVTVzNZo5QcjT5RQY8sfIQtp1Oh4tChsWTeuC+zoEYFhWIvS8PwjdP9sbCR7rizREdAQD7EzKRcD1f4qiJqEkxm4HifNsuuhxg80tAVUP9trwsHmfL/dn4upWVlYU9e/bg/fffx8CBAxEaGoro6Gi8+uqrGDFihM3/1Keeegr79+/Hpk2bbL5Nba1YsQJeXl749ddf0b59e7i5ueHhhx9GQUEBVq5cibCwMDRr1gzPPvssjMbSs6epqam477774OrqivDwcHz99dcICwu77RT/hg0bMGzYsFrtEFbXuDjLSUjTKiC+azKazCgymKBW1u+7KLJddqEeU1ccwl9JN+GukmPJ5J7ltuGVy4RyX+86dw07zmTgo63n8Mmjd0gRMhE1RfoCYF5QHd2ZWazEzg+27fA5VwGVe7WHaTQaaDQa/Pzzz+jduzdcXGxb6Hqr8PBwTJ8+Ha+++iqGDRsGmaxhXq8LCgrwySefYN26dcjNzcWoUaPw0EMPwcvLC5s2bcKlS5cwevRo9O3bF+PGjQMATJo0CdevX8eff/4JpVKJWbNmISMj47b73rhxI2bNmtUg/w5bseLqJCxzXF0asOLqrip9X8N2AcdxI68Ijy3ej7+SbsLTVYk103qVS1Ir8sI97QAAG49fxenUnIYIk4jIKSgUCqxYsQIrV66El5cX+vbtizlz5uDEiRN239fcuXORkJCAtWvXVnrM33//bU2WLZeOHTvWOH69Xo9FixbhjjvuQP/+/fHwww9j7969WLp0KSIjI3H//fdj4MCB2LlzJwDgzJkz2LZtGxYvXoxevXqhW7duWLJkCQoLC8vd75UrV3DixAkMHz68xrHVB1ZcnURpxbXhFtbIZALcVXLkFxuRpzOguaZm70Kp7qRl6zBh6QFcyMhDc40Kq5/ohQ6BHtXermOQJ+7rHIjfTqTif3+cw5LJPRogWiJq8pRuYuXTFkmxwNqHqz9u/PdAaB/bHttGo0ePxn333Yc9e/Zg//792Lx5Mz744AMsWbIEU6ZMsfl+fH19MXv2bPz73/+2Vjdv1b59e2zcWL5X98qVK+UWcdnDzc0NrVu3tn7t7++PsLAwaDSactdZKqpnz56FQqFAt27drN9v06YNmjVrVu5+N27ciH79+sHLy6tGcdUXVlydRHED7JxVEXcu0HIYyTcKMObLWFzIyEOQpxrfPh1jU9JqMXNwO8gEYNvpdBxNvlmPkRIRlRAE8XS9LZfWgwCPIFQ8FwXi9R4txONsuT87FzCp1WoMGTIEr7/+OmJjYzFlyhS88cYbdv+TZ82ahcLCQnz++ecVfl+lUqFNmzblLqGhoXY/jsWt/aeCIFR4nclksut+N27caFePb0Nh4uokpFicBQAatWUkFhNXKV3IyMWYL2ORklmIUB83fDs9Bq18NdXfsIw2fhqM6iaOKfnfH+fqI0wiopqTyYFh75d8UcnG48Pmi8c1gMjISOTn27+gVaPR4PXXX8e7776L3Nzceoisdtq3bw+DwYCjR49ar7tw4QJu3iwtaOTl5WHnzp148MEHpQixSpImrgaDAa+++qpLWFiYxtXVVduqVSvNG2+8oSr7rsBkMmHOnDkuAQEBGldXV+3AgQPdzp492+QSbkvFtSEXZwEcieUITl7Jxtgv9yM9pwjt/DX47ukYtGxm+ymwsp67uy2UcgF7L1xH7MXrdRwpEVEtRY4Axq4CPG4Z3ecRJF4fWfcVwBs3bmDQoEFYs2YNTpw4gYSEBHz33Xf44IMPapy4PfXUU/D09MTXX39dx9HWXkREBAYPHoynnnoKBw8exNGjR/HUU0/B1dXVOmZry5YtaNeuHcLCwm67/d9//41jx45ZL8ePH2/Q+CXtcZ03b57qq6++Ui5fvlwXFRVlPHTokHzatGmunp6emDVrVjEAvPfee6rPP/9ctWzZssJWrVqZ5s6d6zJs2DC3+Pj4PFdXVynDb1B6iSqulgVaTFyl8VdSJqYsP4RcnQGdW3pi5ePRaOauqvH9BXu74dHoEKyKS8KC38/ihxk+DTYPkIjIJpEjgIj7xJ7XvHRA4y/2tNZTpVWj0aBXr1746KOPcPHiRej1egQHB+PJJ5/EnDlzanSfSqUSb7/9Nh577LE6jrZurFq1Ck888QT69++PgIAAvPfeezh16hTUanEG+IYNGyptE+jfv3+5r+VyOQyGhssRBLPZLNkS4+HDh7v6+/ubV6xYobNcN3LkSFdXV1fzN998ozOZTAgKCtI8//zzxa+88koxIM5bCwgI0C5durRw/Pjx1f5PZWdnw8vLS5udnQ0PD9v7AR3NvzecxKq4JDw7qA1m3dO+wR73yVWHsTU+HfMe6oTHeoU02OMSsPf8dTy56jAK9UZEh3lj6ZQe0KprP0svI0eH/v/dCZ3ehGVTemBQhH8dREtETZ1Op0NCQgLCw8OtCRDVnbCwMDz//PN4/vnn6/y+L1++jODgYGzbtg0DBgyAv78/Nm/ejOjo6DqNs6rfkZycHHh6eiIrKyu3qp3BJD3lHhMTY9y5c6fizJkzMgA4cuSILDY2Vj58+HADAFy6dElIT08XhgwZYk1Qvby80LNnT2NcXFyFb710Oh2ys7Otl5ycxjH6Ry95q4C+QR+3qdsan46pKw6hUG9E/3a+WDk1uk6SVgDw81BjckwYAGDB7+dgMnFzCSIiZ/Dyyy9Do9EgOzu7VvezY8cObNy4EQkJCYiNjcUjjzyCsLAw9O/fH5mZmZg5cyZ69uxp133OmzcPGo0GycnJtYqtOpK2Crz22mvFOTk5QmRkpLtcLofRaMSbb75ZNGnSJAMApKamygAgICCg3Curn5+fOS0trcIM7p133nF59913a34u1UEVSdUqULIJQV6RsZojqa5sOHYFs749DqPJjGEdA7Dw0a5wUdTtKbLpA1pj7YFkxKfmYNPJVNzfua4GhBMRNS5r167F008/XeH3QkNDcerUqQaJY9euXdDrxSKSVqut1X3p9XrMmTMHly5dglarRZ8+fbB27VoolUr4+flh7ty5dt/n9OnTMXbsWADiWLD6Imnium7dOsW6deuUq1evLoyKijIdPXpUPmvWLJcWLVqYp06dWqMS39y5c4tefPHFIsvXOTk5CAkJqd1P2AFIsXMWAGhcxCofpwo0jG8OJmPOT3/DbAZG3dECHzzcGYp6+Jk3c1dh2p3h+HjbeXy49RyGdQyol8chInJ2I0aMQK9evSr8XkNuhVqbkVm3Gjp0KIYOHVpn9wcA3t7e8Pb2rtP7rIikievLL7+sfvHFF4ssvapdunQxJSYmCvPnz1dNnTpVHxgYaAKAtLQ0oUWLFtaqa0ZGhtClS5cKS4BqtbpR9tboJZrjqrFUXHVMXOvbkj2X8M5vpwEAE3qH4K0RUZDJ6m/h1BP9wrEyNhGXruXjp6NXMKaHjdsoEhE1IVqtttYVTqo7kpZYCgoKbtvLVy6Xw2wWc9RWrVqZ/f39zdu2bbMm2NnZ2Th06JA8JiamSZ27lmqOq3UDgmImrvXFbDbj423nrEnr0wNa4e0H6zdpBQCtWonpA8TdVj7edh5Fhib1J0VE9cTyGk50q7r43ZA0cb3vvvsM8+fPd9m4caPi0qVLwvfff69YuHChasSIEQYAkMlkeOaZZ4rnz5/v8tNPPymOHz8umzBhgmtgYKB51KhRTSqT0hvFH7ZKosVZbBWoH2azGfM2ncbH284DAGbf0w6vDItosBFVk2LC4Kt1wZWsQqw/lNIgj0lEjZPltHlBQYHEkZCjsvxu1KbFQtJWgc8++0z32muvufzrX/9SX7t2TQgMDDRPmzZN/+abb1p7VF999dXi/Px8Yfr06ers7GwhJibGuHnz5oKmNMMVkHDnLEvFla0Cdc5oMmPuzyfxzUFxBeYbD0Ti8b7hDRqDq0qOZwe1wesbTuHTHRcwpnswXFUNsysNETUucrkcXl5eyMjIAAC4ublxTjQBEIs0BQUFyMjIgJeXF+Tymr/OSJq4enh44NNPPy369NNPiyo7RiaTYd68eUXz5s2r9JimoEiqcVhqbkBQH/RGE2Z/dxwbjl2FTADmj+qMsT2l6TEd1zMEX+6+hMs3C7EqLhFPl7QPEBHZKyAgAACsyStRWV5eXtbfkZqSNHEl20m2c5alVYA9rnVGpzfimW+OYmt8OhQyAR8/0lXScVQqhQzP3d0WL35/Aot2XcRjvULqbGYsETUtgiAgMDAQfn5+1tFNRIDYHlCbSqsFE1cnUWyZKiDVBgRsFagTBcUGPLXqL+y9cB0qhQxfTOjmEDtXPXRHCyzadRGXruVjyZ4EzBzSTuqQiMiJyeXyOklSiG7FwY1OorTHtWH7hUoXZ3HFeW1lF+oxcelB7L1wHW4qOVY83tMhklYAUMhleGGIuJXw0r0JuJlfLHFEREREt2Pi6iSsc1wb+B2spVWg2GjiuKRauJFXhMcW78dfSTfhoVZgzbRe6NO6udRhlTM8KgCRgR7IKzLgi10XpQ6HiIjoNkxcnYRkc1zLrDBn1bVm0nN0GPfVfpy6moPmGhXWPRWDbiHNpA7rNjKZgNlDxRaBlXGJSM/RSRwRERFReUxcnUSxdapAw7YKKOQyuCrF5JWzXO2XklmAMV/E4UJGHgI91Vj/dAwigzykDqtSA9v7oVuIF3R6E/5vxwWpwyEiIiqHiauTkKriCpS2C+RygZZdLmTkYcwXcUjOLECojxu+fToGrX01UodVJUEQ8OLQCADAukPJSMnkIHEiInIcTFydgNlslmyqAABoXEoqrhyJZbOTV7Ix7ss4pOXo0M5fg++ejkGwt5vUYdkkprUP+rVpDr3RjIXbz0sdDhERkRUTVydgNJlh2d5XioorNyGwz19JN/Ho4v24kV+MTi08se6pGPh5qKUOyy6zh4oTBn48chkXMvIkjoaIiEjExNUJWKqtgEStAirOcrXVvgvXMXHpAeTqDOgZ1gxrn+wFb3eV1GHZrWuwFwZ38IfJDHy09ZzU4RAREQFg4uoU9Aaz9fOG3vIVKDvLlYlrVbbFp+PxFYdQUGzEnW2bY9XUXvBw4h2oXrinHQQB+O3vVJy8ki11OERERExcnUGRURxDJQiAQtawUwUAtgrYYuPxq5i+5i8UG0wY2tEfSyb3gKvKuXeN6RDogQdKtqL9kFVXIiJyAExcnYB1ooBcBkFo+MTVMlWAiWvF1h1MxnPrjsJgMmPUHS3w2WPd4KJw7qTVYuaQdpDLBOw4k4G/kjKlDoeIiJo4Jq5OQG8UWwWkmCgAsFWgKkv3JuCVH/+G2QyM7xWCBWO6QCHRz6k+hDd3x8PdWgIA/vv7WZjN5mpuQUREVH8azytsIyblDFegNHHN485ZVmazGQu3ncfbv8YDAJ7u3wrvjIyCTIJWjvr27OC2UMll2H8pE/su3JA6HCIiasKYuDoBS+IqxcIsgK0CtzKbzXhv8xl8tE3s+3xhSDu8MjxCkjaOhtDCyxWP9QoBAPz3D1ZdiYhIOkxcnYB18wHJKq7c8tXCZDLjtZ9P4qvdlwAAr98fiWfubttok1aLfw5sA1elHMdTsrA1Pl3qcIiIqIli4uoEpG8VEEc6NfWKq8Fowqxvj+HrA8kQBOD90Z3wRL9wqcNqEL5aF0zpGwZAnDBgMrHqSkREDY+JqxPQG6VuFRArrk15A4IigxH/WHsEPx+7CoVMwCeP3IFxPUOkDqtBPd2/FbRqBc6k5eKXE1elDoeIiJogJq5OQPqKa8lUgeKmmbgWFBswbeVh/BGfDpVChi8ndscDXYKkDqvBebmp8NSdrQCIu2npy+zoRkRE1BCYuDoBa4+rXJo+SssGBE2xxzVHp8ekpQex5/x1uKnkWDGlJ+7u4C91WJJ5vF84vN1VSLxRgB/+uix1OERE1MQwcXUCeokXZ7mrxMQ1t4m1CmTmF+OxxftxOOkmPNQKrJnWC33aNJc6LElpXBT4x12tAQCfbD+PIgNHpBERUcNh4uoEisrsnCUFS6tAkcEEQxM5PZyeo8O4L+Nw8koOfNxV+Oap3ugW0kzqsBzChN6hCPBQ42q2Dl8fSJY6HCIiakKYuDoB6RdnKayf5zeBTQhSMgsw5os4nM/IQ6CnGuufjkHHIE+pw3IYaqUcz9zdBgDw2c4LKGiivc9ERNTwmLg6AakXZ6kUMutj5xbpJYmhoVzIyMOYL+KQnFmAEG83fPt0DNr4aaQOy+GM7RGMEG83XM8rxvJ9iVKHQ0RETQQTVycgdeIKlJks0IgrrvFXczDuyzik5ejQ1k+D76bHINjbTeqwHJJSLsPzg9sCAL7cdRHZhY37DQ0RETkGJq5OwLo4S6JWAaA0cW2smxAcSb6JR76Kw438YkS18MD6p2Pg76GWOiyH9mDXFmjrp0GOzoAley5JHQ4RETUBTFydgCNUXN0bceIae+E6Jiw5gBydAT1Cm+HrJ3vD210ldVgOTy4T8MI97QAAS/cm4HpekcQRERFRY8fE1QkUG8XtNaVanAUAWpfGOct1++l0TFlxCAXFRtzZtjlWPREND7VS6rCcxtCOAejUwhMFxUYs+vOi1OEQEVEjx8TVCThGxbXxbfv6y/GreHr1Xyg2mHBPpD+WTO4BN5Wi+huSlSCUVl1X709CanahxBEREVFjxsTVCRQbxQVRUva4NrZWgfWHkvHsuqMwmMx46I4W+Hx8N7go5FKH5ZQGtPNFdJg3ig0mfLrjgtThEBFRI8bE1QnoDWKrgJQVV20j2vZ12d4EvPzD3zCbgfG9QvC/MV2gkPBNgbMTBAGzh7YHAHx7KAVJN/IljoiIiBorvlo7gWIHmCpg2fbVmSuuZrMZn24/j7d+jQcAPNW/Fd4ZGQWZTJA4MucXHe6N/u18YTCZ8fG281KHQ0REjRQTVydg6XFVyqVLsJy9VcBsNmP+5jP439ZzAIBZQ9rh1eEREAQmrXVldkmv68/HruBceq7E0RARUWPExNUJWCuuEvZgOnOrgMlkxtyfT+LL3eKs0dfvj8Szd7dl0lrHOrf0wrCOATCbgQ//OCd1OERE1AgxcXUCjjFVwDkrrgajCS98dxxrDyRDEID5ozrhiX7hUofVaM26px0EAdhyKg0nLmdJHQ4RETUykiauoaGhGkEQtLdepk+frgaAwsJCTJ8+Xe3t7a3RaDTakSNHuqampja5Mpll5yy2CtinyGDEP78+gp+OXoFCJuCTR+7AI9EhUofVqLXz12Jk1xYAgAWsuhIRUR2TNHE9ePBg/pUrV/Isly1bthQAwJgxY/QA8Nxzz6l/++03xfr16wt37NiRn5qaKowaNcpVypilYKm4ukg5VcC6AYFRshjsUVhsxLSVh/H7qXSoFDJ8MaE7HugSJHVYTcLzg9tCIROw+9w1HEzIlDocIiJqRCRNXP39/c1BQUHWy6+//qpo1aqVeeDAgcasrCysWLFCuWDBAt2QIUOM0dHRpuXLl+v2798v37dvX5MauFna48pWAVvk6PSYtOwA9py/DjeVHMun9MTgSH+pw2oyQn3cMbZnMABgwe9nYTabJY6IiIgaC4fpcS0qKsLXX3+tnDx5crFMJsOhQ4fker0e99xzjzVTioyMNAUHB5tjY2MrTVx1Oh2ys7Otl5ycnIb5B9Sj0qkCDrBzloMnrjfzizF+8QEcSrwJrVqB1U/0Qt82zaUOq8l5ZlAbqBQyHEzMxO7z16UOh4iIGgmHSVx//PFHRXZ2tjB16lQ9AKSlpQkqlQrNmjUrd5yfn585LS2t0mbPd955x8XLy0truYSEhGjrOfR65whzXLUuSgCOPVUgI0eHcV/F4e8r2fBxV2HdU73RPbRZ9TekOhfo6YqJvUMBsOpKRER1x2ES16VLl6ruueceQ8uWLWv1Cjd37tyirKysXMslOTnZ6QdKWhdnSdoqIFZcC4qNMJocLwlJySzAmC/jcC49DwEeaqx/OgYdgzylDqtJm3FXa7ip5Pj7SjZ+P5UmdThERNQIOETimpCQIOzcuVM+bdo0veW6gIAAc3FxMW7evFnu2IyMDCEgIKDSzEmtVsPT09N68fDwqMfIG4Z1HJakrQIK6+f5xY5Vdb14LQ9jv4xD0o0ChHi74bvpMWjjp5E6rCavucbFOnrsf3+cc8g3PERE5FwcInFdunSpytfX1/zAAw9YM6KePXsalUoltm7das2YTp8+LUtJSRH69OnjHEvbARhNZsRdvIENx64g7uKNGr14O8JUAReFzDqOy5HaBeKv5mDcl3FIzdahrZ8G302PQbC3m9RhUYlpd7aCh1qB8xl52Hj8itThEBGRk1NUf0j9MhqNWLVqlXLChAl6pVJpvd7LywtTpkzRz549W+3t7V3o4eFhfvbZZ9W9evUy9u3b1ykS1y0nU/HmL/FIzdZZrwv0VOONByIxLCrQ5vvRG8VkV8rFWYIgwN1FgawCPfJ0BsABzsIfSb6JKcsOIkdnQFQLD6ya2gve7iqpw6IyPF2VeHpAa/z397P4aOt53N85SNLfYyIicm6Sv4L88ccf8pSUFKFsm4DFwoULdffee69h7NixbgMHDnT39/c3//jjj4VSxGmvLSdTMWPNkXJJKwCkZeswY80RbDmZavN9OcLOWQDgrnKckVixF69jwpIDyNEZ0CO0Gb5+sjeTVgf1eN8wNNeokJxZgG8Pp0gdDhEROTHJE9fhw4cbzWZzbkREhOnW77m6uuKLL77QZWZm5ubn5+du2LChMCgoyOEb5YwmM978JR4VBWq57s1f4m1qGzCbzdapAlJXqrRqx9iEYMeZdDy+/BAKio3o16Y5Vj0RDQ+1svobkiTcVAr8c2AbAMCn2y9Ap3eKEyZEROSAJE9cG6ODCZm3VVrLMgNIzdbZtKuQpU0AcICKq3UTgtuK4w3m1xNX8dSqv1BkMGFIpD+WTO4BN5XkHS9Ujcd6hSDIU420HB3W7E+SOhwiInJSTFzrQUZu5UmrvcdZqq2AtIuzgLKJqzQVs28PpeDZb47CYDLjwa5B+Hx8N6iVTWoTNaflopDj2bvbAgA+//OiQ7SbEBGR82HiWg/8tOo6O05vKE1cJW8VcLG0CjR80rF8XwJe+uEETGaxevfR2K6S/3+QfUZ3b4kwHzdk5hdj+d4EqcMhIiInxFf+ehAd7o1ATzUq295LgDhdIDrcu9r7slRc5TIBclmlG4Y1CCm2fTWbzfi/Hefx5i/xAIAn7wzHuyOjIJP4/4Lsp5TLMHNIOwDAV3suIbtAupYTIiJyTkxc64FcJuCNByIr/J4l3XrjgUibElFH2HzAorRVoGESV7PZjPe3nMWCP84BAGYOboc593aAIDBpdVYPdA5CRIAWuToDvtx9UepwiIjIyUifDTVSw6ICsWhCN7ipyvdgBniqsWhCN5vnuJZOFJA+WbO0CuTp6j9xNZnM+PeGU/hil5jczL2vA54b3JZJq5OTyQTMKqm6Lt+XaHM/OBEREcDEtV4NiwpE1+DSSf1T+4Zh78uD7Np8oHSGq/SLkNwbqMfVYDRh9nfHsXp/EgQBeG9UJ0y7s1W9PiY1nCGR/ugS7IVCvRGf72TVlYiIbMfEtZ4lXC+wfh7k5Wp3n6reaGkVkL7S2BCtAkUGI/719VH8ePQKFDIBH4/rikejQ+rt8ajhCYKAF+9pDwD4+kAyrmQ5xZ4iRETkAJi41qOCYkO5ea5lR1vZylF2zQJKNyCor8S1sNiIJ1f9hS2n0qBSyPDFhO54sGuLenksklbfNj7o3cobxUYTPt1+XupwiIjISUifDTViCdfzy32tN9i/6ZcjJa6WLV/ro1UgV6fH5GUHsfvcNbgq5Vg+pScGR/rX+eOQYxAEAS8OFauu3/11+ba/FSIioopInw01YpeulX8xLjbaP7jfUbZ7BQBNPVVcb+YXY/ySAziYmAmtWoE106LRt03zOn0McjzdQ70xsL0vjCYzPtp6TupwiIjICUifDTVityauZbdvtZUjVVw19dDjmpGjw7iv4nDicja83VX45sne6B5a/XxbahxeKOl1/eXEVZxJy5E4GiIicnTSZ0ONWML1PAClSWexwf4eV0uy6wgV19KpAnWz5evlmwUY+2UczqXnwd/DBd8+3RtRLTyrvyE1GlEtPHFfp0CYzcD//mDVlYiIqiZ9NtSIXSrp22vrpwEAFNUgcbW0F7g4UMU1v9gAk8n+6nFZl67lYewXcUi8UYBgb1d893QftPHT1kWY5GRmDmkHmQBsjU/H0eSbUodDREQOTPpsqBEymsyIu3gdZ9NyAQDt/MXEVV+bqQIOUHG1JK5mM1Cgr3nV9XRqDsZ+GYer2Tq08dPgu6f7IMTHra7CJCfTxk+Dh+5oCYBVVyIiqpr02VAjs+VkKvq9vwOPLj5grbBuPZUOQKwyxl28AaMd1cpiB2oVUCtlsIyhrelkgaPJNzHuyzhczytGxyAPrH+qNwI81XUYJTmj5we3hVIuYO+F64i7eEPqcIiIyEFJnw01IltOpmLGmiPlZrcCQF6xWJ08kpyFRxfvR7/3d2DLyVSb7tORFmcJglCrBVpxF29gwpIDyNEZ0D20Gb5+sjd8NC51HSY5oWBvNzzSU9xoYsEfZ2E2164VhYiIGifps6FGwmgy481f4mHLy21atg4z1hyxKXm17pzlAIkrUGaygM6+xHXnmQxMWX4Q+cVG9G3jg9VPRMPTVVkfIZKT+tegNnBRyPBX0k3sPJshdThEROSAHCMbagQOJmTeVmmtjCW5ffOX+GrbBiwVV0doFTCazBBKWgUOJWTa3PLw24lUPLX6MIoMJgzu4I+lk3vCrWQzAyILfw81JvcJAwAs+P1crRcAEhFRzYhrdW5gw7Erdrc41jdmD3Xk0cX77TreDCA1W4eDCZmIae1T6XGWxFXqqQJbTqbizV/ircn5O5tOY+m+BLzxQCSGRQVWertvD6fglR9OwGQGRnQJwv/GdnGIJJwc0/QBrfH1gWTEp+Zg88k03Ne58t8tIiKqe7e+3gNAoKe62tf7hsIMog6EvfJbjW+bkVt1lVZv3TlLqPFj1FZlvbvVtTys2JeAl74Xk9ZHo4Px0biuTFqpSt7uKjzRLxwA8OHWszDUYBIHERHVTE1f7xsSs4ha+utS7eZO+mmrXlFfJPHirKp6d8u2PBQbTOVOK3y64zz+80s8AODJO8Mx76FOkMukS77JeUy7MxxebkpcvJaPn45ekTocIqImwdbXe6nbBtgqUEujv4qt8W293JSIDq96e9Niy+IsubzGj1Mb1fXuWloeer+3HZn5xbd9//nBbfHc3W0hCExayTZatRLTB7TG/M1n8PG2cwjwUCOzoBh+WjWiw735BoiIqA7l6vQ4l56HLSdTbXq9r67Fsb4xcZWQLS+/esviLIU0L9bVtTJYVJS0AkBEgJZJK9ltckwYPt95AVeydJi47KD1ekfqsyIiciZFBiMuZuTjXHouzqbn4myaeLmSVWjX/diaF9SXWiWuhYWFcHV1ratYmpybBfrqF2cZpd05q7pWhqoIEE8rDIkMYJWM7LLrXAZyKhi5ZumzWjShG5NXIqIKGE1mJGcW4GxarpikpomJasL1/EpP8wd4qOHn4YITl7Orvf/a5AV1we7E1Wg04q233lItXrxYlZGRIZw5cyavTZs25ldffdUlLCzM9PTTT+vrI1BH1SlQi79Tc2t8e1sXZ0nV4xod7o1ATzXSsnU2zagty1FOK5BzsfRZVcQMviEiIgIAs9mM9JyikuppDs6m5eFcei7OZ+RCp694YauHWoGIAA+0C9Cgvb8W7QM80M5fAy83FYwmM/q9v6PS13sBQICnutoWx/pmd+L65ptvqtasWaOaP3++bvr06dZya6dOnYwLFy5UNbXEdXjnwFolrtW9c7HunCVRxVUuE/DGA5GYseYIBMDu5BWQ/rQCORdb+6r5hoiImorsAn1pgpqei3NpeTibnovswopTLheFDO38tWjnr0X7AA3aB3igvb8W/h4ulbbvVfV6b7nFGw9ESl4wsDtxXbt2reqLL74ovOeee4z//Oc/rdd37drVdO7cOWlWEEkov2Q7V3vZ+s5F6qkCADAsKhCLJnS7ba6bt7sSmfnVv0+R+rQCORdb3+ik2tmXRUTk6AqLjbiQkYczaTklvah5OJuWg/ScogqPl8sEhDd3R3trkipeQrzdapRgVvZ6H+BA6wvsTlyvXr0qtG3b9rYatMlkgl7fpIqtAADBpiVWt95GZMs7l9I5rtJOLhsWFYghkQE4mJCJjFwd/LRqdA9thgH/3enwpxXIudj6RufNX+ORlFmAx3qFwN+Db46IqOEYTeZyr4f2TjwxGE1IvJGPM2m5OJdWulgqKbMA5kpObbbwckX7ADFBjSj52MrXHWpl3dYMK3q9d6SJLnYnrhEREabdu3crwsPDy2Wp3377raJLly41Kz86sZjWPvi/nRfsuo0971yKHaDiaiGXCbedmnWG0wrkXGzpq5YJQHahHgu3n8dnOy9gaFQAJvUORXS4N6dYEFG9smdnKbPZjCtZhdYFUufScnEmLReXruVbF1/fyttdVdJ/WlpFbeevgVatrNd/V1kVvd47CrsT19dff71o6tSprpcvXxZMJhO+//575blz52Rr165VbtiwoaA+gnRkvVv5QOsiQ26R7Tv8/Dn7LrjY+A5JbxRfuh0hca2IM5xWIOdiS5/VwkfugBnA6rhEHEq8id9OpOK3E6mICNBiYkwoRnZtAXcXTvsjorpl2Vnq1jfVadk6TF9zBM/f3RaebkqcSxcT1PPpecgrun1CCgC4qeRiYuqvRbuA0ipqc42Kb8CrIJjN5hx7b/Tnn3/K33rrLZe///5blp+fL3Tt2tX4+uuvFw0fPtzhKq7Z2dnw8vLSZmdnw8PDo14eY9LSA9h9/rrNx+9/9W4EeNp2anPoR7txNj0Xa6f1Qt82zWsaYr2r7WkTolvZWtWIv5qD1fsT8dPRK9aVtFoXBR7u0RITe4eila+mwWMnosbHsuq+qsWjFVHKBbT21ZT2oJZ8bOHlChlfJ61ycnLg6emJrKysXE9Pz0qPq1FJ4q677jLeddddTa66Whl7V9qn5+hsTlyLJR6HZStHPq1AzsnWPqvIIA+8N6ozXhnWAd/9lYLV+5OQdKMAy/clYvm+RNzZtjkmxYRhUIQf30wRUY1VN/HEontoM8S08rEulArzcXf413BnwnNpdSDMxw17zld/nMZFjrwiI9JydOhi431belylXpxFJAV73hB5uikx7c5WmNo3HLvPX8PquCTsOJuBPeevY8/562jh5YoJvUMxrmcwvN1V9Rw5ETU2tk48mRQTige7tqjnaJoumxLXZs2aaW3tt8jMzKz5UFMnNefeSKzen1ztcb3Dm2HbmetIz7H9NIPUO2cRORuZTMBd7f1wV3s/JN8owNoDSVh/OAVXsgrx/pYz+GjbOdzfORCTY8LQJdhL6nCJyEnYOvGEIyDrl02J6//+9z9OkK+Cq0qOIZF+2BqfUekxQyL9EOTpCuA60uzoj5F65ywiZxbi44ZX7+2AmUPaYePxq1gVl4iTV3Lw45Er+PHIFXRp6YlJMWG4r3NgnY+UIaLGxTLxpLJ2AY6AbBg2Ja5Tp06ttwGtKSkpwksvveTy+++/KwoLC4VWrVqZli1bVtirVy8TIM6HnTt3rsuyZcuU2dnZQu/evY1ffPGFrn379rYv428Aiyf1xJOrDlWYvA6J9MPiST3xWcnYrDR7Kq4S75xF1BiolXKM7RGMMd1b4mhKFlbHJeG3E6k4fjkbL3x3HO9uOo1xPYMxvlcIWjZzkzpcInJAcpmA1++LxD++PnLb9zgCsuHUqMfVYDDghx9+UMTHx8sAoGPHjqaHHnrIoFTaN2MsMzMT/fr1cx8wYIDht99+K/Dz8zOfO3dO5u3tbV3v9N5776k+//xz1bJlywpbtWplmjt3rsuwYcPc4uPj81xdXau6+wa3eFJPFBYbMW9TPBJvFCDMxw1z7o2Eq0qs5ASUDEm3q1XAgea4Ejk7QRDQLaQZuoU0w2v3dcD6QylYsz8Jqdk6LPrzIr7cdRGDIvwxuU8o+rZuzhW/RFSOi1J8Lb51VB9HQDYcu8dh/f3337IHH3zQLT093bqD1oULF2TNmzc3b9y4saBz5842V0Jnz57tEhcXJ9+3b1+FEwpMJhOCgoI0zz//fPErr7xSDABZWVkICAjQLl26tHD8+PEVD0croyHGYdlq34XrGL/kAFr7umP7C3dVe7zJZEarOZsAAH/NHQwfjUs9R0jU9BiMJmw7nYHV+xOx78IN6/WtmrtjYkwoRndvCY8GHPxNRI7rka/isP9SJqbdGY67I/w5ArIO2ToOy+4y3rRp09QdOnQwpqSk5B47diz/2LFj+cnJyblRUVHGp556yq6O5F9//VXRvXt346hRo1x9fX01Xbp0cV+0aJH1FeLSpUtCenq6MGTIEGuC6uXlhZ49exrj4uIqbEjT6XTIzs62XnJy7B5TW2/8rRXXivccvlXZXTVYcSWqHwq5DMOiArB2Wm9sm9Ufk2NCoXFR4NL1fLz5Szx6z9uO1376G2fTmty6UyIq4+SVbOy/lAmFTMDUvuGIae2DB7u2QExrHyatDcjubOjEiRPy+fPnF3l7lzYfe3t7Y968eUXHjx+3a3VDYmKi7KuvvlK1adPGtHnz5oKnn366eNasWeply5YpASA1NVUGAAEBAeVGpfr5+ZnT0tIqjP2dd95x8fLy0louISEhWnv/jfXFMrs1r8hQ6U4aZTFxJWpYbfy0ePPBKOyfczfefrAj2vppUFBsxNoDyRj68W6M/TIOv51ItS6aJKKmY8meSwCA+zoHIsjLsVoVmxK7e1zbtGljSktLEzp16lTu+vT0dKFVq1Z2PZubTCZ069bN+MEHHxQBQI8ePUynTp2Sffnll8qaLgibO3du0Ysvvmgtaebk5MBRkleNiwIaFwXyigxIz9FBU82OPnpD6X+nUsbElaihaFwUmBgThgm9QxF36QZWxyXhj/h0HEzIxMGETPh7uOCx6FA8Gh0MPw+OviFq7FKzC/HriVQAwLR+rSSOpmmzKRsqe+p93rx5Rc8995x6/fr1iuTkZCE5OVlYv369YubMmer58+fbdg68REBAgLlDhw7lkt2IiAhTSkqKDAACAwNNAJCWllauBp+RkSEEBARUmCSr1Wp4enpaL1L3td7K30PsU023YSSWpeKqlAtcJEIkAUEQ0Kd1cyya0B17Xx6IZwa1QXONCuk5Rfho2zn0mb8Dz3xzFIcSM2E227uHHhE5ixWxiTCYzOgV7o1OLSvvv6T6V6MNCMxmMx599FFXy3WWJ+yRI0e6Go1GmxvBYmJijOfOnSuXPJ8/f14WEhJiAoBWrVqZ/f39zdu2bVN07969GBCT6EOHDsmnT59ebOvjOJIATzUuXsu3aSSW3iD+v3IUFpH0Aj1d8cI97fGvQW2w5WQaVsUl4a+km/jl+FX8cvwqIgK0mNwnDA92DYKbipsSEjUWeUUGfH1A3GToyTtZbZWaTc+u27Ztq3DVf23NnDmz6M4773R/6623VI888oj+wIED8qVLl6oWLVpUCAAymQzPPPNM8fz5813atWtnsozDCgwMNI8aNar6JlEH5F+yo4YtiWux0QgAULK/lchhuCjkeLBrCzzYtQVOXsnG6rgk/HzsCs6k5eLVH//GvE2nMbZHMCb2DkVYc3epwyWiWvr2UApydQa0au6OQRF+UofT5NmUuA4aNMhYHw/eu3dv0/fff184Z84cl3nz5rmEhoaaFixYoJs0aZI1KX311VeL8/PzhenTp6uzs7OFmJgY4+bNmwscbYarrfxLFmjZ0ipQxM0HiBxaVAtPvP9wZ7x6bwS+O3wZq/cnITmzAEv3JmDp3gQMaOeLSTGhuKu9H1cdEzkho8mMZfsSAABT+4Wzbc8B1Ph8Vn5+PpKSkmTFxeXP2Hft2tWuBVoPPvig4cEHH6y0eiqTyTBv3ryiefPm2dU/66gsmxDY1CpgFFsFlExciRyal5sKT/ZvhSf6hWPXuWtYGZeIXeeuWS/B3q6Y0CsUY3sEo5m7SupwichGv59Kw+WbhWjmpsTobi2lDodQg8Q1PT1dmDJlivqPP/6o8Lb29Lg2Rf7WxLX6PNyya5YLWwWInIJMJmBghB8GRvgh6UY+1uxPwvpDKUjJLMR7m8/gw63nMKJLECbFhHGBB5ETsIzAmtA71LoLJknL7ozoueeeU2dnZwuxsbH5rq6u+O233wqWLVuma926temnn34qrI8gGxPLLNcMmyqu3O6VyFmF+rjjtfsicWDOYLw/uhMiAz1QZDDhu78u44H/24uHPt+Hn45eRpGhXjqxiKiW/kq6iSPJWVDJZZgYEyp1OFTC7orrn3/+Kf/pp58KevXqZZLJZAgLCzMPGzZM7+HhYZ4/f75qxIgRTrloqqFYWgUycotgNJmr7HuzVFzZKkDkvFxVcozrGYKxPYJxJPkmVsYmYfPJVBxNzsLR5Cy88+tpPBIdjPG9QjnUnMiBWKqtD3YNgp+W85odhd2Ja0FBgeDv728GAC8vL3NGRoYQERGBzp07G48dO8Y6ejWaa1SQCWLD9428oiqHl1sXZ7HiSuT0BEFA91BvdA/1RkZuB6w/mIK1B5KRlqPDZzsvYtGfFzEk0h+TYsLQp7UPyo4gJKKGlXyjAL+fSgMATOMILIdid0bUtm1b05kzZ2QA0KlTJ+OXX36pTElJET7//HPVrVuz0u0Uchmaa8RNCKpboGVtFWDFlahR8dOq8czdbbHn5YFYNL4berfyhskM/H4qHeOXHMCQj3ZjZWwicnU12kCQiGpp2b4EmMxA/3a+aB/gEJtvUgm7K67PPvts8dWrV2UAjG+88UbRvffe67Zu3TqlSqXC0qVL2eNqgwBPNTJyi5CWrUPnKhYpWlsFWHElapSUchmGdwrE8E6BOJeei1VxifjxyBVcyMjDGxtP4YMtZzCqW0tMiglFW3++eBI1hOwCPb49nAIAmNYvXOJo6FZ2J66TJ0+2lgCio6NNSUlJefHx8bKwsDCzr68vK642ECcLZCOdFVciKtHOX4t3RnbCy8Mi8OORK1gZl4hL1/Kxen8SVu9PQkwrH0yKCcWQSH8o+JxAVG++OZSMgmIjIgK0uLNtc6nDoVvUel9Cd3d39OzZ067ZrU2drbNci61TBdjrRtRUaNVKTO4ThkkxoYi9eAMrYxOx7XQ64i7dQNylGwj0VOOx6BA8Eh0CX62L1OESNSrFBhNW7EsEADzRL5y95g7IpsT1ueeec3n33XeLNBoNnnvuuSqfKRcuXNgoNgqoT5aRWGnZVf9XFXPnLKImSxAE9G3THH3bNMeVrEKs3Z+EdYdSkJqtw/+2nsMnO87j3k6BmBQThm4hXnyBJaoDm/5ORVqODr5aF4zoGiR1OFQBmxLXY8eOyfV6sUPg6NGj8sqeIPnEaRt/60gsWyuuTFyJmrIWXq54aVgEnr27LTb9nYpVcUk4lpKFDceuYsOxq+gY5IFJMaEY0aUFh6QT1ZDZbMbikhFYk2NC4aLg35Ijsilx3bVrV4Hl8927dxdUdSxVz9oqkF1N4so5rkRUhlopx6huLTGqW0ucuJyFVXFJ2Hj8Kk5dzcHLP/yNeZvOYGyPlpjQOxShPu5Sh0vkVOIu3cCpqzlQK2UY34sbDjgquzKi4uJiKBQK7YkTJ5hJ1UKAp23jsIo5x5WIKtG5pRcWjOmC/a/ejVeGR6BlM1dkF+qxeE8C7lrwJx5ffhA7z2bAZOKaWSJbLN2TAAB4uHtLNHNXSRwNVcauxVkqlQrBwcFmg4GbY9WGZdOBXJ0BBcUGuKkq/jFwqgARVcfbXYXpA1rjyTtbYeeZDKzan4Td565h51nxEurjhgm9QjGmR0t4ufHFmKgiFzLysP1MBgQBmNqXI7Acmd0Z0SuvvFI0Z84clxs3btRHPE2C1kUBt5I+tKraBVhxJSJbyWUCBkf6Y9XUaOx4YQCm9g2HVq1A0o0CvLvpNHq/tx0vf38CJ69kSx0qkcNZulestt4d4Y9WvhqJo6Gq2D0O6/PPP1ddunRJ1qJFC21ISIjJzc2t3PePHTuWX2fRNVKCICDAQ41L1/ORlqOr9I+k2Cie4mPFlYjs0cpXg38/EInZQ9vh56NXsSouEWfScrH+cArWH05B99BmmBQTiuFRgXxjTE3ejbwi/HjkMgDgyTtZbXV0dieuI0aM4B6EdcC/JHGtahMC7pxFRLXhplLgsV4heDQ6GIcSb2JVXCK2nEzDX0k38VfSTbytOY3HooPxWK9Q65g+oqZmzf5kFBlM6NzSE9Hh3lKHQ9WwO3F9++23i+sjkKbGllmuxexxJaI6IAgCosO9ER3ujYwcHb4+mIyvDyQjI7cIn+y4gM/+vIh7Iv0xKSYMvVt5c7QhNRk6vRGr9ycC4IYDzqLWO2dRzVhmuVZVcdWzx5WI6pifhxrPD26Hfw5sg99PpWFVXBIOJmRi88k0bD6Zhnb+GkyMCcNDd7SAxoUvEdS4bTh2BdfzihHkqca9nQKlDodsYPezksFgwIIFC1Tff/+9MiUlRdDr9eXenmRmZubWXXiNV4CHOBKrylYBVlyJqJ4o5TLc3zkI93cOwpm0HKyKS8JPR67gXHoeXv/5JN7ffAYPdxdnwrbx42IVanzMZjOWlIzAmtI3jDPTnYTdP6V///vfLgsXLlSNGTNGn5OTIzz77LNFDz74oF4QBPPcuXO53auNrK0CVVVcuXMWETWAiAAPzHuoE/bPuRv/vj8SrZq7I6/IgBWxiRj84S6MX7Ifv59Kg6HkOYmoMdh17hrOZ+RB46LAI9EhUodDNrK74rpu3Trll19+qRsxYoThnXfecRk/fry+bdu25g8//FB14MAB7o9mI8ss1/QqxmEVcecsImpAnq5KTO0Xjil9wrDv4nWsjE3CjjPp2HfhBvZduIEgTzXG9w7FIz2D4aNxkTpcolqxVFvH9QyGh1opcTRkK7szovT0dKFz585GAHB3dzdnZWUJgDhtYMuWLWyIspFl29eM3KJKd7bhHFcikoJMJuDOtr5YMrkHdr04ENMHtEYzNyWuZuvw39/PIua9HZi1/hiOJt+E2cyducj5xF/Nwd4L1yETgCl9wqQOh+xgd0bUokUL09WrV2UA0KpVK9Pvv/+uAICDBw/KVSoVn8Fs5Kt1gSAABpMZ1/Mr7rBgqwARSS3Y2w2vDI9A3Kt3Y8GYLujS0hPFRhN+PHoFD30eiwc/24fvDqdApzdKHSqRzSwbDgzvFIhgb7dqjiZHYndGNGLECMO2bdvkAPCvf/2r+M0333Rp06aN++OPP+46efJkzni1kVIuQ/OSU23plYzEss5xlXM8BxFJS62U4+HuLbHhX/3w8z/7YlS3FlApZDhxORsvfn8Cvd/bjvc2n0ZKZoHUoRJVKT1Hh43HrwAAnryzlcTRkL1sPrX/8ccfKydPnqxfsGCBNct67LHHDCEhIQWxsbHydu3amUaOHGmonzAbpwAPNa7lFiEtR4dO8Lzt+5apAi6suBKRA+ka7IWuwV3x2r0dsP5wCtbuT8aVrEJ8uesSvtp9CXdH+GFiTBjubNMcMhnfeJNjWRWXCL3RjB6hzdA12EvqcMhONmdEb7zxhrpFixbaRx55xHXr1q3WRVj9+vUzvvTSS8VMWu1X3SxXPRdnEZED89G44B93tcHulwbiq4ndcWfb5jCbgW2nMzB52UHc/eEuLN2bgOxCnowjx1BQbMCa/ckAgGmstjolmzOi1NTU3M8++0yXmpoqDBs2zC0sLEzzxhtvqJKSkvh2uoYCPKue5VrMHlcicgJymYB7OgZg9RO9sP2FAZjSJwxaFwUSrufj7V/j0Xvedrz64984nZojdajUxH3/12VkF+oR6uOGIZH+UodDNWBzRuTm5obHH39cv2vXroKzZ8/mPfbYY/oVK1aoWrdurRkyZIjbunXrFMXF3A3WHv5ay7avlSSuBm5AQETOpbWvBv8Z0RH759yNd0ZGob2/FoV6I745mIzhC/dg7Bdx+OX4VevzG1FDMZrMWFayKGtq33DI2cbilGqUEbVp08Y8b968ooSEhLxff/21wMfHx/zEE0+4tmjRgtur2MG/mk0ILBVXtgoQkbNxd1FgQu9QbHn+Tqx7qjfu6xQIuUzAwcRMPPPNUfR9fwc+2nquyt0DierSttPpSLxRAE9XJcb0aCl1OFRDtZq7KpPJoFAoIAiC2Ww2w2Aw8O2LHQKq6XG1VCS4OIuInJUgCOjdyge9W/kgLVuHrw8m45uDybiWW4SF28/js50XMDQqAJN6hyI63BuCwJcRqh9L9lwCADzWKwRuKo6dd1Y1yoiSkpKEf//736rw8HDNsGHD3FJTU2VffPFF4dWrV3PrOsDGzLrtawWtAkaTGZZ9CdjjSkSNQYCnGrOGtMO+lwfhk0fvQM+wZjCYzPjtRCrGfbUfwxfuwdoDScgv4lpfqlvHUrJwKPEmlHKBGw44OZvfchQVFeH7779XLFu2TLVr1y55QECAecKECfpp06YVt2nThhsP1IBlqkCOzoDCYiNcVaU75pbt/2KrABE1JiqFDCO6BGFElyDEX83B6v2J+OnoFZxJy8VrP53E/E1n8HCPlpjYOxStfNmBRrVnqbY+0CXI+tpLzsnmxDUwMFBbUFCA4cOHG37++eeC4cOHG+VyefU3pEp5qBVwVcpRqDciLUeH8Obu1u9Z+lsBVlyJqPGKDPLAe6M645VhHfDdXylYvT8JSTcKsHxfIpbvS8SdbZtjUkwYBkX4cTEN1cjlmwXYfDINADCtH0dgOTubE9dXXnmlaPLkyXp/f39WV+uIIAgI8FQj4Xo+0m9NXMtUXBV8siaiRs7TTYlpd7bC1L7h2H3+GlbHJWHH2QzsOX8de85fRwsvV0zoHYpxPYPh7a6SOlxyIiv2JcJoMqNvGx9EBnlIHQ7Vks2J60svvcRZV/XA38PFmriWVXaGKxcrEFFTIZMJuKu9H+5q74fkGwVYeyAJ6w+n4EpWId7fcgYfbTuHBzoHYVJMKLpw1yOqRo5Oj3WHUgBww4HGgsvqJGbptbl1gZZl1ywX9rcSURMV4uOGV+/tgJlD2mHj8atYFZeIk1dy8MORy/jhyGV0CfbCpN6huK9zINRKtq7R7b49lIK8IgPa+GkwoK2v1OFQHWDiKjHLSKxbZ7laZ7iyv5WImji1Uo6xPYIxpntLHE3Jwuq4JPx2IhXHU7LwQkoW3t10GuN6BmN8rxC0bOYmdbjkIAxGE5bvSwQATOsXDhnb7hoFSbOiuXPnugiCoC17adeunbXRs7CwENOnT1d7e3trNBqNduTIka6pqamN6jfPv5JZrtw1i4ioPEEQ0C2kGT4a1xWxrw7Ci0PbI9BTjcz8Yiz68yL6f7ATT646jD3nr8Fs5nKMpm7TyTRcySqEj7sKI+9oIXU4VEdqnBUVFRXh9OnTMr1eX6sAOnToYLpy5Uqe5bJv374Cy/eee+459W+//aZYv3594Y4dO/JTU1OFUaNGudbqAR1MZbNcSyuujSpPJyKqE801LvjnwDbY89JAfDGhO/q09oHJDGyNT8fEpQdx94e7sHxfAnJ0tXuNIudkNputI7AmxoSylaQRsbtVID8/H//85z/Va9asUQLAmTNn8tq0aWP+xz/+oQ4KCjLNnTvXrkVcCoUCQUFBt701zsrKwooVK5SrV68uHDJkiBEAli9fruvYsaP7vn375H379jVWdH86nQ5FRUXWr3Nycuz69zW00oprUbnrWXElIqqeQi7DsKgADIsKwPn0XKzen4Qf/rqMS9fy8eYv8fjv72fx0B0tMCkmDO0DtFKHSw3kUOJNnLicDZVChom9Q6UOh+qQ3VnRyy+/7HLixAn59u3bC9Tq0iG+gwcPNnz33XdKe+/v4sWLssDAQE14eLjmkUcecU1MTBQA4NChQ3K9Xo977rnHuoVKZGSkKTg42BwbG1vpW6d33nnHxcvLS2u5hISEOPQzlaXimp6jg8lUmr/rrVMF+C6RiMgWbf21eOvBKBx4bTDefrAj2vhpUFBsxNoDyRj68W6M+zIOv51ItT6/UuNlqbaO7tYCPhoXiaOhumR3xXXjxo3Kb775prBv377GsmOaoqKijAkJCXYlwr179zYsXbrUGBERYbp69arw1ltvufTv39/95MmTeWlpaYJKpUKzZs3K3cbPz8+clpZW6fnzuXPnFr344ovW8mVOTg4cOXn107pAEACDyYzMgmI0L/kDK624slWAiMgeGhcFJsaEYULvUMRduoFVsUnYejodBxIycSAhE/4eLngsOhSP9gqGn5a7KDU2CdfzsfV0OgDgCW440OjYnbhev35d8Pf3v+3tal5enmDvvNH777/ferq/a9euiImJKQgLC9N+8803Sjc3txp11qvVapStBDs6pVwGH3cXXM8rQlq27vbElVMFiIhqRBAE9GndHH1aN8fVrEJ8fSAZ6w4lIz2nCB9tO4f/23kew6ICMSkmFD1Cm3FmdiOxbG8CzGZgUIQf2vhxy+DGxu6sqFu3bsZffvnFmvBa/tCXLFmiio6OrrDv1FbNmjVDmzZtTBcuXJAFBASYi4uLcfPmzXLHZGRkCAEBAY1quai/h5islp0sUHYDAiIiqp0gL1fMHtoe+14ZhIWPdEW3EC/ojWb8cvwqxnwRh3s/2YtvDiajoNhQ/Z2Rw8oqKMZ3f5VsONAvXOJoqD7YXXF99913i+6//36306dPyw0GAz7++GNVfHy8/MCBA/IdO3bk1yaY3NxcJCQkyAIDA809e/Y0KpVKbN26VTF27FgDAJw+fVqWkpIi9OnTp1YJsqMJ8FDj1NWccrNcLRVXJRdnERHVGReFHA92bYEHu7bAySvZWBWXiA3HruJ0ag5e/fFvvLfpNMb0CMbE3qEIK7MNNzmHtQeSodObEBnogZjWPlKHQ/XA7qxowIABxiNHjuQZDAZ07NjRtHXrVoWfn59p3759+dHR0XZ1vM+cOdNlx44d8kuXLgl79uyRP/jgg24ymcw8fvx4vZeXF6ZMmaKfPXu2etu2bfKDBw/KHn/8cXWvXr2MlU0UcFb+lgVaZUZi6Y1iUZlTBYiI6kdUC0988HAXHJhzN+bcG4EQbzfk6AxYujcBdy34E5OXHcSOM+kwmhrVSb5Gq8hgxIrYRADAtDvD2frRSNVo56y2bdualy1bpqv+yKpdvnxZNn78eNfMzEyhefPm5j59+hjj4uLy/f39zQCwcOFC3cyZM9Vjx451KyoqwuDBgw2LFi2q9eM6mop2zyo2iLk5d84iIqpfXm4qPNW/Nab1a4Vd565hZVwi/jx7DbvOiZdgb1dM6BWKsT2C0cxdJXW4VIlfjqfiWm4R/D1ccH/nIKnDoXpid+Iql8u1V65cybu1z7Rk0ZbGaDTm2npf3333XWFV33d1dcUXX3yh++KLLxpdslpWaeJaOsvV0uPqwoorEVGDkMkEDIzww8AIPyRez8ea/Un49nAKUjIL8d7mM/hw6zmM6BKEyX3CENXCU+pwqYyyGw5M6RPO9SGNmN2Ja2Xb6Ol0OqhUfCdaE1W2CvCPj4iowYU1d8fc+yPxwj3tsfH4FayMTUJ8ag6+++syvvvrMu4I8cLkmDAM7xQAF87blty+CzdwJi0Xbio5HosOkTocqkc2J64ffvihChCnCHz11VdKjaZ0xITRaMSePXvk7dq141TnGrBUXNNzSxPXIi7OIiKSnKtKjnE9QzC2RzCOJN/EytgkbD6ZiqPJWTiafAxv/6rCI9HBGN8rFEFejWpHcqeyuKTaOrZHMDzd7N4LiZyIzYnrJ598ogLEiuvixYtVcnnpO0yVSmUOCQkxN8b+04ZgGYeVVaCHTm+EWikvs3MWE1ciIqkJgoDuod7oHuqNjNwOWH8wBWsPJCMtR4fPdl7Eoj8vYkikPybHhCGmtQ8XBjWgc+m52HXuGgQBmNqXI7AaO5sT18TExDwAGDBggNtPP/1U4O3tXX9RNTGerkq4KGQoMpiQnqNDqI87x2ERETkoP60az9zdFtPvao1t8elYGZeI/Zcy8fupdPx+Kh1t/DSYFBOKh+5oAa2a1b/6tnRPAgBgaGQAQnzcJI6G6pvdWdGuXbuYtNYxQRAQUNLnmlbS58qds4iIHJtSLsPwToFY91QM/pjZHxN6h8BNJceFjDz8e8Mp9J63Ha//fBLn021es0x2upZbhJ+OXgEAPNmf1damoEbjsJKTk4Wff/5ZkZycLNPr9eW+t3DhwqJKbkZV8PdQI+lGgXUklqVVwIWJKxGRw2vnr8U7Izvh5WER+PHIFayMS8Sla/lYvT8Jq/cnIaaVDyb3CcXgDv5Q8ExanVkdl4hiowl3hHiheyiLak2B3YnrH3/8IX/ooYfcwsLCTOfOnZNFRkaakpOTZWazGV27dm1UGwM0JOsCrZzyFVelnH1SRETOQqtWYnKfMEyKCUXsxRtYGZuIbafTEXfpBuIu3UCgpxrje4VgXM8Q+GpdpA7Xqen0RqzenwQAmNavlcTRUEOx+23fnDlz1M8//3zxqVOn8tVqNX744YeC5OTk3H79+hlGjx6tr/4eqCKlrQJiwdoyx5U7ZxEROR9BENC3TXN8NakH9rw8CP+4qzW83VVIzdZhwR/n0Gf+djy/7ij+SrpZ6ZhJqtoPRy7jZoEeLZu5YmhHf6nDoQZid1Z09uxZ2ZQpU4oBQKFQoLCwUNBqtXjrrbeKFixYwLePNeRfScVVxfmAREROrYWXK14aFoHYVwbhw7Fd0DXYC3qjGT8fu4rRi2Jx/6d78e2hFOj0PGlpK5PJjKV7xUVZj/cNZ/tFE2L3T9rNzc1cXFwsAIC/v7/pwoUL1vu4ceMGz2vX0G2tAka2ChARNSZqpRyjurXEz//si43/6ouHu7eESiHDqas5eOmHE+g1bzvmbTqN5BsFUofq8HaezcCla/nQqhUY1zNY6nCoAdnd4xodHW3cvXu3vGPHjqbhw4cbXnzxRZcTJ07Ifv75Z2XPnj35drGGLLNcb12cxakCRESNT+eWXlgwxgtz7u2Abw+nYM3+JFy+WYivdl/C4j2XMLC9HybGhGJAW1/IZCxg3Mqy4cBj0SHQuNRonTk5Kbt/2h999JEuLy9PAIC33367KC8vT/juu++Ubdq0MX300UfcgKCGLK0CGTlFMJvNpa0CPP1BRNRoeburMH1Aazx5ZyvsPJOBVfuTsPvcNew4k4EdZzIQ6uOGib1DMaY7d4SyOHklG/svZUIhEzC5T5jU4VADsztxbdOmjRmAGQA0Gg0WL17MZLUOWBLXYqMJmfnFnONKRNSEyGUCBkf6Y3CkPy5dy8Oa/cn47q8UJN0owDu/ncaCP85iZNcWmBgTio5BnlKHK6klJdXW+zoHcpvdJsjurCg8PFxz/fr1285b3Lx5E+Hh4Zq6CavpUSlk8HFXARDbBYqNZuv1RETUdLTy1eDfD0TiwJy7Me+hTogI0EKnN2HdoRTc98lePLwoFhuOXbEWOJqS1OxC/HoiFQBHYDVVdldck5KSBIPBcNv1Op1OuHr1KhtxasHfQ40b+cVIz9Gh2CC2C3PLVyKipslNpcBjvULwaHQwDiXexKq4RGw5mYbDSTdxOOkm3tacxmPRwXisV6h1pGJjtyI2EQaTGb3CvdGpZdOuPDdVNieuP/30k/XYLVu2KDw9Pa2D54xGI7Zv364IDQ1tem//6lCApxrxqTlIyy6CnhVXIiKCOBM2Otwb0eHeyMjR4euDyfj6QDIycovwyY4L+OzPixja0R8Te4ehdytvCELjrCHlFRnw9YFkAMCTd7La2lTZnLiOHj3aFRD/gKZOnVrurZ1SqURoaKjpv//9L7d7rQVLn2tajo6Ls4iI6DZ+Hmo8P7gd/jmwDX4/lYZVcUk4mJCJTX+nYdPfaWjnr8HEmDCMuqMF3BvZavvvDqcgV2dAq+buGBThJ3U4JBGbf6tNJlMuAISFhWkOHTqU7+vry60+6phlJFZGjq505yxWXImI6BZKuQz3dw7C/Z2DcCYtB6vikvDTkSs4l56H138+iQ82n8Ho7i0xoXco2vg5//ITo8mMZfvEDQem9gvniLAmzO63Y4mJiXn1EQiVbkKQlqODnhVXIiKyQUSAB+Y91AkvD4vAD39dxur9SUi4no8VsYlYEZuIfm2aY2JMKO6O8HPaHaZ+P5WGlMxCNHNTYnS3llKHQxKy+Td479698g0bNpRLdJcvX64MCwvT+Pr6ap544gm1TsfJWLXhX9Jcn5atQ5Fl5yxWXImIyAaerkpM7ReO7bMGYNXUaAzu4A9BAPZeuI6nV/+FAf/9E5/tvIAbec7X1WcZgTWhdyhcVdwKvSmzOSt66623XE6ePGk9/vjx47KnnnpKPWjQIMOLL75Y/Ntvvyneeecdl/oJs2kou+2rdecsJ313TERE0pDJBPRv54slk3tg94sDMX1AazRzU+JKViH++/tZxLy3A7PWH8PR5Jswmx2/6++vpJs4kpwFlVyGiTGhUodDErM5Kzpx4oRs8ODB1jlYX3/9tbJnz57GZcuW6V566aXijz/+WPf99983rk7wBmZJXG8W6GF5LmGPKxER1VSwtxteGR6BuFfvxoIxXdC5pSeKjSb8ePQKHvo8Fg9+tg/fHU6BTu+4O7Yv3StWW0feEQQ/bdMY+0WVszkrysrKEgICAqxvzfbs2SMfOnSoNZGNjo42XrlyhVlWLXi5KW9LVFlxJSKi2lIr5Xi4e0ts/Fc//PzPvhjVrQVUchlOXM7Gi9+fQO/3tuO9zaeRklkgdajlpGQWYMvJNADAE9xwgGBH4urn52e+dOmSDACKiopw7NgxeUxMjPUtWm5urqBUch/l2hAEwVp1tWDFlYiI6lLXYC98OLYr4l4dhJeGtUcLL1dkFejx5a5L6P/fnZi28hB2nbsGk0n6NoKlexNgMgP92/mifYBW6nDIAdh8an/YsGGGV1991WX+/PlFP//8s8LV1dU8YMAAa+J6/PhxWXh4ODcgqKUADzWSS97xygRx/2oiIqK65qNxwT/uaoOn+7fG9tPpWBWXhL0XrmPb6QxsO52B8ObumNA7FA93bwlP14YvTGUX6vHt4RQAwLR+4Q3++OSYbE5c33333aKHHnrIddCgQW4ajQbLli0rdHEpXYu1fPlyVdkeWKoZP4/S/1NWW4mIqL7JZQLu6RiAezoG4OK1PKyOS8IPf11GwvV8vP1rPBb8fhYj72iBSTGh6BDo0WBxfXMwGQXFRkQEaHFn2+YN9rjk2GxOXH19fc179+4tyMrKgkajgUJR/qbff/99gUbj/EOOpVa2VYD9rURE1JBa+2rwnxEd8eLQ9vjp6BWsikvEufQ8fHMwGd8cTEZ0mDcmxoRiWFQAlPX4GlVsMGHFvkQAwBP9whvtNrZkP7unAHh5eVV4vY+PT21jIQABnmUSV1ZciYhIAu4uCkzoHYrxvUJwICETq+OSsOVUGg4mZuJgYib8tC54NDoEj/UKsW5XXpc2/Z2KtBwdfLUuGNE1qM7vn5wXx1c5GH9WXImIyEEIgoDerXzQu5UP0rJ1+Lqk8pqRW4SF28/js50XMDQqAJN6hyI63LtOKqNmsxmLSzYcmBwTChcFNxygUkxcHUzZiit3zSIiIkcR4KnGrCHt8K+BbbDlVBpWxyXiUOJN/HYiFb+dSEVEgBYTY0IxsmsLuLvUPL3YfykTp67mQK2UYXwvbjhA5TFxdTDscSUiIkemUsgwoksQRnQJQvzVHKzen4ifjl7BmbRcvPbTSczffAYPd2+Jib1D0crX/rUvlu1dH+7eEs3cVXUdPjk5ZkYOhlMFiIjIWUQGeeC9UZ1x4NXBmHtfB4T6uCFXZ8DyfYkY9L9dmLj0ALbGp8No40zYCxl52H4mA4LADQeoYqy4OhgXhRzN3JS4WaBHns6AuIs3EB3uDTlMQFIskJcOaPyB0D6AjH0/REQkPU83Jabd2QpT+4Zj9/lrWB2XhB1nM7Dn/HXsOX8dLbxcMaF3KMb1DIZ3BVVUo8mMgwmZ+HTHeQDA3RF+CG/u3tD/DHICgtlszpE6iPqUnZ0NLy8vbXZ2Njw8Gm7+XE1tOZmKf319FIYy704f0RzDG8pVcC1MKz3QIwgY9j4QOUKCKImIiKqWfKMAaw8kYf3hFGQV6AGIZxIf6ByEyX1C0bmlFwDxde/NX+KRmq2z3tbbXYV5D0VhWFSgFKGTBHJycuDp6YmsrKxcT0/PSo9j4upAtpxMxYw1R1D2hMpQ2UEsUn4MQNxJq1TJF2NXMXklIiKHpdMbsfH4VayKS8TJK6UpR5dgL3Rt6YlVcUm4tZHA8nK3aEI3Jq9NBBPXEs6SuBpNZvR7f0e5d5wymLDX5VkEIBMV7/wqiJXX5/9m2wARETk0s9mMoylZWB2XhN9OpKLYWPUu8QLESQZ7Xx7E7c+bAFsTV4dZ/fPOO++oBEHQPvPMM9bVSYWFhZg+fbra29tbo9FotCNHjnRNTU1tlL+9BxMyyyWtABAtO4MgobKkFQDMQM4VsfeViIjIgQmCgG4hzfDRuK6IfXUQxvUMrvJ4M4DUbB0OJmQ2TIDkFBwicd2/f79syZIlqqioqHJvv5577jn1b7/9pli/fn3hjh078lNTU4VRo0a5ShVnfcrI1d12nR+ybLtxXnrdBkNERFSPmmtc0Ke1bTtuVvT6SE2X5Ilrbm4uJk6c6Prll18WNmvWzNrmkpWVhRUrVigXLFigGzJkiDE6Otq0fPly3f79++X79u1rdOfF/bS3b5mXAS/bbqzxr9tgiIiI6llFr3u1OY6aBskT1xkzZqiHDx9uGDp0qLHs9YcOHZLr9Xrcc889Bst1kZGRpuDgYHNsbGyliatOp0N2drb1kpPjHC280eHeCPRUo2xXwEFTBK6avVHl+Du1lzgai4iIyIlU9LpXlgAg0FON6HDvhgyLHJykievatWsVR48elb///vtFt34vLS1NUKlUaNasWbnr/fz8zGlpaZV2fb7zzjsuXl5eWsslJCREWw+h1zm5TMAbD0QCKF1NaYIMb+oniZ9XlrzqsoDNLwNGQyUHEBEROZ6KXvcsLF+/8UAkF2ZROZIlrklJScLMmTPVa9euLXR1rbu21blz5xZlZWXlWi7Jycm5dXbn9WxYVCAWTeiGAM/S0yK/m6IxR/kSil1vaQfwaAF0GgtAAA4tBtY+DBTebNiAiYiIaqGi1z1AnCbAUVhUEcnGYf3www+Khx9+2FUuLz3rbzQaIQgCZDIZNm3aVDB06FC3zMzM3LJV15CQEM0zzzxT/OKLLxbb8jjOMg6rLMsOIhm5OvhpxdMk8rw04MMI8YBJG4GwfuIIrNO/Aj8+BejzAZ82wGPfAj6tpf0HEBER2aHC1z1WWpsUW8dhSbbl65AhQwzHjx/PL3vd448/7tq+fXvjK6+8UhwSEmJSKpXYunWrYuzYsQYAOH36tCwlJUXo06ePseJ7bRzkMgExt662LC75r1JpgVYDSq/vcD/wxO/A148ANy4AiweJmxKUPYaIiMiBVfi6R1QByRJXDw8PdO7cudz4K3d3d7OPj4/Zcv2UKVP0s2fPVnt7exd6eHiYn332WXWvXr2Mffv2bdSJa4WKssWP6gqqxgGdgCd3AOvHA5cPAasfAu79L9DziYaNkYiIiKgeST5VoCoLFy7U3XvvvYaxY8e6DRw40N3f39/8448/FkodlyR0JR0dLpW0O2j9gcm/Ap3HAWYj8NssYNNLXLRFREREjQa3fHUW8RuAbycBwb2AJ/6o/DizGdj7IbD9LfHr1oOAh5cDrl4NEiYRERGRvZxuy1eqRnUVVwtBAO58ARi3BlC6ARd3AEsGAzcu1n+MRERERPWIiauzKCpJXCvqca1IhweAqVvEsVk3zouLthJ21198RERERPWMiauzsLXiWlZgF+DJnUCLHuJGBasfAg4vq5fwiIiIiOobE9f6ZjICCXuAv78XP5pqOBDB3oqrhdYfmPIb0GkMYDIAv87kTltERETklCQbh9UkxG8EtrwM5Fwtvc4jCBj2PhA5wr77KqpBxdVCqQZGLQZ8I4AdbwMHvgCunwceXsZFW0REROQ0WHGtL/EbxSkAZZNWAMhJFa+P32jf/VlaBdSVr7SrkiAA/WcDY1eXLNraDiwdwkVbRERE5DSYuNYHk1GstMJcwTdLrtvyin1tA7WpuJYVOQJ4fDOgDQKunwOW3C22MFDjUlctKkRERA6EiWt9SIq9vdJajhnIuSIeZytdDXtcKxLUFXhqJ9CiO1B4E1g9EvhrRe3vlxxD/Ebg4yhg5f3AD0+IHz+Osr/KT0RE5GCYuNaHvPS6PQ6ou4qrhTZAXLQVNVpctPXLc8CWV7loy9nVdYsKERGRA2HiWh80/nV7HFC3FVcLpSsweikwcK749f7PgW/GAbpsnmp2RvXRokJERORAOFWgPoT2EacH5KSi4iRCEL8f2sf2+6zriqs1FAEY8CLg2w748Wngwjbgs96A2Vi+IlzTaQhUt8xmoChXfHOhywIKs0o/Xj1ie4tK+J0NEi4REVFdYuJaH2RyMcn7dlIlB5iBYfPF42xhKAYMOvHzuqy4lhX5IOAVCqwaCeRWkPxYTjWPXcXktbZMJqAoW0w+yyaeZT9W+r1s8U1FbdjTokJERORAmLjWl8gRYpL345OlSaeFq7d992WptgKASlv72CoT0AlQuFTyTTMAQTzVHHGf7Ul3Y2U0lFY9K0o8LUlmhUlpDiquxNtBphRn8Kq9Sj8ai4GEXdXfVuNXu8cmIiKSCBPX+nZr0goAhZn2VS912eJHpTsgr8cfWVIskJdWxQFVnGo2GUtuny727ob2cfzk1lBcTXUzq3wSWvZ7xbm1f3yFqziX99YE1PKxqu8pXcU2j7JMRnF6QKUtKiW2vQkMfRcI6V37fwMREVEDYuJaX6wLZSpjtr16WdPtXu1V02kIdblDmL30hVVXN6v6nr6g9o+v0lSfZFb0PbWnuKNZXSrXoiKgfPJa8rVcBVw5DCwbCnQYAQz+D+DTum7jICIiqidMXOtLtbNcYftCGV09Lcy6VU2mIVjGL91a4bO1J9ZsBorzq698VpaAGotsi7kqLp6Aq6eYTFaUeFqTzVuv8wTkyto/fl2ytKhU+EZiPhAcDex8Fzi6Bji9ETi7GYh+Euj/IuBmZwsLERFRA2PiWl9yU+vuuIaquNo7DcGW8Usb/iWudtflVJKcZgMmfe3iFmS3JJ1VJaCet1c+Hb2lwV6RI8RKfmWtGyM+BXpNB7b+W5wisf9z4NhaMXmNfqqKPmciIiJpMXGtL/nX6u64hqq4VnmquUTZaQi2VJWLsoG9H9nw2Irqk8zKvqfSAjKOJC5HJq+6ku/fEZjwA3BhO/DH60DGKeCPucDBxcDgN4COo27voSUiIpIYE9f64u5bd8c1VMUVqPxUs9pLrNSVPe1va09s60FAix5VJ6AqdyZKUmhzN9DqLuDY18COd4CsJOD7qUDc51zARUREDoeJa33RBtbdcQ1VcbUoe6r5wBfAmV+B9sNv71W1tSe23ywOvHdkMjnQbSIQNQqI/T9g30Iu4CIiIofE86v1JbSPePq7Kh4tbNs9qyErrhaWU81dx4tfpx6//RhLT2ylBNv/jSQ9lTtw18vAs0eAbpPF3uHTG4HPegGbXwEKMqWOkIiImjgmrvXBZAQu7QZMhpIrbj0FLogXW3fPsm736lmHQdooqKv48doZoPiW8VGWntgKlfyb7dkhjByDNgAY8QkwfR/QZoi4eO7AImBhV2DfJ4ChDiY5EBER1QAT17oWv1EcAr9mZOl1t/Zuavzs2zpVJ0HF1UIbKLYEmE1A2t+3fz9yBOAbcfv1HkHcHtbZ+UcCE74HJv4E+EeJC+22vg78Xw/g5A/iKDMiIqIGxB7XulTZTFOzSfzo1hwouA4MX2BfQlfUwD2uZQkCENgVOP87kHoMCOlV/vuFN4Hr58XPRy8VPzrLzllkm9aDgKcHAMe/KVnAlVy6gOued4DQGKkjJCKiJoIV17pS5UxTABAAfb746Y1z9t23lBVXoLRd4OrR2793YTtgNgJ+kUCnh8VL+J1MWhsbmRy4YwLwzF/AwNfE7YevHAaWDwPWTwBuXJQ6QiIiagKYuNaVameamsXtSQHg2ln77lvKiisABN0hfrx67Pbvnd0sfmw3tMHCIQmp3IEBLwHPHgW6TylZwPUL8Fk0F3AREVG9Y+JaV2ydaQqIC53sIXXFNbCr+PH6WXF7VgujAbiwVfy83fAGD4skpPUHHlhYZgGXofwCLr1O6giJiKgRYuJaV2ydaQqIPaEmk+3HS11x9QgENAG3L9BK2S9u2ermA7TsIU1sJK3KFnB91hP4+3su4CIiojrFxLWuVDvTFOJMU5kKMBQC2cm23a9RD+hLxlCpJRiHZWHtcz1Wet25LeLHtvewp7Wpaz0IeHo38ODn4iSKrGTghyeAJXcDSXFSR0dERI0EE9e6IpMDUQ9XfUzUaKB5W/FzW/tci3JLP3fR1iy2umDtcy2zQOtsSeLK/lYCShZwjb9lAddfXMBFRER1holrXTEZgZPfV33MyR+A5u3Ez23tc9Vlix8VroBcWfP4asvS55p6TPx44yJw47y4O1jru6WKihxRlQu4Xgbyb0gdIREROSkmrnWl2qkCAHKuAEo38fNrNo7EkmK714pYd9A6CxTllbYJhPaVPjZyTJYFXDNixXYSkwE48AXwyR3AvoVcwEVERHZj4lpXbJ0qoC453W9zxVXihVkW2gCxdxFmcYGWJXFtz2kCVA2/DsD474CJPwP+nUoWcP2bC7iIiMhuTFzriq1TBQI6ix+vnbXtBdtRKq5AabtAwm6xwgywv5Vs13og8PSuShZwxUodHRGR7UxGIGGP+OY7YY/4NTUIJq51JbQP4OpTxQGCOFWg4yhAkAPFudW3FgCOU3EFgMAu4sd9C8XTvj7tAO9W0sZEzsW6gOsIMHBumQVcw4F144HrF8ofzxcHInI08RuBj6OAlfeLb75X3i9+Hb9R6sjqjgM/9yqkDqDReMu7+mOGzQdUroBPa+D6ObFdwLNF1bdxlIpr/Ebg0GLxc8vWtblXxesjR0gXFzknlRsw4EWg2yTgz/eAIyuBM7+KLSg9pwH9XwKS9onbKJd9g+cRBAx7n79zRCSN+I3At5Nw2/buOani9WNXOf/zU/xGh37ulbTi+umnnyqjoqLcPTw8tB4eHtpevXq5/frrr9aBoIWFhZg+fbra29tbo9FotCNHjnRNTU0VpIy5Qv+xYb5q0B2lP3Df9uLH6zYs0JJ68wGg9A+14JbV4MV54vWN6V0mNSytP/DAx7cv4PooCvh24u1nJSwvDvydI6KGZjKKCd2tSStQet2WVxyqOmk3y+u9Az/3SlpxDQ4ONr/33ntF7dq1M5nNZixfvlw5atQot7/++iu/U6dOpueee069efNmxfr16ws9PT3NzzzzjHrUqFGucXFxBVLGXc7fW2077uoRoLhQrLj6RojjgWxZoGXd7lWizQeq/EMtseUVIOI+bkJANWdZwHVxJ/D7XCDjZCUHmgEI/J0jooZX7fQgszg96McnAa8QcRQgBPFjuQvKf13hMULJpbpjZNUcZ8cxZjPw2yxUnpg7xnOvpInryJEjDWW/fv/994sWL16sio2NlQcHB5tWrFihXL16deGQIUOMALB8+XJdx44d3fft2yfv27evY7yl+aGaTQfK2joXuO9/QPOSiqstmxBIXXG19Q81KRYIv7PBwqJGqvVAYNg8YFVVp6Msv3P7gPD+DRYaETVxtk4POvlD/cYhGcd4vXeYHleDwYD169cr8vPz0bdvX+OhQ4fker0e99xzjzW5jYyMNAUHB5tjY2MrTVx1Oh2KioqsX+fk5DRA9DbKvCR+tLQKZJwW3+EIVXQ/WCuuEiWutv6h2nocUXXyr9l23JoxQFAXwL8j4BcJ+EcB/pHSbo1MRI2XrdODIkeKi7HNptILzOW/NpvEIma1x5hLLvV9jFncqbPgevX/Polf7yVPXI8fPy7r27evu06ng0ajwffff18YFRVlOnr0qEKlUqFZs2bljvfz8zOnpaVVmum98847Lu+++66q3gOvCcsK/OZtAQiALkt8kdb4VX4bqSuutv6h2nocUXVs/V0y6oCUA+KlLM9gMZm1XPw6Aj5tALnkT3dE5MxC+4hvjC07Wt5GEBcxPbzMOduYEvaIExKqI/HrveTP5BEREaYjR47kZWVlCd99953y8ccfV//555817mGdO3du0Ysvvmgtuebk5CAkJERbN9FWYPT3trcLDHlH/Kh0BZqFATcTxHaBqhJXqSuuoX3EP8ScVFTc91Lyhxrap6Ejo8bK1t+5x74V+8TTT5Veci4D2SnixbJJBgDIXcQzHWUTWv+oqv/2iIjKOrqm6qQVEKcHOWPSCjjN673kiauLiwvatWtnBmCOjo4uOnz4sPzjjz9WjRs3Tl9cXIybN2+Wq7pmZGQIAQEBla4UUqvVUKvVDRG6qNMQwJZ2lvb3iguzLHwjShLXM1X3ikhdcZXJxREY306C+IdZ9r++EfyhkuOx9XcuIEq8dCrzxrHwptiCk34KSD8JpMcDGfHiBIy0E+KlLLfmpUmsf0ex1cA3QnxzSURkcexr4JfnxM/bDgXS/65gXNR8hxgXVWNO8noveeJ6K7PZjKKiIvTs2dOoVCqxdetWxdixYw0AcPr0aVlKSorQp08fx1iYZfGf7KpHYrW/F3j0m/LX+bYDzm2ufoGW1BVXQPxDHLuqkrluTv6HSo6ppr9zrs3EakDZioDJBGQliclsRnxJQnsKuHFR7OdK2CVeLASZ2Fpg7ZstqdB6hVTdj05EjdOJb4Gf/wHADPR8Erj3v2JfaFKs2O+p8RefcxpDAccJXu8lTVxffPFFl3vvvdcQGhpqys3NFdasWaPcvXu3fNOmTUVeXl6YMmWKfvbs2Wpvb+9CDw8P87PPPqvu1auX0WEmCpT1n2xxNFbZtoFWQ4FHVpavtFr4RogfqxuJJXXF1SJyhDgCozH+oZJjqqvfOZkM8A4XLx3K9G8VF5RvNcg4BaSdBAozxRnL188B8T+XHq/SihXZsr2zXAxG1Lid/AH46WkAZqD742LSKgjiDpiNdZKOg7/eS5q4Xrt2TZgyZYprWlqa4OHhYY6KijJt2rSpYNiwYUYAWLhwoW7mzJnqsWPHuhUVFWHw4MGGRYsW6aSMuUqdhgCdKut/uYUtmxCYjOIpTsAxXhxljfgPlRxTff7OqdyAFt3Ei4XZLD5RW9oMLEnt9bPiNs0VLgYLKZ/Q+kcB3q25GIzI2cVvAH54Uqyu3jERuO/DpnPWxYFf7wWz2exA86LqXnZ2Nry8vLTZ2dnw8JC4allWUS7wXkvx85cSALcKtowtvAm8HyZ+PjcDULg0WHhEVIZRD9y4UL531rIYrCLWxWBRZZJaLgYjchpnfhN7PU0GoMujwIOfi2dvqN7k5OTA09MTWVlZuZ6elRfrWBKQiotWHNuTnSJWXUN6336Mpb9V7sKklUhKcqW4u5dfh2oWg50Sv65sMZi77+29s74RgLIBF5QSUdXO/Q58O1lMWjuNAR78jEmrA2HiKqXm7cTE9dqZihPXIgdYmEVElatuMZild9ayGCz/WuWLwax9s1wMRiSZC9uA9RMAkx7o+BAw8guH6e0kERNXKflGABe3Vz5ZQOcgC7OIyHa2LgazVGjLLgY79VPp8VwMRtSwLu4E1o0HjMVAhweAUYvZq+6A+BORkmWBVmWJKyuuRI2HPYvBrp2pZjFYx/K9s1wMRlQ7CXuAbx4FDDqg3XBg9DKxRYgcDp/ppGQdicWKK1GTJAiANkC8tBlcev1ti8FOiYltzmUgO1m8nNtceny5xWCWpJaLwYhskhQLfD0WMBQCbe8Bxq4EFI65czwxcZWWbzvxY85lMUm9tbLKiitR01TVYjBLZdbSO5seD+jzK18MdmvvLBeDEZVKPgCsHQPoC4DWg4Cxq7kY2sExcZWSazNxsG9eOnD9PNCye/nvWzcfYE8bEUF8zgjrK14sbl0Mln5S3CHMshjs0p/ixaLsYrCySS0Xg1FTc/kvYM1ocQpI+ADgka/5ps4JMHGVmm97MXG9dub2xNURtnslIsdW5WKw02V6Z6tZDObiUTKqq0zvrF8HLgajxunqUWD1Q2IveWg/4NF1gLKCXS7J4TBxlZpvBJCwW9yZ51aOst0rETkflRvQort4sSi3GKxMq8G1M+LzTcp+8VKWdTFYmd5ZLgYjZ5Z6Alg1EijKBkJigMfWi38v5BT4zCO1qiYLsOJKRHWpqsVg18+LLQZlk9qcKzYuBiu5cDEYObr0U8CqBwFdFtCyJzD+O8BFI3VUZAcmrlKzThY4c/v3WHElooYgV5ZUUyO5GIwar4wzwMoRYrtMUDdgwg/iLpbkVJi4Sq15ScX1ZhKgLyzfY8OKKxFJqdLFYIm3985mXrJtMZh/lNhLy8Vg1JCunQNWPgAUXAcCuwATf2T/tpNi4io19+aAq3fJgonzQGDn0u+x4kpEjkYmA7xbiZdyi8Hyy+wMFs/FYOQ4blwUk9b8DMC/EzDxZ/FNGTklJq5SEwTxVFpyrNjnWjZxZcWViJyFyr3ixWC5aWXaDLgYjBpYZoKYtOaliW+UJm0A3Lyljopqgc8EjsC3fUniekufK+e4EpEzEwTAI1C8VLQYrFzvbDWLwfwiyvfO+kcBGt+G/zeR87iZJCatOVfEtrxJGwF3H6mjolpi4uoIKlqgZTIBRbni56y4ElFjUnYxGMaUXl92MZil1SDjtLgYLPW4eCnLshjM0jfLxWBkkX1ZTFqzU8Qe68kb+UankWDi6ggsW7+WHYlVnAvALH7OVY9E1BRUuRjslt7ZSheDyUsWg0VyMVhTlXMVWHG/uKNcs3Bg8i/iCDhqFJi4OgJLxTXzEmAoBhSq0v5WmRJQsHpARE1UucVgD5ReX24xWJntbgtvihu6XD9byWKwMr2zfpE8o9XY5KaJldabCYBXKDDlV8AjSOqoqA4xcXUE2kDxSbUoB8i8KK6sLSqzMItVAiKi8qpaDHZr7+y1s5UvBvMKuaV3tiMXgzmrvAxxTuuNC4BnsFhp9WwpdVRUx/iX6QgEQVygdfmQWEHw61BaceUoLCIi25RdDNa2zGIwQ7GYzFiqshnxpYvBspLFS0WLwcr2znIxmGPLvyHuiHX9LODRQkxam4VKHRXVAyaujsKauJb0uRZxFBYRUZ1QqCpeDFaQWZLExtu/GMy/o5jUcjGY9AoyxaQ1I148gzn5F8A7XOqoqJ4wcXUUlh20LJMFWHElIqpfbt5AWD/xYlFuMViZS7WLwcr0zvp3FE9Vs82r/hXeFJPW9L8Bjb+YtPq0ljoqqkdMXB2FdSTWOfGjteLKGa5ERA2mqsVgGWdu2Uzh1sVgP5YeX24xWMfSCi3PotUdXTawehSQdgJway7OaW3eVuqoqJ4xcXUUviUV1xvnAaOB270SETkSlTvQsrt4sSi7GKxs72x1i8Fu7Z31bsXFYPbS5QBrRgNXj4jbpk/+RexLpkaPfymOwjMYULoB+gLgZiK3eyUicnS2LgZLPyUmtWUXg53dVHp82cVglsosF4NVrigPWDtGXBei9hK3cfWPlDoqaiBMXB2FTAY0bwekHivdxxtgxZWIyNlUuxisTLtBRrxYsKhwMZhf+b5Z/47ieoimvBisOB/4epxYyXbxBCb9DAR2ljoqakBMXB2Jb3sxcb1+lhVXIqLGprLFYDcTyiS0J8UpB5mXgPwM4FIGF4NZ6AuBbx4BkvaKRZ2JPwFBd0gdFTUwJq6OxNLnaumPAlhxJSJqzGQycRW8T2suBquKXgesewxI2A2oNMCEH8r3G1OTwcTVkVgnC5wBlO7i543lSYeIiGxny2IwS6tBY18MZigCvp0IXNwhrgUZ/x0QHC11VCQRJ/rNbQLKjsSyDE9mxZWIiIBqFoOdv332bO7ViheDKdTiGT5nWAxmKAa+mwKc/wNQuAKPfQuE9pE6KpIQE1dH4hUKyFWAoVBckQpwjisREVVNoSptDyir3GKwkt7ZaheDdSx/kXIxmFEPfP+4mHQr1MBj64DwO6WJhRwGE1dHIlcAPm3FniZjsXgdK65ERFQTNi8GOwVkJpRZDLaz9Phyi8HKXOp7MZjRAPwwDTjzq1jQeWQt0Oqu+ns8chpMXB2Nb3sxcbVgjysREdWV6haDle2drW4xmLXNoKR31q9DzV+zTEYgKRbISwfcfYEjq4D4nwGZEhi3FmgzuNq7oKaBiauj8b1l5w8XrTRxEBFR01HpYrBUscXAktCmnwKunxMXgyXHiZeyLIvByvbOVrcYLH4jsOVlIOdq+esFGTB2FdDunrr7d5LTY+LqaCwjsQDxFI3STbpYiIio6RIEwCNIvNR6MVjELaO6OoqLweI3At9OAmC+/fHNJsBkqPd/JjkXJq6OpmziqlADiXvFFZQyuXQxERERWdi8GOwUkHG6ZDHYMfFSlptvyczyCpJWAIAAbHkFiLiPr4FkxcTV0WScLv1cnw+svF98tzvsfSByhHRxERERVaWqxWBl+2Yti8EKrlVzh2Yg54rY+8ppAlRCJuWDv/3226ru3bu7a7Vara+vr+aBBx5wPX36dLmYCgsLMX36dLW3t7dGo9FoR44c6Zqamto497WL3wh8P/X263NSxVMp8RsbPiYiIqKasiwGixwB3PUKMG4N8OxRYM4VYOBrtt1HXnr9xkhORdLEdffu3YoZM2YUx8bG5v/+++8Fer0eQ4cOdcvLy7Me89xzz6l/++03xfr16wt37NiRn5qaKowaNcpVwrDrh8koNqdXeMqk5Lotr4jHEREROTOVOxASY9uxGv/6jYWcimA2m3OkDsIiPT1dCAgI0OzYsaNg4MCBxqysLPj5+WlXr15dOG7cOAMAxMfHyzp27Oi+d+/egr59+96Wxel0OhQVFVm/zsnJQUhIiDY7OxseHg48Wiphj9gWUJ3Jv/KUCREROT+TEfg4SjyrWGHRpmRx2PN/s8e1CcjJyYGnpyeysrJyPT0r33xJ0orrrbKzswEAPj4+ZgA4dOiQXK/X45577rEuK4yMjDQFBwebY2NjK/wtfuedd1y8vLy0lktISIhzzJOy9VQIT5kQEVFjIJOL6zcAALd2AJZ8PWw+k1Yqx2ESV6PRiOeee04dExNj7Ny5swkA0tLSBJVKhWbNmpU71s/Pz5yWllZhn+vcuXOLsrKyci2X5OTk3AYIv/ZsPRXCUyZERNRYRI4QZ7V6BJa/3iNIvJ6LkukWDjNVYMaMGer4+Hj5nj178mtzP2q1Gmq1RPsq10ZoH/EPtbpTJqF9GjoyIiKi+hM5Qhx5Zdk5S+PPMZBUKYdIXGfMmKHetGmTYteuXfkhISHWrC0gIMBcXFyMmzdvlqu6ZmRkCAEBAZUNfnNOllMm306CeIqk7D+Pp0yIiKgRk8m5foNsImmrgMlkwowZM9QbNmxQbN++vaB169blktGePXsalUoltm7dak2wT58+LUtJSRH69OnT+JbX85QJERERUaUkrbjOmDFDvX79euWPP/5YoNVqzVevXhUAwMvLy+zm5gYvLy9MmTJFP3v2bLW3t3ehh4eH+dlnn1X36tXLWNFEgUaBp0yIiIiIKiTpOCxBECpc8b948WLdtGnT9IC4AcHMmTPV3377rbKoqAiDBw82LFq0SBcUFGRTq0B2dja8vLwcfxwWERERURNl6zgsh5rjWh+YuBIRERE5Nqec40pEREREVBkmrkRERETkFJi4EhEREZFTYOJKRERERE6BiSsREREROQUmrkRERETkFJi4EhEREZFTkHTnrIZgNov7FOTkNOpxtUREREROy5KnWfK2yjT6xDU3NxcAEBwcLHEkRERERFSV3NxceHl5Vfr9Rr9zlsFgQGpqKjQaDWSy+u+MyMnJQUhIiDY5OTmXO3U5J/4MnR9/hs6NPz/nx5+h82von6HJZEJeXh4CAwOhUFReV230FVeFQiFJtdXDwwNVbVlGjo8/Q+fHn6Fz48/P+fFn6Pwa8mfYrFmzao/h4iwiIiIicgpMXImIiIjIKTBxrWMuLi547bXXil1cXKQOhWqIP0Pnx5+hc+PPz/nxZ+j8HPVn2OgXZxERERFR48CKKxERERE5BSauREREROQUmLgSERERkVNg4kpEREREToGJax1buHChMjQ0VKNWq7U9e/Z0i4uL4/+xE3j77bdV3bt3d9dqtVpfX1/NAw884Hr69Gn+7JzYO++8oxIEQfvMM8841pJYqlJKSorw6KOPqr29vTWurq7ajh07uh84cIB/i07CYDDg1VdfdQkLC9O4urpqW7VqpXnjjTdUJpNJ6tCoAjt37pTfe++9roGBgRpBELQ//PBDuY2pTCYT5syZ4xIQEKBxdXXVDhw40O3s2bOS/j3yyaAOff3114qXXnpJPXfu3KLDhw/nd+7c2XTvvfe6p6WlCVLHRlXbvXu3YsaMGcWxsbH5v//+e4Fer8fQoUPd8vLypA6NamD//v2yJUuWqKKiovhq6UQyMzPRr18/d6VSid9++63g5MmTeQsWLNB5e3ubpY6NbDNv3jzVV199pfzkk090p06dynvvvfd0H374ocvHH3+skjo2ul1+fj46d+5s+vTTT3UVff+9995Tff7556rPP/9cFxcXl+/u7m4eNmyYW2FhYUOHasVxWHWoZ8+ebj169DAtWrRIBwBGoxHBwcGaf/zjH8Vz584tljo+sl16eroQEBCg2bFjR8HAgQONUsdDtsvNzUW3bt3c/+///k/37rvvunTp0sX46aefFkkdF1Vv9uzZLnFxcfJ9+/YVSB0L1czw4cNd/f39zStWrLAmQiNHjnR1dXU1f/PNNxUmR+QYBEHQfv/994WjR482AGK1NSgoSPP8888Xv/LKK8UAkJWVhYCAAO3SpUsLx48fb5AiTlZc60hRURGOHj0qHzx4sPUHKZfLMWjQIMP+/fvlUsZG9svOzgYA+Pj4sNLjZGbMmKEePny4YejQoXzD4WR+/fVXRffu3Y2jRo1y9fX11XTp0sV90aJFSqnjItvFxMQYd+7cqThz5owMAI4cOSKLjY2VDx8+XJIkh2ru0qVLQnp6ujBkyBDrz87Lyws9e/Y0xsXFSZbXKKo/hGxx7do1wWg0wt/fv1yi4+fnZ5a6H4TsYzQa8dxzz6ljYmKMnTt35qlmJ7J27VrF0aNH5YcPH86XOhayX2Jiouyrr75SPfvss8Vz5swpOnjwoHzWrFlqFxcXTJ06VS91fFS91157rTgnJ0eIjIx0l8vlMBqNePPNN4smTZrExNXJpKamygAgICDgtrwmLS1NsryGiSvRLWbMmKGOj4+X79mzh8mPE0lKShJmzpyp/uOPPwpcXV2lDodqwGQyoVu3bsYPPvigCAB69OhhOnXqlOzLL79UMnF1DuvWrVOsW7dOuXr16sKoqCjT0aNH5bNmzXJp0aKFmT9DqgusBNYRX19fs1wuR3p6ermFWBkZGcKtVVhyXDNmzFBv2rRJsWPHjvyQkBD+3JzI4cOH5deuXRN69OjhrlAotAqFQrtnzx75Z599plIoFFqDgQUfRxcQEGDu0KFDubMcERERppSUFL5WOYmXX35Z/eKLLxaNHz/e0KVLF9OUKVP0zz77bPH8+fO5OMvJBAYGmgDg1gXmGRkZQkBAgGRnI/lkUEdcXFxwxx13GLdv326tYhuNRuzcuVPRu3dv9to5OJPJhBkzZqg3bNig2L59e0Hr1q2ZtDqZIUOGGI4fP55/5MgR66Vbt26mRx55RH/kyJF8hYInmBxdTEyM8dy5c+Vel86fPy8LCQlhy46TKCgogExWPrWQy+Uwm/mU6mxatWpl9vf3N2/bts365JmdnY1Dhw7JY2JiJMtr+Exeh2bOnFk8depU1x49ehh79epl/Oijj1QFBQXCE088wdMjDm7GjBnq9evXK3/88ccCrVZrvnr1qgAAXl5eZjc3N6nDIxt4eHjg1p5kd3d3s4+Pj5m9ys5h5syZRXfeeaf7W2+9pXrkkUf0Bw4ckC9dulS1aNEi6WbvkF3uu+8+w/z5811CQ0PNUVFRxiNHjsgXLlyomjx5Ml8HHVBubi7Kvlm8dOmS7K+//pL5+PiYw8LCzM8880zx/PnzXdq1a2dq1aqVae7cuS6BgYHmUaNGSXYKi+Ow6tjHH3+s/PDDD13S09OFzp07GxcuXFjUp08fVlwdnCAI2oquX7x4sW7atGl8wnVS/fv3d+M4LOeyYcMGxZw5c1wuXrwoCw0NNT3//PPFM2bM4N+gk8jJycFrr73msmHDBuW1a9eEwMBA89ixY/VvvvlmkYsL9wJxNNu3b5cPHjz4turMhAkT9KtXr9aZTCbMnTvXZenSpcrs7GwhJibGuGjRIl1ERIRkxQAmrkRERETkFNjjSkREREROgYkrERERETkFJq5ERERE5BSYuBIRERGRU2DiSkREREROgYkrERERETkFJq5ERERE5BSYuBIRERGRU2DiSkTUACZOnKh+4IEHXBv6cZcsWaIUBEErCIL2mWeeqXLrotDQUM2CBQtUZb+23PbmzZv1HywRUTUUUgdAROTsKtsy2OK1114r/vTTT3Vms7mhQirHw8MDp0+fztNoNHYFcPDgwfzdu3fLx44d2+AJNxFRRZi4EhHV0pUrV/Isn3/zzTfKt956y+X06dPW67RarVmrrTK3rVeCICAoKMjurNnf39/s7e0tTbZNRFQBtgoQEdVSUFCQ2XLx9PQ0WxJFy0Wr1d7WKtC/f3+3f/zjH+pnnnnGpVmzZlo/Pz/NokWLlHl5eZg0aZJaq9VqW7durfn111/lZR/rxIkTsnvuucdNo9Fo/fz8NI899pj62rVrgr0xp6WlCffee6+rq6urNiwsTLNq1SoWMojI4TFxJSKSyJo1a5Q+Pj7m/fv35//jH/8ofuaZZ9SjR492jYmJMR4+fDh/8ODBhsmTJ7vm5+cDAG7evIm7777brWvXrsaDBw/mb9q0qSA9PV02ZswYu0/lT548WX358mXZtm3bCr799tuCRYsWqWqSABMRNSQmrkREEunUqZPxP//5T3H79u1Nc+fOLVar1WjevLl5xowZ+vbt25veeOONoszMTOHYsWNyAFi4cKGqS5cupg8++KAoMjLS1KNHD9Py5csLd+3aJT9z5ozNz+dnzpyR/fHHH4qvvvqqsG/fvsbo6GjT0qVLdYWFhfX3jyUiqgM8NUREJJFOnTqZLJ8rFAp4e3ubo6KirNcFBASYASAjI0MAgBMnTsh3794t12g0tzXMXrhwQYiIiLDpcePj42UKhQI9e/a0PlZkZKTJy8ur5v8YIqIGwMSViEgiSqWy3MInQRCgVCqtX8tkYhHVZBLzy7y8POHee+81fPDBB7pb76smi6+IiJwNE1ciIidxxx13GH/66SdFeHi4uWyCa68OHTqYDAYDDh06JOvdu7cJAE6fPi3Lysqqq1CJiOoFe1yJiJzEM888U3zz5k1h3Lhxrvv375edP39e2LRpk3zSpElqg8Fg8/106NDBNGTIEOP06dNdY2Nj5QcPHpRNmzZN7erKca1E5NiYuBIROYmWLVua9+7dW2A0GjF8+HD3Ll26aGbOnKn28vIyW9oKbLVixYrCwMBA06BBg9wefvhhtyeffFLv6+vLdgMicmiC2WzOkToIIiKqH0uWLFHOnj1bnZWVlVuT22/fvl0+ePBgt8zMzNxmzZrVdXhERHZhxZWIqJHLzs6GRqPRvvDCCy723K5Dhw7u999/v1t9xUVEZC9WXImIGrGcnBykpaUJANCsWTPY0w6QkJAg6PV6AEDr1q3Ncrm8mlsQEdUvJq5ERERE5BTYKkBEREREToGJKxERERE5BSauREREROQUmLgSERERkVNg4kpEREREToGJKxERERE5BSauREREROQUmLgSERERkVP4fxk5iay/64qfAAAAAElFTkSuQmCC\n", "text/plain": [ "
" ] @@ -2271,22 +2160,17 @@ { "cell_type": "markdown", "id": "786d6034", - "metadata": { - "slideshow": { - "slide_type": "skip" - } - }, + "metadata": {}, "source": [ "[Back to top](#top)" ] } ], "metadata": { - "celltoolbar": "Slideshow", "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "Python [conda env:tut]", "language": "python", - "name": "python3" + "name": "conda-env-tut-py" }, "language_info": { "codemirror_mode": { @@ -2298,36 +2182,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" - }, - "varInspector": { - "cols": { - "lenName": 16, - "lenType": 16, - "lenVar": 40 - }, - "kernels_config": { - "python": { - "delete_cmd_postfix": "", - "delete_cmd_prefix": "del ", - "library": "var_list.py", - "varRefreshCmd": "print(var_dic_list())" - }, - "r": { - "delete_cmd_postfix": ") ", - "delete_cmd_prefix": "rm(", - "library": "var_list.r", - "varRefreshCmd": "cat(var_dic_list()) " - } - }, - "types_to_exclude": [ - "module", - "function", - "builtin_function_or_method", - "instance", - "_Feature" - ], - "window_display": false + "version": "3.9.0" } }, "nbformat": 4, diff --git a/docs/source/tutorials/12_Anaerobic_Digestion_Model_No_1.ipynb b/docs/source/tutorials/12_Anaerobic_Digestion_Model_No_1.ipynb deleted file mode 100644 index b986a473..00000000 --- a/docs/source/tutorials/12_Anaerobic_Digestion_Model_No_1.ipynb +++ /dev/null @@ -1,1538 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "8d891055", - "metadata": {}, - "source": [ - "# Anaerobic Digestion Model No. 1 (ADM1) \n", - "\n", - "- **Prepared by:**\n", - " \n", - " - [Ga-Yeong Kim](https://qsdsan.readthedocs.io/en/latest/CONTRIBUTING.html)\n", - " \n", - "- **Covered topics:**\n", - "\n", - " - [1. Introduction](#s1)\n", - " - [2. System Setup](#s2)\n", - " - [3. System Simulation](#s3)\n", - " \n", - "To run tutorials in your browser, go to this [Binder page](https://mybinder.org/v2/gh/QSD-Group/QSDsan-env/main?urlpath=git-pull%3Frepo%3Dhttps%253A%252F%252Fgithub.com%252FQSD-group%252FQSDsan%26urlpath%3Dtree%252FQSDsan%252Fdocs%252Fsource%252Ftutorials%26branch%3Dmain)." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "9a2a96b7", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "This tutorial was made with qsdsan v1.3.1 and exposan v1.3.1\n" - ] - } - ], - "source": [ - "import qsdsan as qs, exposan\n", - "print(f'This tutorial was made with qsdsan v{qs.__version__} and exposan v{exposan.__version__}')" - ] - }, - { - "cell_type": "markdown", - "id": "1bbdffaa", - "metadata": {}, - "source": [ - "## 1. Introduction " - ] - }, - { - "cell_type": "markdown", - "id": "cefa6e0a", - "metadata": {}, - "source": [ - "Anaerobic Digestion Model No.1 (ADM1) includes multiple steps describing **biochemical** as well as **physicochemical processes**. \n", - "\n", - "The **biochemical steps** include disintegration from homogeneous particulates to carbohydrates, proteins and lipids; extracellular hydrolysis of these particulate substrates to sugars, amino acids, and long chain fatty acids (LCFA), respectively; acidogenesis from sugars and amino acids to volatile fatty acids (VFAs) and hydrogen; acetogenesis of LCFA and VFAs to acetate; and separate methanogenesis steps from acetate and hydrogen/CO2. \n", - "\n", - "The **physico-chemical equations** describe ion association and dissociation, and gas-liquid transfer. \n", - "\n", - "Implemented as a differential and algebraic equation (DAE) set, there are 26 dynamic state concentration variables, and 8 implicit algebraic variables per reactor vessel or element. Implemented as differential equations (DE) only, there are 32 dynamic concentration state variables.\n", - "\n", - "*Water Science and Technology, Vol 45, No 10, pp 65–73*" - ] - }, - { - "attachments": { - "ADM1.JPG": { - "image/jpeg": "/9j/4AAQSkZJRgABAQEAYABgAAD/4RDyRXhpZgAATU0AKgAAAAgABAE7AAIAAAANAAAISodpAAQAAAABAAAIWJydAAEAAAAaAAAQ0OocAAcAAAgMAAAAPgAAAAAc6gAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEdhLVllb25nIEtpbQAAAAWQAwACAAAAFAAAEKaQBAACAAAAFAAAELqSkQACAAAAAzc4AACSkgACAAAAAzc4AADqHAAHAAAIDAAACJoAAAAAHOoAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAyMDIzOjEwOjE1IDIxOjM4OjU0ADIwMjM6MTA6MTUgMjE6Mzg6NTQAAABHAGEALQBZAGUAbwBuAGcAIABLAGkAbQAAAP/hCx9odHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvADw/eHBhY2tldCBiZWdpbj0n77u/JyBpZD0nVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkJz8+DQo8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIj48cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPjxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSJ1dWlkOmZhZjViZGQ1LWJhM2QtMTFkYS1hZDMxLWQzM2Q3NTE4MmYxYiIgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIi8+PHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9InV1aWQ6ZmFmNWJkZDUtYmEzZC0xMWRhLWFkMzEtZDMzZDc1MTgyZjFiIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iPjx4bXA6Q3JlYXRlRGF0ZT4yMDIzLTEwLTE1VDIxOjM4OjU0Ljc4MjwveG1wOkNyZWF0ZURhdGU+PC9yZGY6RGVzY3JpcHRpb24+PHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9InV1aWQ6ZmFmNWJkZDUtYmEzZC0xMWRhLWFkMzEtZDMzZDc1MTgyZjFiIiB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iPjxkYzpjcmVhdG9yPjxyZGY6U2VxIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+PHJkZjpsaT5HYS1ZZW9uZyBLaW08L3JkZjpsaT48L3JkZjpTZXE+DQoJCQk8L2RjOmNyZWF0b3I+PC9yZGY6RGVzY3JpcHRpb24+PC9yZGY6UkRGPjwveDp4bXBtZXRhPg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8P3hwYWNrZXQg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" - } - }, - "cell_type": "markdown", - "id": "180af880", - "metadata": {}, - "source": [ - "![assets/ADM1.JPG](attachment:ADM1.JPG)" - ] - }, - { - "cell_type": "markdown", - "id": "deab6410", - "metadata": {}, - "source": [ - "**Note:** You can find validation of the ADM1 system in [EXPOsan](https://github.com/QSD-Group/EXPOsan/tree/main/exposan/adm)." - ] - }, - { - "cell_type": "markdown", - "id": "47af6e27", - "metadata": {}, - "source": [ - "## 2. System Setup " - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "fb4e6486", - "metadata": {}, - "outputs": [], - "source": [ - "# Import packages\n", - "import numpy as np\n", - "from chemicals.elements import molecular_weight as get_mw\n", - "from qsdsan import sanunits as su, processes as pc, WasteStream, System\n", - "from qsdsan.utils import time_printer\n", - "\n", - "import warnings\n", - "warnings.simplefilter(action='ignore', category=FutureWarning) # to ignore Pandas future warning" - ] - }, - { - "cell_type": "markdown", - "id": "8c7244dc", - "metadata": {}, - "source": [ - "### 2.1. State variables of ADM1" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "5774fdae", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "CompiledComponents([S_su, S_aa, S_fa, S_va, S_bu, S_pro, S_ac, S_h2, S_ch4, S_IC, S_IN, S_I, X_c, X_ch, X_pr, X_li, X_su, X_aa, X_fa, X_c4, X_pro, X_ac, X_h2, X_I, S_cat, S_an, H2O])\n" - ] - } - ], - "source": [ - "# Components \n", - "cmps = pc.create_adm1_cmps() # create state variables for ADM1\n", - "cmps.show() # 26 components in ADM1 + water" - ] - }, - { - "cell_type": "markdown", - "id": "4ee7c0b5", - "metadata": {}, - "source": [ - "**S_su**: Monosaccharides, **S_aa**: Amino acids, **S_fa**: Total long-chain fatty acids, **S_va**: Total valerate, **S_bu**: Total butyrate, **S_pro**: Total propionate, **S_ac**: Total acetate, **S_h2**: Hydrogen gas, **S_ch4**: Methane gas, **S_IC**: Inorganic carbon, **S_IN**: Inorganic nitrogen, **S_I**: Soluble inerts, **X_c**: Composites, **X_ch**: Carobohydrates, **X_pr**: Proteins, **X_li**: Lipids, **X_su**: Biomass uptaking sugars, **X_aa**: Biomass uptaking amino acids, **X_fa**: Biomass uptaking long chain fatty acids, **X_c4**: Biomass uptaking c4 fatty acids (valerate and butyrate), **X_pro**: Biomass uptaking propionate, **X_ac**: Biomass uptaking acetate, **X_h2**: Biomass uptaking hydrogen, **X_I**: Particulate inerts, **S_cat**: Other cations, **S_an**: Other anions" - ] - }, - { - "cell_type": "markdown", - "id": "c4f28ea2", - "metadata": {}, - "source": [ - "### 2.2. The ADM1 `Process`" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "0dd6a5b8", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "ADM1([disintegration, hydrolysis_carbs, hydrolysis_proteins, hydrolysis_lipids, uptake_sugars, uptake_amino_acids, uptake_LCFA, uptake_valerate, uptake_butyrate, uptake_propionate, uptake_acetate, uptake_h2, decay_Xsu, decay_Xaa, decay_Xfa, decay_Xc4, decay_Xpro, decay_Xac, decay_Xh2, h2_transfer, ch4_transfer, IC_transfer])\n" - ] - } - ], - "source": [ - "# Processes\n", - "adm1 = pc.ADM1() # create ADM1 processes\n", - "adm1.show() # 22 processes in ADM1" - ] - }, - { - "cell_type": "markdown", - "id": "0b3d103f", - "metadata": {}, - "source": [ - "### 2.3. Petersen matrix of ADM1" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "9a9db08e", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
S_suS_aaS_faS_vaS_bu...X_h2X_IS_catS_anH2O
disintegration00000...00.2000
hydrolysis_carbs10000...00000
hydrolysis_proteins01000...00000
hydrolysis_lipids0.0500.9500...00000
uptake_sugars-10000.117...00000
uptake_amino_acids0-100.2120.239...00000
uptake_LCFA00-100...00000
uptake_valerate000-10...00000
uptake_butyrate0000-1...00000
uptake_propionate00000...00000
uptake_acetate00000...00000
uptake_h200000...0.060000
decay_Xsu00000...00000
decay_Xaa00000...00000
decay_Xfa00000...00000
decay_Xc400000...00000
decay_Xpro00000...00000
decay_Xac00000...00000
decay_Xh200000...-10000
h2_transfer00000...00000
ch4_transfer00000...00000
IC_transfer00000...00000
\n", - "

22 rows × 27 columns

\n", - "
" - ], - "text/plain": [ - " S_su S_aa S_fa S_va S_bu ... X_h2 X_I S_cat S_an H2O\n", - "disintegration 0 0 0 0 0 ... 0 0.2 0 0 0\n", - "hydrolysis_carbs 1 0 0 0 0 ... 0 0 0 0 0\n", - "hydrolysis_proteins 0 1 0 0 0 ... 0 0 0 0 0\n", - "hydrolysis_lipids 0.05 0 0.95 0 0 ... 0 0 0 0 0\n", - "uptake_sugars -1 0 0 0 0.117 ... 0 0 0 0 0\n", - "uptake_amino_acids 0 -1 0 0.212 0.239 ... 0 0 0 0 0\n", - "uptake_LCFA 0 0 -1 0 0 ... 0 0 0 0 0\n", - "uptake_valerate 0 0 0 -1 0 ... 0 0 0 0 0\n", - "uptake_butyrate 0 0 0 0 -1 ... 0 0 0 0 0\n", - "uptake_propionate 0 0 0 0 0 ... 0 0 0 0 0\n", - "uptake_acetate 0 0 0 0 0 ... 0 0 0 0 0\n", - "uptake_h2 0 0 0 0 0 ... 0.06 0 0 0 0\n", - "decay_Xsu 0 0 0 0 0 ... 0 0 0 0 0\n", - "decay_Xaa 0 0 0 0 0 ... 0 0 0 0 0\n", - "decay_Xfa 0 0 0 0 0 ... 0 0 0 0 0\n", - "decay_Xc4 0 0 0 0 0 ... 0 0 0 0 0\n", - "decay_Xpro 0 0 0 0 0 ... 0 0 0 0 0\n", - "decay_Xac 0 0 0 0 0 ... 0 0 0 0 0\n", - "decay_Xh2 0 0 0 0 0 ... -1 0 0 0 0\n", - "h2_transfer 0 0 0 0 0 ... 0 0 0 0 0\n", - "ch4_transfer 0 0 0 0 0 ... 0 0 0 0 0\n", - "IC_transfer 0 0 0 0 0 ... 0 0 0 0 0\n", - "\n", - "[22 rows x 27 columns]" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# Petersen stoichiometric matrix\n", - "adm1.stoichiometry" - ] - }, - { - "cell_type": "markdown", - "id": "4d14e88c", - "metadata": {}, - "source": [ - "**The rate of production or consumption for a state variable**
\n", - "\n", - "$a_{ij}$: the stoichiometric coefficient of component $j$ in process $i$ (i.e., value on the $i$th row and $j$th column of the stoichiometry matrix)
\n", - "$\\rho_i$: process $i$'s reaction rate
\n", - "$r_j$: the overall production or consumption rate of component $j$
\n", - "$$r_j = \\sum_i{a_{ij}\\cdot\\rho_i}$$\n", - "In matrix notation, this calculation can be neatly described as\n", - "$$\\mathbf{r} = \\mathbf{A^T} \\mathbf{\\rho}$$\n", - "where $\\mathbf{A}$ is the stoichiometry matrix and $\\mathbf{\\rho}$ is the array of process rates." - ] - }, - { - "cell_type": "markdown", - "id": "e2c2360d", - "metadata": {}, - "source": [ - "### 2.4. Influent & effluent" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "a28bc7d2", - "metadata": {}, - "outputs": [], - "source": [ - "# Flow rate, temperature, HRT\n", - "Q = 170 # influent flowrate [m3/d]\n", - "Temp = 273.15+35 # temperature [K]\n", - "HRT = 5 # HRT [d]" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "28a9c8e5", - "metadata": {}, - "outputs": [], - "source": [ - "# WasteStream\n", - "inf = WasteStream('Influent', T=Temp) # influent\n", - "eff = WasteStream('Effluent', T=Temp) # effluent\n", - "gas = WasteStream('Biogas') # gas" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "bdd90569", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "WasteStream: Influent\n", - "phase: 'l', T: 308.15 K, P: 101325 Pa\n", - "flow (g/hr): S_su 70.8\n", - " S_aa 7.08\n", - " S_fa 7.08\n", - " S_va 7.08\n", - " S_bu 7.08\n", - " S_pro 7.08\n", - " S_ac 7.08\n", - " S_h2 7.08e-05\n", - " S_ch4 0.0708\n", - " S_IC 3.4e+03\n", - " S_IN 992\n", - " S_I 142\n", - " X_c 1.42e+04\n", - " X_ch 3.54e+04\n", - " X_pr 1.42e+05\n", - " ... 6.97e+06\n", - " WasteStream-specific properties:\n", - " pH : 7.0\n", - " Alkalinity : 2.5 mg/L\n", - " COD : 57096.0 mg/L\n", - " BOD : 12769.4 mg/L\n", - " TC : 20596.5 mg/L\n", - " TOC : 20116.0 mg/L\n", - " TN : 3683.2 mg/L\n", - " TP : 489.3 mg/L\n", - " TK : 9.8 mg/L\n", - " Component concentrations (mg/L):\n", - " S_su 10.0\n", - " S_aa 1.0\n", - " S_fa 1.0\n", - " S_va 1.0\n", - " S_bu 1.0\n", - " S_pro 1.0\n", - " S_ac 1.0\n", - " S_h2 0.0\n", - " S_ch4 0.0\n", - " S_IC 480.4\n", - " S_IN 140.1\n", - " S_I 20.0\n", - " X_c 2000.0\n", - " X_ch 5000.0\n", - " X_pr 20000.0\n", - " ...\n" - ] - } - ], - "source": [ - "# Set influent concentration\n", - "C_mw = get_mw({'C':1}) # molecular weight of carbon\n", - "N_mw = get_mw({'N':1}) # molecular weight of nitrogen\n", - "\n", - "default_inf_kwargs = {\n", - " 'concentrations': {\n", - " 'S_su':0.01,\n", - " 'S_aa':1e-3,\n", - " 'S_fa':1e-3,\n", - " 'S_va':1e-3,\n", - " 'S_bu':1e-3,\n", - " 'S_pro':1e-3,\n", - " 'S_ac':1e-3,\n", - " 'S_h2':1e-8,\n", - " 'S_ch4':1e-5,\n", - " 'S_IC':0.04*C_mw,\n", - " 'S_IN':0.01*N_mw,\n", - " 'S_I':0.02,\n", - " 'X_c':2.0,\n", - " 'X_ch':5.0,\n", - " 'X_pr':20.0,\n", - " 'X_li':5.0,\n", - " 'X_aa':1e-2,\n", - " 'X_fa':1e-2,\n", - " 'X_c4':1e-2,\n", - " 'X_pro':1e-2,\n", - " 'X_ac':1e-2,\n", - " 'X_h2':1e-2,\n", - " 'X_I':25,\n", - " 'S_cat':0.04,\n", - " 'S_an':0.02,\n", - " },\n", - " 'units': ('m3/d', 'kg/m3'),\n", - " } # concentration of each state variable in influent\n", - "\n", - "inf.set_flow_by_concentration(Q, **default_inf_kwargs) # set influent concentration\n", - "inf" - ] - }, - { - "cell_type": "markdown", - "id": "4bf9c287", - "metadata": {}, - "source": [ - "### 2.5. Reactor" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "1fc90df0", - "metadata": {}, - "outputs": [], - "source": [ - "# SanUnit\n", - "AD = su.AnaerobicCSTR('AD', ins=inf, outs=(gas, eff), model=adm1, V_liq=Q*HRT, V_gas=Q*HRT*0.1, T=Temp)" - ] - }, - { - "cell_type": "markdown", - "id": "0716d4c9", - "metadata": {}, - "source": [ - "**su.AnaerobicCSTR**(\n", - " ID='',\n", - " ins=None,\n", - " outs=(),\n", - " thermo=None,\n", - " init_with='WasteStream',\n", - " V_liq=3400,\n", - " V_gas=300,\n", - " model=None,\n", - " T=308.15,\n", - " headspace_P=1.013,\n", - " external_P=1.013,\n", - " pipe_resistance=50000.0,\n", - " fixed_headspace_P=False,\n", - " retain_cmps=(),\n", - " fraction_retain=0.95,\n", - " isdynamic=True,\n", - " exogenous_vars=(),\n", - " **kwargs,\n", - ")\n", - "\n", - "**Parameters**
\n", - "*ins* : :class:`WasteStream`,\n", - " Influent to the reactor.
\n", - "*outs* : Iterable,\n", - " Biogas and treated effluent(s).
\n", - "*V_liq* : float, optional,\n", - " Liquid-phase volume [m^3]. The default is 3400.
\n", - "*V_gas* : float, optional,\n", - " Headspace volume [m^3]. The default is 300.
\n", - "*model* : :class:`Processes`, optional,\n", - " The kinetic model, typically ADM1-like. The default is None.
\n", - "*T* : float, optional,\n", - " Operation temperature [K]. The default is 308.15.
\n", - "*headspace_P* : float, optional,\n", - " Headspace pressure, if fixed [bar]. The default is 1.013.
\n", - "*external_P* : float, optional,\n", - " External pressure, typically atmospheric pressure [bar]. The default is 1.013.
\n", - "*pipe_resistance* : float, optional,\n", - " Biogas extraction pipe resistance [m3/d/bar]. The default is 5.0e4.
\n", - "*fixed_headspace_P* : bool, optional,\n", - " Whether to assume fixed headspace pressure. The default is False.
\n", - "*retain_cmps* : Iterable[str], optional,\n", - " IDs of the components that are assumed to be retained in the reactor, ideally.\n", - " The default is ().
\n", - "*fraction_retain* : float, optional,\n", - " The assumed fraction of ideal retention of select components. The default is 0.95.
" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "4d403072", - "metadata": { - "scrolled": false - }, - "outputs": [ - { - "data": { - "image/svg+xml": [ - "\n", - "\n", - "\n", - "\n", - "AD\n", - "Anaerobic CSTR:c->179376415616:w\n", - "\n", - "\n", - " Biogas\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "AD\n", - "Anaerobic CSTR:c->179376414536:w\n", - "\n", - "\n", - " Effluent\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "179376414696:e->AD\n", - "Anaerobic CSTR:c\n", - "\n", - "\n", - " Influent\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "AD\n", - "Anaerobic CSTR\n", - "\n", - "\n", - "AD\n", - "Anaerobic CSTR\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "179376414696\n", - "\n", - "\n", - "\n", - "\n", - "179376415616\n", - "\n", - "\n", - "\n", - "\n", - "179376414536\n", - "\n", - "\n", - "\n", - "" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "AnaerobicCSTR: AD\n", - "ins...\n", - "[0] Influent\n", - "phase: 'l', T: 308.15 K, P: 101325 Pa\n", - "flow (g/hr): S_su 70.8\n", - " S_aa 7.08\n", - " S_fa 7.08\n", - " S_va 7.08\n", - " S_bu 7.08\n", - " S_pro 7.08\n", - " S_ac 7.08\n", - " S_h2 7.08e-05\n", - " S_ch4 0.0708\n", - " S_IC 3.4e+03\n", - " S_IN 992\n", - " S_I 142\n", - " X_c 1.42e+04\n", - " X_ch 3.54e+04\n", - " X_pr 1.42e+05\n", - " ... 6.97e+06\n", - " WasteStream-specific properties:\n", - " pH : 7.0\n", - " COD : 57096.0 mg/L\n", - " BOD : 12769.4 mg/L\n", - " TC : 20596.5 mg/L\n", - " TOC : 20116.0 mg/L\n", - " TN : 3683.2 mg/L\n", - " TP : 489.3 mg/L\n", - " TK : 9.8 mg/L\n", - "outs...\n", - "[0] Biogas\n", - "phase: 'l', T: 298.15 K, P: 101325 Pa\n", - "flow: 0\n", - " WasteStream-specific properties: None for empty waste streams\n", - "[1] Effluent\n", - "phase: 'l', T: 308.15 K, P: 101325 Pa\n", - "flow: 0\n", - " WasteStream-specific properties: None for empty waste streams\n" - ] - } - ], - "source": [ - "AD # anaerobic CSTR with influent, effluent, and biogas\n", - " # before running the simulation, 'outs' have nothing" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "b162ac79", - "metadata": {}, - "outputs": [], - "source": [ - "# Set initial condition of the reactor\n", - "default_init_conds = {\n", - " 'S_su': 0.0124*1e3,\n", - " 'S_aa': 0.0055*1e3,\n", - " 'S_fa': 0.1074*1e3,\n", - " 'S_va': 0.0123*1e3,\n", - " 'S_bu': 0.0140*1e3,\n", - " 'S_pro': 0.0176*1e3,\n", - " 'S_ac': 0.0893*1e3,\n", - " 'S_h2': 2.5055e-7*1e3,\n", - " 'S_ch4': 0.0555*1e3,\n", - " 'S_IC': 0.0951*C_mw*1e3,\n", - " 'S_IN': 0.0945*N_mw*1e3,\n", - " 'S_I': 0.1309*1e3,\n", - " 'X_ch': 0.0205*1e3,\n", - " 'X_pr': 0.0842*1e3,\n", - " 'X_li': 0.0436*1e3,\n", - " 'X_su': 0.3122*1e3,\n", - " 'X_aa': 0.9317*1e3,\n", - " 'X_fa': 0.3384*1e3,\n", - " 'X_c4': 0.3258*1e3,\n", - " 'X_pro': 0.1011*1e3,\n", - " 'X_ac': 0.6772*1e3,\n", - " 'X_h2': 0.2848*1e3,\n", - " 'X_I': 17.2162*1e3\n", - " } # concentration of each state variable in reactor\n", - "\n", - "AD.set_init_conc(**default_init_conds) # set initial condition of AD" - ] - }, - { - "cell_type": "markdown", - "id": "051f6b47", - "metadata": {}, - "source": [ - "### 2.6. System set-up" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "85b13876", - "metadata": {}, - "outputs": [ - { - "data": { - "image/svg+xml": [ - "\n", - "\n", - "\n", - "\n", - "Influent:c->Anaerobic_Digestion\n", - "System:c\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "Anaerobic_Digestion\n", - "System:c->Biogas\n", - "Effluent:c\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "Influent\n", - "\n", - "\n", - "Influent\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "Anaerobic_Digestion\n", - "System\n", - "\n", - "\n", - "Anaerobic_Digestion\n", - "System\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "Biogas\n", - "Effluent\n", - "\n", - "\n", - "Biogas\n", - "Effluent\n", - "\n", - "\n", - "\n", - "\n", - "" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "System: Anaerobic_Digestion\n", - "ins...\n", - "[0] Influent \n", - " phase: 'l', T: 308.15 K, P: 101325 Pa\n", - " flow (kmol/hr): S_su 0.000393\n", - " S_aa 0.00708\n", - " S_fa 2.76e-05\n", - " S_va 6.94e-05\n", - " S_bu 8.13e-05\n", - " S_pro 9.69e-05\n", - " S_ac 0.00012\n", - " ... 709\n", - "outs...\n", - "[0] Biogas \n", - " phase: 'l', T: 298.15 K, P: 101325 Pa\n", - " flow: 0\n", - "[1] Effluent \n", - " phase: 'l', T: 308.15 K, P: 101325 Pa\n", - " flow: 0\n" - ] - } - ], - "source": [ - "# System\n", - "sys = System('Anaerobic_Digestion', path=(AD,)) # aggregation of sanunits\n", - "sys.set_dynamic_tracker(eff, gas) # what you want to track changes in concentration\n", - "sys # before running the simulation, 'outs' have nothing" - ] - }, - { - "cell_type": "markdown", - "id": "cd84e41c", - "metadata": {}, - "source": [ - "[Back to top](#top)" - ] - }, - { - "cell_type": "markdown", - "id": "bd50264c", - "metadata": {}, - "source": [ - "## 3. System Simulation " - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "132152fe", - "metadata": {}, - "outputs": [], - "source": [ - "# Simulation settings\n", - "t = 10 # total time for simulation\n", - "t_step = 0.1 # times at which to store the computed solution \n", - "\n", - "method = 'BDF' # integration method to use\n", - "# method = 'RK45'\n", - "# method = 'RK23'\n", - "# method = 'DOP853'\n", - "# method = 'Radau'\n", - "# method = 'LSODA'\n", - "\n", - "# https://docs.scipy.org/doc/scipy/reference/generated/scipy.integrate.solve_ivp.html" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "id": "74bcbaf0", - "metadata": {}, - "outputs": [], - "source": [ - "# Run simulation\n", - "sys.simulate(state_reset_hook='reset_cache',\n", - " t_span=(0,t),\n", - " t_eval=np.arange(0, t+t_step, t_step),\n", - " method=method,\n", - " # export_state_to=f'sol_{t}d_{method}_AD.xlsx', # uncomment to export simulation result as excel file\n", - " )" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "id": "55247c4c", - "metadata": { - "scrolled": false - }, - "outputs": [ - { - "data": { - "image/svg+xml": [ - "\n", - "\n", - "\n", - "\n", - "Influent:c->Anaerobic_Digestion\n", - "System:c\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "Anaerobic_Digestion\n", - "System:c->Effluent\n", - "Biogas:c\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "Influent\n", - "\n", - "\n", - "Influent\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "Anaerobic_Digestion\n", - "System\n", - "\n", - "\n", - "Anaerobic_Digestion\n", - "System\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "Effluent\n", - "Biogas\n", - "\n", - "\n", - "Effluent\n", - "Biogas\n", - "\n", - "\n", - "\n", - "\n", - "" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "System: Anaerobic_Digestion\n", - "ins...\n", - "[0] Influent \n", - " phase: 'l', T: 308.15 K, P: 101325 Pa\n", - " flow (kmol/hr): S_su 0.000393\n", - " S_aa 0.00708\n", - " S_fa 2.76e-05\n", - " S_va 6.94e-05\n", - " S_bu 8.13e-05\n", - " S_pro 9.69e-05\n", - " S_ac 0.00012\n", - " ... 709\n", - "outs...\n", - "[0] Biogas \n", - " phase: 'g', T: 308.15 K, P: 101325 Pa\n", - " flow (kmol/hr): S_h2 0.00119\n", - " S_ch4 8.5\n", - " S_IC 0.414\n", - " H2O 0.205\n", - "[1] Effluent \n", - " phase: 'l', T: 308.15 K, P: 101325 Pa\n", - " flow (kmol/hr): S_su 0.00164\n", - " S_aa 0.129\n", - " S_fa 0.0222\n", - " S_va 0.00332\n", - " S_bu 0.00447\n", - " S_pro 0.0106\n", - " S_ac 0.639\n", - " ... 587\n" - ] - } - ], - "source": [ - "sys # now you have 'outs' info." - ] - }, - { - "cell_type": "markdown", - "id": "7b57f738", - "metadata": {}, - "source": [ - "### 3.1. Check simulation results: Effluent" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "id": "990d5e59", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(
,\n", - " )" - ] - }, - "execution_count": 16, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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v7JMiUZE5Cmys7Ph7++PzMzMHH9//0rbsgeYiKgGuX76FPb8/jMuHSmaqUYQ0LRzN8QOHoqQBo0qnAWiWI+RzzP8ElG1xwBMVi1fvhwvvGB9erbIyEicPHmyiisiojslSRKunjyGPb/+hKunjgMABJkMLeK6o9NjwxBcN8LStklsVwycPKXcPMDa4FroMfJ5NIntWuX1ExE5m8cE4JkzZ3pNmzZN9eKLL+o/+eSTQgAoKCjAK6+8ol6xYoVCr9cLPXv2NH7++ee68PBwy7xaly5dEsaNG6fevn27wtfXVxo+fLghPj6+UKlUWs69efNm+b/+9S91YmKirF69etJbb71VOGbMGM7HVYmBAwciNjbW6r6SX1si8lySJOHikQPY89vPSDp7GgAgkyvQ8sGH0WngPxAQFm71uCaxXdG4Y2yFK8EREVV3HhGA9+zZI1uyZIlXdHR0qSswXnrpJfXatWsVP//8c4G/v780ceJE9eOPP+69e/fufAAwGo3o37+/T2hoqLRjx468pKQkYdSoUd5KpRLx8fGFAHD+/Hlh4MCBPmPHjtUvX77csHHjRvm4cePUderUEfv162dyx/OtDrRaLbRarbvLIKI7IIki/t6/B3t++9myVLFC6YVWD/dBhwGPw69WbZvnkMnkiGhp34qQRETVjdsDcE5ODp555hnvL774omDWrFmq4u2ZmZlYunSp8rvvvivo1auXCQC++eYbXcuWLX137dolj4uLM61bt05++vRp2aZNm3KLe4Xfeeedwrffflv93nvvFapUKixcuNArMjJSnD9/fiEAtGzZUty1a5di3rx5qn79+uW751kTETmfaDLhzO4d2Pv7CqRfuwIAUKrUaN27Hzo8+hh8OXsDEREAD5gHePz48epHHnnE2KdPn1K9sfv375cbDAb07t3bWLwtKipKjIiIkBISEuQAkJCQoGjZsqVYckjEI488YszOzsbx48dlALB37175ww8/bCx57t69exv37dtX4Xt5Op0OWVlZlo/sbE6UQUSey2Q04PjWDfhm8jj8+cmHSL92BSofX3Qe8iTGfvY1ug8fzfBLRFSCW3uAly9frjh8+LD8wIED5ZYdS05OFry8vBAYWPqPdkhIiJScnCwAQEpKihASElJq/eCwsDCp+PjK2mRnZyM/Px8+VpYsnjlzpmrWrFled/v8iIhcyajX48TW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803Bd999p3z77bctCf38+fPCwIEDfR588EHjoUOH8iZOnFg4btw49Z9//skea7JJFE24evIYEnf9hasnjyHl0gX8OP11ZCYnwa92KJ76dzxCG93n7jKJiIjIToIkSdmOHPCPf/zD29/fX/rqq690Wq1We+TIkdyQkBBp4MCBPhEREeLdzBKRkpIihIWFabZs2ZLfo0cPU2ZmJkJCQrTfffddwRNPPGEEgFOnTslatmzpu3Pnzvy4uDjTH3/8IR80aJDPtWvXcsPDwyUA+PTTT5Vvv/22OjU1NUelUuFf//qXau3atYpTp07lFT/W0KFDvTMzM4WNGzfm26orKysLAQEB2qysLPj5+d3p06Nq6NzeBGxZuhi5t9JubxQEQJJQKyISQ6b8G5qgYPcVSERERACA7Oxs+Pv7IzMzM8ff37/Stg73AM+dO1eXkJAgb968ua9Op8M///lP7wYNGmiuX78uxMfHF95x1TAHTQAIDg6WAGD//v1yg8GA3r17G4vbREVFiREREVJCQoIcABISEhQtW7YUi8MvADzyyCPG7OxsHD9+XAYAe/fulT/88MPGko/Vu3dv4759+6z2AOt0OmRlZVk+ime6oJrl3N4ErJ77funwCwCS+Uetw4AhDL9ERETVkMOzQNSvX186duxY3o8//qg4cuSIPC8vT3j22WcNI0aMMPj4+NxxISaTCS+99JK6S5cuppiYGBEAkpOTBS8vLwQGBpZqGxISIiUnJwuAudc4JCSk1NCLsLAwqfj4ytpkZ2cjPz8fZeueOXOmatasWV53/GSo2hNFE7YsXVxpm10/f4sW93fnUsZERETVjMMBGACUSiVGjBhhHDFihNF2a/uMHz9eferUKfmOHTvybLd2ralTpxa+9tprlt7s7Oxs1K9fX+vOmqhqXU88Wb7nt4yc9DRcTzzJi9+IiIiqGYeHQLz33nteixcvLjeDwuLFi5V32ms6fvx49Z9//qnYsmVLXv369S09tWFhYZJer0dGRkap9qmpqUJxL29oaKiUmppaalaI4p5fW238/PzK9f4CgFqthr+/v+WD435rntzMDNuNHGhHREREnsPhALxkyRKvFi1aiGW3R0dHi19++aVDAVgURYwfP169atUqxebNm/MbN25caphCx44dTUqlEhs3brT0VCcmJsquXr0qdO3a1QQAXbt2NZ48eVJWHHoB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wHZQKBRu6/318/ODreX8yHPx+1f98XtY/fF7WL3x+1f9VeX3MDAw0K52vAiOiIiIiGoUBmAiIiIiqlEYgD2USqXC22+/rVepVO4uhe4Av3/VH7+H1R+/h9Ubv3/Vnyd/D3kRHBERERHVKOwBJiIiIqIahQGYiIiIiGoUBmAiIiIiqlEYgImIiIioRmEA9kALFixQRkZGatRqtbZjx44+u3fv5vepmnjvvfe82rdv76vVarW1a9fWDBgwwDsxMZHfv2pq5syZXoIgaCdOnOh5lzBTha5evSo89dRT6qCgII23t7e2ZcuWvnv37uXvYTVhNBrx1ltvqRo0aKDx9vbWNmrUSDNjxgwvURTdXRpVYOvWrfJ+/fp5h4eHawRB0P7666+lFloTRRFTpkxRhYWFaby9vbU9evTwOXPmjFt/J/kHwcP88MMPitdff109derUwgMHDuTFxMSI/fr1801OThbcXRvZtn37dsX48eP1CQkJeevXr883GAzo06ePT25urrtLIwft2bNHtmTJEq/o6Gj+161Gbt26hW7duvkqlUqsWbMm/8SJE7kffvihLigoSHJ3bWSf999/32vx4sXKjz/+WHfy5Mnc2bNn6+bOnauaP3++l7trI+vy8vIQExMjfvLJJzpr+2fPnu21cOFCr4ULF+p2796d5+vrK/Xt29enoKCgqku14DRoHqZjx44+HTp0ED///HMdAJhMJkRERGgmTJignzp1qt7d9ZFjUlJShLCwMM2WLVvye/ToYXJ3PWSfnJwctGvXzvfTTz/VzZo1S9W6dWvTJ598Uujuusi2V199VbV79275rl278t1dC92ZRx55xDs0NFRaunSpJUwNHjzY29vbW/rxxx+tBizyHIIgaH/55ZeCIUOGGAFz72+dOnU0L7/8sv7NN9/UA0BmZibCwsK0X331VcHTTz9tdEed7AH2IIWFhTh8+LC8Z8+elh8GuVyOhx56yLhnzx65O2ujO5OVlQUACA4OZu9TNTJ+/Hj1I488YuzTpw9ftFQzf/zxh6J9+/amxx9/3Lt27dqa1q1b+37++edKd9dF9uvSpYtp69atitOnT8sA4NChQ7KEhAT5I4884pagRHfnwoULQkpKitCrVy/L9y8gIAAdO3Y07d69223ZRmG7CVWVmzdvCiaTCaGhoaXCUkhIiOTusTLkOJPJhJdeekndpUsX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\n", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "eff.scope.plot_time_series(('S_aa', 'S_fa', 'S_va', 'S_bu', 'S_pro', 'S_ac')) # you can plot how each state variable changes over time" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "id": "6f674fab", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(
,\n", - " )" - ] - }, - "execution_count": 17, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "eff.scope.plot_time_series(('S_IC'))" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "id": "2ea79de8", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(
,\n", - " )" - ] - }, - "execution_count": 18, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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vn37uJEjR4YkKJEbc/78eSYvL48ZOnSo8/en0WjQq1cvfv/+/SHLNhLvRUh9KSgoYHieR3JysltYSkpKEkM9Vob4j+d5zJ49W9GvXz++S5cu9DV6hPj0008lx44d43755ZeKULeF+O/ChQvs+++/L5s1a5Z50aJFpkOHDnFz585VyOVyTJ482RLq9hHvFi9ebC4tLWU6dOgQxXEceJ7H888/b3rkkUcoAEegnJwcFgC0Wm21bJObmxuybEMBmJAgycjIUJw6dYrbs2cPBakIcfHiRWbOnDmK7du365VKZaibQ+pAEAR0796df/nll00A0LNnT+HkyZPse++9J6UAHBk2btwo2bhxo3TDhg2GTp06CceOHePmzp0rb9asmUi/QxIo1KsYRhITE0WO45CXl+d2wVt+fj5TtVeYhLeMjAzFd999J9m1a1dFamoq/e4ixC+//MIVFBQwPXv2jJJIJGqJRKLes2cP984778gkEonaaqUOqHCn1WrF9u3bu33j0q5dO+Hy5cv09y5CLFiwQDF//nzT+PHjrV27dhUmTZpkmTVrlnnlypV0EVwESklJEQCg6sX8+fn5jFarDdm3o/SGEEbkcjluueUWfufOnc6eeZ7nsXv3bknfvn1pLGIEEAQBGRkZis2bN0t27typb926NYXfCDJ06FDrr7/+WnH06FHnrXv37sJDDz1kOXr0aIVEQl+ahbt+/frxZ8+edfvblpWVxaamptIwpAih1+vBsu7xhOM4iCK9nUaiVq1aicnJyeKOHTucb6A6nQ6HDx/m+vXrF7JsQ+/mYWbOnDnmyZMnK3v27Mn36dOHf+ONN2R6vZ6ZMmUKfe0TATIyMhSff/65dNOmTXq1Wi1eu3aNAQCNRiOqVKpQN494ERMTg6rjtaOiosSEhASRxnFHhjlz5pgGDhwY9cILL8geeughy8GDB7m1a9fKVq9eHbr5lohfRo0aZV25cqW8ZcuWYqdOnfijR49yq1atkj366KP0dzBMlZWVwfWD5/nz59kjR46wCQkJYlpampiZmWleuXKlvG3btkKrVq2EJUuWyFNSUsR77703ZF+r0TRoYejNN9+Uvv766/K8vDymS5cu/KpVq0z9+/enHuAIwDCM2tP2Dz74wDh16lR6845At912m4qmQYssmzdvlixatEh+7tw5tmXLlsJTTz1lzsjIoH9/EaK0tBSLFy+Wb968WVpQUMCkpKSI48aNszz//PMmuZzWpAlHO3fu5IYMGVKtl2fChAmWDRs2GAVBwJL/b+/+Qdp44ziOP5c/eknv1Gi1KoVaHGJEUaEVpdDBf1CxU61CQbNYqIEMpW4KLR3FRRwCRVFEEFxcSoZiB0VEokNxMIJClwq2glGbqNXk7jeUiP2VWiP1T7j3Cw7uDp57vjflw5Pv3fX0pA4NDVl3dnak6urqmM/nOygqKrqyhQUCMAAAAAyFHmAAAAAYCgEYAAAAhkIABgAAgKEQgAEAAGAoBGAAAAAYCgEYAAAAhkIABgAAgKEQgAEAAGAoBGAASBJtbW3y48ePbZc97+DgoFWSJFWSJNXr9Z76Ka47d+4ofX19KSeP42NDodDFFwsAZ2C56gIAAH/+jHZcd3f34cDAwIGu65dV0i/S0tJEMBgMK4qSUAGBQCAyMzNjbmlpufTgDgB/QgAGgGtgfX09HN8fHx+3vn37NjUYDB6fU1VVV9VTM/KFkiRJ5OfnJ5y+b926pWdmZl5NageAP6AFAgCugfz8fD2+paen6/HAGd9UVf2tBeLhw4d2j8cje73eVIfDoebk5Cg+n88aDodFe3u7rKqqWlhYqLx//958cq6lpSVTQ0ODXVEUNScnR3n27Jm8ubkpJVrzxsaG1NjYaLPZbGpBQYEyOjrKogqApEAABoAkNjY2Zs3KytLn5+cjHo/n0Ov1yk+ePLFVV1fHFhcXI3V1dVG3222LRCJCCCFCoZCora21l5eXxwKBQMTv9+99/frV9PTp04RbFNxut/zlyxfT1NTU3sTExJ7P50s5T5AGgMtGAAaAJFZaWhp78+bNodPp1Hp6eg5lWRY3b97UOzs7j5xOp/b69esfW1tb0qdPn8xCCNHf359SVlam9fb2/iguLtbu3bunDQ8P709PT5tXVlbO/JuwsrJi+vDhg+Xdu3f7Dx48iFVWVmpDQ0MH+/v7F3ezAPCP8HcVACSx0tJSLb5vsVhEZmamXlJScnwuNzdXF0KIb9++SUIIsbS0ZJ6ZmTErivJbQ/Ha2ppUVFR0pnmXl5dNFotF3L9//3iu4uJiLSMj4/w3AwCXhAAMAEnMarX+8oCZJEnCarUeH5tMPxd1Ne1nTg2Hw1JjY2O0t7f34P/XOs9DbgCQjAjAAGAgFRUVscnJScvdu3f1k0E5US6XS4tGo2JhYcFUVVWlCSFEMBg0bW9v/6tSAeDC0AMMAAbi9XoPQ6GQ1Nraapufnzetrq5Kfr/f3N7eLkej0TNfx+VyafX19bEXL17Y5ubmzIFAwNTR0SHbbLzuF8D1RwAGAAO5ffu2Pjs7uxeLxcSjR49ulJWVKS9fvpQzMjL0eLvEWY2MjOzn5eVpNTU19ubmZvvz58+PsrOzaaMAcO1Juq7vXnURAIDra3Bw0NrV1SVvb29/P8/4jx8/muvq6uxbW1vfHQ7Hvy4PABLGCjAA4K92dnaEoijqq1evUhMZ53K5bjQ1Ndkvqi4AOA9WgAEAp9rd3RUbGxuSEEI4HA6RSJvD58+fpaOjIyGEEIWFhbrZbP7LCAC4eARgAAAAGAotEAAAADAUAjAAAAAMhQAMAAAAQyEAAwAAwFAIwAAAADAUAjAAAAAMhQAMAAAAQyEAAwAAwFD+A/fWK90JbqE6AAAAAElFTkSuQmCC\n", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "eff.scope.plot_time_series(('X_aa', 'X_fa', 'X_c4', 'X_pro', 'X_ac', 'X_h2'))" - ] - }, - { - "cell_type": "markdown", - "id": "1f991148", - "metadata": {}, - "source": [ - "### 3.2. Check simulation results: Gas" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "id": "d54aeb58", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(
,\n", - " )" - ] - }, - "execution_count": 19, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "gas.scope.plot_time_series(('S_h2'))" - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "id": "e021d8fb", - "metadata": { - "scrolled": false - }, - "outputs": [ - { - "data": { - "text/plain": [ - "(
,\n", - " )" - ] - }, - "execution_count": 20, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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qMoNKUlWMPeLnPPCLwDvY3O/yIEkh+zTTURERNRk1OmqqiNHjgiTJ0/WDxo0yDBo0CDDlClTdEeOHAm6MbW4uBi7du0Sd+3aJQLAL7/8Iu7atUs8duyYIIoi7r//fuezzz6r+/TTT9U//PCDOG7cuBiLxaJ4VoXo0qWLPGTIEOnvf/97zI4dO8TNmzerHnjgAf0tt9zibt26tQIAd955p0uj0Sh/+9vf9Pv27RPff/999auvvqp96KGHnLXV1piycwu9bQ++KADyrHZk5/q+iI+IiIiIahZ0C8TXX3+t+stf/mLo2rWr1L9/fwkAtm/frrrsssuMn332WVlGRkbAO8FlZ2erKm+e8eijj+oA6MaNG+d699137U888YSztLRUuPfee/VWq1Xo16+f9M0335TFxHiX+cXq1avLJk+eHDN06NBYURRxww03uF555RVveoyPj8e6devKpkyZou/Vq1dsYmKiMmPGDEckL4F2orjm8FuXcURERER0jqAoSlBrfHXr1i12yJAh7ueff95R+fFp06bp1q9fr967d29paEtsfFarFfHx8Sar1Yq4uLiwv96Oo6dx2xtZfsetntQX/donhr0eIiIiokhns9lgNptRVFRUbDabax0bdAvETz/9JE6aNOm82dOJEye6Dh06xIVqQ6B3agIsZj1q6ikRAFjMevROjcweZiIiIqJIFnRgbd68ubJnz57znrdnzx6xRYsWvCorBFSigDmj0wDgvBDsuT9ndBrXAyYiIiKqg6B7gCdMmODMzMyMOXr0qGPAgAESAGzdulX1wgsv6B544AGHv+dTYDLSLVg2rkeVdYABINms5zrARERERPUQdA+wLMt44YUXtEuWLNF6dluzWCzKww8/7Jw6dapTFC+8LoiG7gGuTJIVXPv8f3G8sAyPD++ESVddzJlfIiIiomqC6QEOegZYFEVMnz7dOX36dKfNVpGdGzoUNiUqUYBKVRF4u7eJZ/glIiIiqqegA3BlDL4Nw+Gq2LBOr1E1ciVERERE0S+gANy9e/fYjRs3liYkJKBbt26xglDzLOSFuAxaY3O4K5ZW1msuvPYSIiIiooYWUAAePXq0S6fTeT52C4LA1R4akGcGWKfmDDARERFRfQUUgOfNm+fdNnj+/Plc6aGB2TkDTERERBQyQSeq1NRU46lTp87rgThz5gxSU1ONoSmLPCRZgUuqmHDnDDARERFR/QUdgI8fPy643e7zHrfb7cIff/zBJQpCrMx57nP94+9FkGR2nxARERHVR8CrQHz66afesWvXrlWbzWZvEpMkCRs2bFC3a9dODnWBTdna/Xl4cs0B7/3xb+2EhRthEBEREdVLwBthiKJoAgBBEKAoVWchNRoN2rZtKz/33HOO66+//vzp4SjXGBthrN2fh8xVu1F9vtczxb5sXA+GYCIiIqKzwrIRhizLxQDQrl07486dO0tbtGjB38WHiSQrmPtFznnhFwAUVITguV/kYEhaMjfGICIiIgpS0D3Ax44dK2H4Da/s3ELkWe01HlcA5FntyM4tbLiiiIiIiC4QddoJrqSkBJs2bVIfP35ccDqdVaYgH374YWdNz6PAnCiuOfzWZRwRERERnRN0AP7+++/FUaNGGcrLy4XS0lI0a9ZMOX36tGAwGNCiRQuFAbj+Wpr0IR1HREREROcE3QLx8MMP60eOHOkuLCwsjomJwY4dO0pzc3NLLr/8cmnx4sWckgyB3qkJsJj1qKm7VwBgMevROzWhIcsiIiIiuiAEHYB//PFH1bRp05wqlQoqlQoOh0No27atsmjRIsfMmTN14SiyqVGJAuaMTvN5zBOK54xO4wVwRERERHUQdABWq9WKKFY8rUWLFvLx48cFAIiPj1f++OMP7tUbIhnpFiwb1wPxMZoqjyeb9VwCjYiIiKgegu4B7tatm5ydnS1eeuml8lVXXSXNmTNHd+rUKee7776rTUtLk8JRZFOVkW5BYakTMz7djy6t4jBrZBp6pyZw5peIiIioHoKesX3mmWfsFotFOfuxIz4+Hvfdd1/MqVOnhNdff509wCHmdFdsrtcuMRb92icy/BIRERHVU1AzwLIsIykpSenatasMAMnJycp3331XFp7SCADsZwOwTsPuEiIiIqJQCCpVKYqCjh07Gn/99VemsQbicFUEYL1G1ciVEBEREV0YggqyKpUK7du3l0+dOsXfwzcQu7uirVqvZgAmIiIiCoWgZ3IXLlzoePTRR3U//vgjZ4HDTJIV5J4qAQCcLnFAkrkDNREREVF9CYqi2IJ5QrNmzUxlZWVwu93QarWIiYmpcrywsLA4pBVGAKvVivj4eJPVakVcXFyDvOba/XmY+0UO8qznriu0mPWYMzqNS6ARERERVWOz2WA2m1FUVFRsNptrHRv0MmjPPfec3bMOMIXH2v15yFy1G9Xne/OtdmSu2s11gImIiIjqIegAPHHiRFc4CqEKkqxg7hc554VfAFBQsRPc3C9yMCQtmUuiEREREdVB0FO5KpXKlJ+ff17yOnXqlKBSqUyhKavpys4trNL2UJ0CIM9qR3ZuYcMVRURERHQBCToAK4rvC7Hsdju0Wm29C2rqThQHtpdIoOOIiIiIqKqAWyBefPFFLQAIgoDly5drjEaj95gkSdiyZYuqY8eOchhqbFJamvQhHUdEREREVQUcgP/xj39ogYoZ4DfeeEOrUp1bl1ar1SopKSnKsmXLOC1ZT71TE2Ax65FvtfvsAxYAJJv16J2a0NClEREREV0QAm6BOHbsWMmxY8dKrrrqKumHH34o8dw/duxYyeHDh0vXr19f1r9/fynUBbZt29YoCIKp+u3ee+/VA8DAgQMN1Y9NmjSpyvTosWPHhIyMjBiDwWBq0aKFcerUqTqXKzKv5VOJAuaMTgNQEXYr89yfMzqNF8ARERER1VHQq0D873//KwtHITXJzs4ulaRzuXrfvn1iRkaG4ZZbbvEm2AkTJrjmzZvn8NyPjY31Tp663W6MHDnSkJSUpGzZsqU0Ly9PGD9+fIxGo8HixYu9z4kkGekWLBvX47x1gJO5DjARERFRvQUdgN1uN1asWKHZuHGj+sSJE0L1i+L++9//hjQgJyUlVXmBhQsXqi+++GJl0KBB3lRsMBiUVq1a+bw6b+3atapDhw6J69evL7FYLAoAPPXUU46ZM2fq582b59DpdKEsN2Qy0i0YkpaMrnPXodQh4flbuuIvl7fmzC8RERFRPQW9CsT999+vf+SRR/SSJCE9PV3q2rVrlVs4ivRwOBx4//33NX/961+dlTfjWL16tSYxMdGYlpYWO336dF1paan32Pbt29VdunSRPeEXAIYPH+622WzYt2+fz/dvt9thtVq9N5stqM3yQkYlCt7tj/ukJjL8EhEREYVA0DPAH330kXr16tXlo0ePdoejoNp88sknaqvVKkyYMMHb/jB27FhXu3bt5Isuukj54YcfxCeeeEJ/+PBhcc2aNeUAUFBQILRs2bLK7HBycrICAL7WMwaA+fPn6xYsWNDoa7opigK7q2JhjRitys9oIiIiIgpE0AFYq9WiQ4cOjbLc2YoVK7RDhw51t27d2htoJ0+e7A3D3bp1ky0WS/nQoUMNR44cETp06OB70WI/Zs2a5Zg+fbq3P9hmsyElJaXBN/lwuM99mvUaBmAiIiKiUAi6BeKhhx5yLlmyRCvLDZuBc3NzhU2bNqn8bcXcr18/CQCOHDkiAhU9xCdOnKgy0+uZ+fXMBFen1+thNpu9t7i4uNC8iSCVO891lOjVQX+piIiIiMiHoGeAt23bptq8ebN63bp16s6dO0sajabKcU/rQaitWLFC26JFC8Vf68Xu3btVAOC5KK5///7uRYsWafPz8wVP4F23bp06Li4O6enpEb1xR6mz4q2qBGDnsTPonZrAPmAiIiKiego6AMfHxytjxoxp0EV0JUnCO++8oxk3bpyrcuA+cuSIsGrVKs3IkSPdzZs3V3744QfVI488or/yyiul7t27ywCQkZEhderUSR43blzM4sWL7fn5+cKcOXN099xzj1Ovj9zd1Nbuz8Psz/YDACQFuO2NLFi4DBoRERFRvQmKojTOEgdB+Oabb1QjRowwHDx4sLRTp07eWdvjx48L48aNizlw4IBYVlYmtG7dWh4zZox7zpw5DrPZ7H1+bm6ucO+99+q3bNmiNhgMyp133ulavHixo/rsdU2sVivi4+NNVqu1Qdoh1u7PQ+aq3eftBOeZ+102rgdDMBEREVElNpsNZrMZRUVFxZVzoC91CsAulwsbN25U/fzzz+Kdd97piouLw++//y6YzWbFZGrwa8XCriEDsCQruHLRxiobYFTm2Qp562PXsh2CiIiI6KxgAnDQLRC5ublCRkaG4ffffxcdDgeGDRvmjouLU5599lmtw+EQ3njjDd/JjQKSnVtYY/gFAAVAntWO7NxC9Guf2HCFEREREV0ggl5a4IEHHtD37NlTKiwsLI6JifE+/pe//MW9adMmrtVVTyeKA/v5IdBxRERERFRVnVaB2LZtW1n1LYRTU1PlP//8k2t11VNLU2AX5gU6joiIiIiqCjqwKooiSNL5Ox7/9ttvotForNPGE3RO79QEWMx61NTdKwCwmPXonZrQkGURERERXTCCDsDXXXed+6WXXvJuEywIAoqLi/HUU0/pMjIyGnx75AuNShQwppvlvBUgKpszOo0XwBERERHVUdAtEC+++KJ92LBhhk6dOsXa7XbcfvvtMT///LOYmJiorF692uH/DFSbtfvzsHxzbo3H/z4wlUugEREREdVDnZdBW716tXrv3r2q0tJS4fLLL5fuuusul8FgCEeNja6hlkHztwQaUNH+wCXQiIiIiKoK6zJoAKDRaHDXXXe577rrLrY8hJC/JdAALoFGREREVF9B9wDPmzdPu3z58vO2UFu+fLlmwYIFWl/PocBwCTQiIiKi8As6AL/55pvazp07y9UfT09Pl9944w0G4HrgEmhERERE4Rd0AC4oKBBatWp1XgBu2bKlnJ+fz8bUeuASaEREREThF3QAbt26tbx169bzeoe3bt2qtlgsXAe4HlSigDmj02pcAk0Bl0AjIiIiqq+gL4KbMGGC6+GHH9Y5nU4MHjzYDQDfffed+oknntA99NBDztCXSEREREQUOkEH4Mcff9x5+vRp4YEHHtA7nRV5V6/X45FHHnHMnj2bAbgeJFnB3C9yajwuAJj7RQ6GpCVzFpiIiIiojuq0DjAAFBcX48CBA6LBYEDHjh1lvf7CvTCrodYB3nH0NG57I8vvuNWT+nIZNCIiIqJKwr4OMACYTCb07dv3vIvhqO64DBoRERFR+AUdgEtKSrBgwQLdpk2bVCdPnhRluWoGzs3NLQlZdU0Ml0EjIiIiCr+6XAQXs2XLFtXtt9/uslgsbkFgL2qoeJZBy7fafa4EIQBI5jJoRERERPUSdAD+9ttv1Z9//nnZwIEDpXAU1JR5lkG7d9Vun8e5DBoRERFR/QW9DnB8fLySmJjI9X6JiIiIKCoFHYDnzp3rmD17tq60tDQc9TRpgS6DJsn8+YOIiIioroJugXjxxRe1ubm5YnJysiklJUXWaDRVju/du5fJuI6ycwuRZ615hQcFQJ7VjuzcQi6DRkRERFRHQQfgMWPGuMJRCHEZNCIiIqKGEHQAnjdvHnd7CxMug0ZEREQUfnXeCCM7O1s8ePCgCgC6dOkiXXHFFdwUo556tm0GUQBqa/EVhYpxRERERFQ3QQfg/Px8YezYsTGbN29WxcfHAwCKiopw9dVXSx988EF5UlISr9Cqo13Hz9QafoGKcLzr+Bn2ABMRERHVUdCrQNx333364uJiYd++faWFhYXFhYWFxT/++GOpzWYT7r//fv5uvh7YA0xEREQUfkHPAH/33XfqdevWlXbp0sXb8pCeni6/8sor5cOHD48NbXlNS3OjLqTjiIiIiOh8Qc8Ay7KM6kufAYBGo4Essw24XgJtHmGTCREREVGdBR2Ar776avdDDz2k//3337378f7222/Cww8/rL/mmmvcoS2vaTlV6gjpOCIiIiI6X9AB+NVXX7XbbDbh4osvNnpu7du3N9psNuGVV15hc2o9cBk0IiIiovALuge4bdu2yp49e0q//fZb1cGDB0UASEtLk4cNGyaFvrymhcugEREREYVfndYBFkURGRkZUkZGBkNvCHEZNCIiIqLwC7gF4rvvvlN16tQp1mq1nnesqKgInTt3jv3vf/+rCmVxs2bN0gmCYKp869ixo3elifLyctx77736hIQEo9FoNN1www0xeXl5QuVzHDt2TMjIyIgxGAymFi1aGKdOnapzuSJzN2cug0ZEREQUfgEH4CVLlmjvvvtul9lsPu9YfHw8Jk2a5HrxxRe1Ia0OQOfOneU//vijxHPbtm1bmefYgw8+qP/qq6/UH374YfnGjRtL8/LyhBtvvDHGc9ztdmPkyJEGp9MpbNmypfStt94qf/fddzUzZ86MyHXEjp0qDWgce4CJiIiI6i7gFoh9+/apFi9eXOPyAxkZGe6XXnop5AFYrVajVatW5zUGFBUVYeXKlZp33323fMiQIRIAvPXWW/YuXbrEbtu2TTVgwABp7dq1qkOHDonr168vsVgsCgA89dRTjpkzZ+rnzZvn0Ol852C73Q6H49xbtdlsoX5b55FkBauzf/U7zmLWo3dqQtjrISIiIrpQBTwDfOLECUGj0dTYoapWq5VTp04JNR2vq6NHj4oWi8WYmppqHDt2bMyxY8cEANi5c6fK5XJh6NCh3qXX0tLS5DZt2ijbt29XAcD27dvVXbp0kT3hFwCGDx/uttls2LdvX43vff78+br4+HiT55aSkmIK9fuqLju3EPk2/8ubje2VApUY8k8zERERUZMRcABu1aqVsm/fvhp7fH/44QdVcnJySLdo6Nu3r3vFihXl33zzTdmrr75afuzYMWHgwIGxNpsN+fn5glarRbNmVVdEaNmypZKfny8AQEFBgdCyZcsqNXlq9IzxZdasWY6ioqJiz+3XX38tDuX78iXQvt52zQ1hroSIiIjowhZwC0RGRob7ySef1I0YMcIdExNT5VhZWRmeeuop3YgRI0J6ddmoUaO8q0x0794d/fr1K2vXrp1p9erVGoPBELb90PR6PfT6hu2z5TbIRERERA0j4AA8e/Zsx2effRbbsWNHY2ZmprNTp04yABw8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5c0339Q0dm0AoPnpy5COIyIiIiLfmkQAdjgc2LNnj2rw4MFuz2MqlQrXXnutOysr67w9he12O6xWq/dms9nCXmNqUWCrOwQ6joiIiIh8axIB+OTJk4IkSUhKSqqyzUTLli2VgoKC81og5s+fr4uPjzd5bikpKaZw11jD/hd1HkdEREREvjWJABysWbNmOYqKioo9t19//bU43K+ZE9M3pOOIiIiIyDd1YxfQEFq0aKGoVCpUn+09ceKEUH1WGAD0ej30en3DFQigT+ZyKC+0BuB7NzjPFsl9Mpc3YFVEREREF54mMQOs0+lw+eWXSxs2bPAGfkmSsGnTJnXfvn2lxqzNwxhnQpa2D4BzYdfDcz9L2wfGuLB3YxARERFd0JpEAAaAqVOnOv/1r39p/vWvf2kOHDgg3nPPPfqysjLh7rvvdjV2bR79Zn7rDcHVZWn7oN/Mbxu4IiIiIqILT5NogQCA22+/3X3ixAn7U089pSsoKBC6du0qffXVV2UWiyWirivrN/NblNiKkb08E3Hlv8EW0wa9/74M/TjzS0RERBQSgqIo4V/jK8pZrVbEx8ebrFYr4uLiGrscIiIiIqrGZrPBbDajqKio2Gw21zq2ybRAEBEREREBDMBERERE1MQwABMRERFRk8IATERERERNCgMwERERETUpDMBERERE1KQwABMRERFRk8IATERERERNSpPZCa4+FKViszibjXuGEBEREUUiT07z5LbaMAAHoLi4GADQpk2bRq6EiIiIiGpTXFyM+Pj4WsdwK+QAuN1u5OXlwWg0QhQbpmvEZrMhJSXF9OuvvxZz++Xow69f9OPXMPrxaxjd+PWLfg39NZRlGSUlJbBYLFCra5/j5QxwANRqdaPN/sbFxcHfftYUufj1i378GkY/fg2jG79+0a8hv4bNmjULaBwvgiMiIiKiJoUBmIiIiIiaFAbgCKXT6TBz5kynTqdr7FKoDvj1i378GkY/fg2jG79+0S+Sv4a8CI6IiIiImhTOABMRERFRk8IATERERERNCgMwERERETUpDMBERERE1KQwAEegpUuXatq2bWvU6/WmXr16GXbs2MGvU5SYN2+etmfPnrEmk8nUokUL4+jRo2MOHjzIr1+Umj9/vlYQBNP9998feZcwU41+++034bbbbtMnJCQYY2JiTF26dIn9v//7P/47jBJutxtPPPGErl27dsaYmBjTxRdfbJwzZ45WluXGLo1qsGnTJtWIESNiLBaLURAE08cff1xlozVZljFjxgxdcnKyMSYmxjRo0CDDTz/91Kj/JvkfQoR5//331Y8++qh+1qxZju+//760a9eu8ogRI2Lz8/OFxq6N/Nu8ebM6MzPTuX379tJ169aVuVwuDBs2zFBSUtLYpVGQsrKyxDfffFObnp7O77pRpLCwEFdeeWWsRqPBV199VbZ///6S559/3p6QkKA0dm0UmGeeeUa7fPlyzT/+8Q/7gQMHShYuXGh/8cUXdUuWLNE2dm3kW2lpKbp27Sq//PLLdl/HFy5cqH3ttde0r732mn3Hjh2lsbGxSkZGhqG8vLyhS/XiMmgRplevXoYrrrhCXrZsmR0AJElCmzZtjJMnT3bOmjXL2dj1UXAKCgqE5ORk48aNG8sGDRokNXY9FJji4mL06NEj9pVXXrEvWLBA161bN+nll192NHZd5N+0adN0O3bsUG3btq2ssWuhuhk+fHhMUlKSsnLlSm+YuuGGG2JiYmKU1atX+wxYFDkEQTD95z//Kb/pppvcQMXsb6tWrYwPPfSQ8/HHH3cCQFFREZKTk00rVqwov+OOO9yNUSdngCOIw+HAnj17VIMHD/b+ZVCpVLj22mvdWVlZqsasjerGarUCABITEzn7FEUyMzP1w4cPdw8bNow/tESZL7/8Ut2zZ0/pxhtvjGnRooWxW7duscuWLdM0dl0UuH79+kmbNm1SHzp0SASA3bt3i9u3b1cNHz68UYIS1c8vv/wiFBQUCEOGDPF+/eLj49GrVy9px44djZZt1P6HUEM5efKkIEkSkpKSqoSlli1bKo3dK0PBkyQJDz74oL5fv35S165d+Wv0KPHee++p9+zZo/r+++9LG7sWCt6xY8fE5cuXax944AHnjBkzHNnZ2aqHH35Yr9PpMGHCBFdj10f+zZw502mz2YS0tLRYlUoFSZIwd+5cx1133cUAHIXy8vJEAEhOTj4v2+Tn5zdatmEAJgqTzMxMfU5OjmrLli0MUlHi+PHjwtSpU/XffvttWUxMTGOXQ3UgyzJ69OghLV682AEAV1xxhXzgwAHx9ddf1zAAR4cPPvhA/cEHH2jefffd8vT0dHnPnj2qhx9+WHfRRRcp/BpSqHBWMYK0aNFCUalUKCgoqHLB24kTJ4Tqs8IU2TIzM/Vff/21euPGjaUpKSn82kWJ77//XnXy5EnhiiuuiFWr1Sa1Wm3asmWL6tVXX9Wq1WqT280JqEiXnJysdO7cucpvXDp16iT/9ttv/H4XJR577DH99OnTHXfccYe7W7du8vjx410PPPCA89lnn+VFcFHIYrHIAFD9Yv4TJ04IycnJjfbbUf6HEEF0Oh0uv/xyacOGDd6ZeUmSsGnTJnXfvn3ZixgFZFlGZmamfs2aNeoNGzaUtW/fnuE3igwZMsT9ww8/lO7evdt769Gjhzx27FjX7t27S9Vq/tIs0vXr1086fPhwle9tR44cEVNSUtiGFCXKysogilXjiUqlgqLwv9NodPHFFytJSUnK+vXrvf+BWq1W7Ny5U9WvX79Gyzb83zzCTJ061TlhwoSYK664QurTp4/00ksvacvKyoS7776bv/aJApmZmfoPP/xQ88knn5SZTCblzz//FAAgPj5eMRgMjV0e+REXF4fq/dqxsbFKYmKiwj7u6DB16lTHVVddFfv0009rx44d6/q///s/1YoVK7TLli1rvPWWKCgjR450P/vss7q2bdsq6enp0u7du1VLly7V/vWvf+X3wQhVXFyMyj94/vLLL+KuXbvExMREpV27dsr999/vfPbZZ3UdO3aUL774YnnWrFk6i8Wi3HjjjY32azUugxaBlixZonnxxRd1BQUFQteuXaWlS5c6+vfvzxngKCAIgsnX42+88YZ94sSJ/M87Cg0cONDAZdCiy5o1a9QzZszQHT16VGzbtq380EMPOTMzM/nvL0rYbDbMnDlTt2bNGs3JkycFi8Wi3Hrrra65c+c6dDruSROJNmzYoBo8ePB5szzjxo1zvfvuu3ZZljFr1izdihUrNFarVejXr5+0bNkye6dOnRptYoEBmIiIiIiaFPYAExEREVGTwgBMRERERE0KAzARERERNSkMwERERETUpDAAExEREVGTwgBMRERERE0KAzARERERNSkMwERERETUpDAAExFFiTvvvFM/evTomIZ+3TfffFMjCIJJEATT/fffX+tWXG3btjU+//zz2sr3Pc89c+ZM+IslIgqAurELICKimrfR9pg5c6bz5ZdftiuK0lAlVREXF4eDBw+WGI3GoArIzs4u3bx5s+rWW29t8OBORFQTBmAiogjwxx9/lHg+Xr16tebpp5/WHTx40PuYyWRSTKZaM3JYCYKAVq1aBZ2+k5KSlISEhMZJ7URENWALBBFRBGjVqpXiuZnNZsUTOD03k8l0XgvEwIEDDZMnT9bff//9umbNmplatmxpXLZsmaakpAR33XWX3mQymdq3b2/88ssvVZVf68cffxSHDh1qMBqNppYtWxpvv/12/cmTJ4Vga87PzxdGjBgRExMTY2rXrp3xnXfe4aQKEUUFBmAioii2atUqTWJiopKVlVU6efJk5/3336+/6aabYvr16yd9//33pYMHD3b/9a9/jSktLQUAnDlzBtddd52he/fuUnZ2dunXX39dVlBQIN5yyy1Btyj89a9/1f/+++/i+vXryz766KOyZcuWaesSpImIGhoDMBFRFLvsssukp556ynnppZfKs2bNcur1ejRv3lzJzMx0XXrppfKcOXMchYWFwt69e1UAsHTpUm23bt3kxYsXO9LS0uQrrrhCfuutt8r/97//qQ4dOhTw94RDhw6J3377rXr58uXlAwYMkHr37i2vWLHCXl5eHr43S0QUIvx1FRFRFLvssstkz8dqtRoJCQlKenq697Hk5GQFAE6cOCEAwI8//qjavHmzymg0ntdQ/PPPPwudOnUK6HVzcnJEtVqNXr16eV8rLS1Njo+Pr/ubISJqIAzARERRTKPRVLnATBAEaDQa731RrJjUleWKnFpSUiKMGDHCvXjxYnv1c9XlIjciomjEAExE1IRcfvnl0qeffqpOTU1VKgflYHXu3Fl2u93YuXOn2LdvXxkADh48KBYVFYWqVCKisGEPMBFRE3L//fc7z5w5I/y///f/YrKyssQjR44IX3/9tequu+7Su93ugM/TuXNneciQIdK9994bs337dlV2drY4ceJEfUwMl/slosjHAExE1IS0bt1a2bp1a5kkSRg+fHhst27djFOnTtXHx8crnnaJQK1cubLcYrHI1157reHmm282TJo0ydWiRQu2URBRxBMURbE1dhFERBS53nzzTc20adP0RUVFxXV5/oYNG1SDBw82FBYWFjdr1izU5RERBY0zwERE5JfVaoXRaDQ98sgjumCe17lz59hRo0YZwlUXEVFdcAaYiIhqZbPZkJ+fLwBAs2bNEEybQ25uruByuQAA7du3V1QqlZ9nEBGFHwMwERERETUpbIEgIiIioiaFAZiIiIiImhQGYCIiIiJqUhiAiYiIiKhJYQAmIiIioiaFAZiIiIiImhQGYCIiIiJqUhiAiYiIiKhJ+f9Q08vRVFV3KAAAAABJRU5ErkJggg==\n", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "gas.scope.plot_time_series(('S_ch4','S_IC'))" - ] - }, - { - "cell_type": "markdown", - "id": "ccde4d80", - "metadata": {}, - "source": [ - "### 3.3. Check simulation results: Total VFAs" - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "id": "56f3fad7", - "metadata": {}, - "outputs": [], - "source": [ - "# Total VFAs = 'S_va' + 'S_bu' + 'S_pro' + 'S_ac' (you can change the equations based on your assumption)\n", - "idx_vfa = cmps.indices(['S_va', 'S_bu', 'S_pro', 'S_ac'])\n", - "\n", - "t_stamp = eff.scope.time_series\n", - "\n", - "vfa = eff.scope.record[:,idx_vfa]\n", - "total_vfa = np.sum(vfa, axis=1)" - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "id": "a879f514", - "metadata": { - "scrolled": false - }, - "outputs": [ - { - "data": { - "text/plain": [ - "Text(0, 0.5, 'Total VFA [mg/l]')" - ] - }, - "execution_count": 22, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAkQAAAGwCAYAAABIC3rIAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy81sbWrAAAACXBIWXMAAA9hAAAPYQGoP6dpAABVuklEQVR4nO3deViU9foG8PudhVmYGRZlV9wXQBEXNJdKTc2l1OyopWYe05NamlubUrZoi782j5VWmpWZ6dFKc8lcU8MFxR3cUETZFZhhm/39/YGQpCko8AJzf65rrmp4h3mYdObmuzxfQRRFE4iIiIhcmEzqAoiIiIikxkBERERELo+BiIiIiFweAxERERG5PAYiIiIicnkMREREROTyGIiIiIjI5SmkLqAmsNvtSE1NhU6ng0zGDElERFQTOJ1O5OXlISAgAArF7SMPA1EZpKamIjg4WC91HURERFR+SUlJufXr17/tNQxEZaDXF2Why5cvw2AwSFwNERERlYXJZEL9+vVLPsdvh4GoDARBAAAYDAYGIiIiohqm+HP8drgghoiIiFweAxERERG5PAYiIiIicnkMREREROTyGIiIiIjI5TEQERERkctjICIiIiKXx0BERERELo+BiIiIiFweAxERERG5PAYiIiIicnkMREREROTyGIiIiIhIUln5VpxLz5W0Bp52T0RERFUiO9+Kcxl5OJuei3PpuTibnodzGbm4mmdFYx937JjRXbLaGIiIiIioQhkLbDibkXs9+BQFoLPpebiaZ/nHxzidIhxOEXKZUIWV/oWBiIiIiO6Kwyni4tU8nEox4WSyEfGpRSEoI/efg0+QpwbN/HRo7qdHM9+ifzb11cFdJW0kYSAiIiKiO7LanTibnou4FBNOphhxKsWEuBQTCm2OW14f6KFGMz89mvvprv+zKPjoJA4+/6R6VkVERESSKbDaEZ+ai7gUI04mm3Aq1YgzabmwOcSbrtUo5QgJ0KNVkAdCAwxo7l808qNXKyWo/O4xEBEREbkwY6ENp1KMRSM/yUUjPwmZeXDenH1gUCvQKsgDYYGGkn82qquTbN1PRWIgIiIichGZuRac/Fv4ScoquOW1PnoVWgUaEBbogVZBRf+s56WBINT88HMrDERERES1jCiKSM4pxMlkU9G01/UA9E+Lnet5adAqsPTIj69BXcVVS4uBiIiIqAZzOkVcvJaPk8nGUguecwpsN10rCEDjuu5/TXsFeiA00ABPrZsElVcvDEREREQ1SL7FjqOXc3DwYhYOXcrC0aQc5Ftv3umllAto5qtHq6C/Rn1a+hsk395eXfFVISIiqsau5llwKDEbMYlZOJSYhZMpJjj+tuJZrZQhNKD0ep9mfjqoFHKJqq55GIiIiIiqCVEUkZRVgJjEbMRczELMpSxcyMy/6bogTw0iG3qhQ0NvRDb0RlPf2rHTS0oMRERERBJxOEXEp5pwKDGrKAQlZt1y4XMLPz0iG3khsqE3OjT0RpCnRoJqazcGIiIioipitjlw9HIODiVm4WBiNmIvZSPPYi91jVIuILyeJyIbeiOyoRfaN/DioucqwEBERERUSXIKrEXrfy5lIeZiFk4kG2/q9qxXKdCugRc6NvJGhwZeaFPfE2ol1/5UNQYiIiKiCnIluwCHErNx8PoC6LPpeTdd46tXIbKRNzo29EaHhl5o6W/g+p9qgIGIiIjoLjidIs5m5JYsgD6UmIUUo/mm65r4uF+f/iq61feuvd2eazIGIiIiojK6dC0fu85kYvfZTMQkZsFkLr3+RyETEBbkgcgGXoi8PgVWR6eSqFoqDwYiIiKif2C2OXDwYhZ2nsnAH2cyceFq6S3wWjc52gV7oUNDL3Rs6I2IYE9o3fjRWhPx/xoREdENLmcVYNfZTOw6nYHohGsotP3VBVohE9ChoRe6t/BFlyZ1EBJggFIuk7BaqigMRERE5NKsdidiErOw60wGdp7JxPmM0guh/QwqdG/uix4tfdC1aV3o1UqJKqXKxEBEREQuJyWnELvOZGLXmQz8ef5qqbPA5DIB7YO90L2lD7o390VIgJ6LoF0AAxEREdV6NocThy9ll6wFOp2WW+rrdXUqdG/hgx4tfNGtWV14aDgK5GoYiIiIqFZKN5nxx5lM7DyTgb3nriL3ho7QMgGIqO+JHi180aOlL0IDDJCxF5BLYyAiIqJawe5w4sjlnKK1QKczEZdqKvV1b3c3dG/ugwdb+OCBZj7wcudxGPQXBiIiIqqxMnMt+ONs0Vqg3WczS/UFEgQgvJ4nerTwQfcWvggP8uAoEP0jBiIiIqoxHE4Rx67kYNfpDOw6m4njV4ylvu6pVeKBZj7o0bJoFIhNEamsGIiIiKhas9qd2H02ExuOp+CPs5nILrCV+nrrIA90vz4KFFHfk+eC0V1hICIiomrH4RRx4OI1/HosBZtOpMFY+FcI0qsVeKC5T8l6IF+9WsJKqbZgICIiompBFEWcSDZi3dEU/HosBRm5lpKv+epVeCQ8EH1b+aNdsCcU7A5NFYyBiIiIJHU+Ixfrj6Zg/bEUJF4rKLnfoFagf+sADIwIRKdGdTgVRpVK0ogdFRWlEgRBf+OtefPm7sVfLywsxIQJE9Te3t46nU6nHzx4sCY1NbXU34jExEShb9++Gq1Wq/fx8dFNmzZNZbOVnl/evn27PCIiwl2lUumbNGmiW7JkCTtuERFJKCWnEF/8kYD+C/ag10e78d8d55F4rQAapRyPtgnEV6M7ICaqF957PBxdmtRlGKJKJ/kIUUhIiHPbtm0lvxIolX9llRdeeEG9efNmxapVqwo9PDzEyZMnq4cMGaLZt29fAQDY7XYMGDBA6+fnJ+7Zsyc/NTVVGDNmjEapVGL+/PkWAEhISBAGDhyoHT9+vHXFihW2rVu3yidMmKAODAx09u/f33FTQUREVCmy8q3YeCIVvx5NwcHErJL7FTIBDzb3wcCIQPQK8YO7SvKPJnJBgiiKpjtfVjmioqJU69evVxw/fjz/71/LycmBr6+vfvny5YXDhw+3A0BcXJwsLCzMfe/evQVdu3Z1bNiwQT5o0CDtlStX8gICAkQA+PTTT5WzZ89WZ2Rk5KpUKsyYMUO1efNmRVxcXMlzDB06VJOTkyNs3bq14O/PeytGoxGenp56o9EIg8FQUT8+EVGtl2exY2tcGtYdTcHec1dhd4oAinoEdWzojUERQejXyp9NEqlSmEwmeHh4ICcnJ9fDw+O210oewxMSEmQBAQE6tVqNTp06Od577z1zw4YNxZiYGLnNZkOfPn1KumyFhoY669evL0ZHR8u7du3qiI6OVoSFhTmLwxAA9OvXzz558mScOHFC1qFDB+eBAwfkDz30kP3G5+zTp4995syZ/7gtwWw2w2L5azGfySRZZiQiqnEsdgd2ncnE+mMp2B6fDrPNWfK11kEeGNgmEI+0CUCAh0bCKolKkzQQ3XffffalS5c6WrZs6UxJSRHeeust1QMPPOB+8uTJvLS0NMHNzQ1eXl6lHuPr6yumpaUJAJCeni74+vqKN37d399fBIA7XWMymVBQUACtVntTXXPnzlXNmzePv64QEZWRwyliX8I1rD+WjM0n05B7Q8foxnXdMTAiEAPbBKKxj07CKon+maSB6JFHHilZwxMREYHOnTsXNGzYUL9y5UqlVqsVb/fYyhQVFWV58cUXS4aITCYTgoOD9VLVQ0RUHYmiiKOXc7DuaAo2nkhF5g3b5P0N6pIQFBZogCBwUTRVb5JPmd3Iy8sLTZs2dZ4/f17Wp08fu9VqRXZ2dqlRooyMDKF4FMjPz0+MiYkp9beseGToxmsyMjJuusZgMNxydAgA1Go11Go2+iIiupWz6X9tk0/K+msppqdWif6tAzCoTSAiG3rz3DCqUapVIMrNzcXFixdlAQEBYmRkpEOpVGLr1q2KYcOG2QEgPj5edvnyZaFLly4OAOjSpYv9/fffd0tLSysJSVu2bFEYDAa0atXKCQCdOnVy/PbbbwoAJb+6bNu2TdGxY0fuMCMiKqPLWQX49XgK1h9Nwem03JL7tW5y9An1w8CIQHRr6gM3BRsmUs0kaSCaNm2a6tFHH7U3bNjQmZycLJszZ45KJpOJI0eOtHl6emLMmDG2mTNnqr29vQsNBoM4ZcoUdadOnRxdu3Z1AEDfvn0dLVu2dI4aNUozf/58c1pamjBnzhzVs88+ay0e4Zk0aZJ18eLFbtOnT1eNGzfOtm3bNvnatWsV69evL9MOMyIiV5VrtuGXoyn45UgyDl/KLrlfKRfwYHNfDIoIxEMhvtC6VavfrYnuiqR/iq9cuSIbOXKkJisrS6hbt67YpUsXx759+/L9/PxEAFiwYIF52rRp6mHDhmktFgt69eplX7Rokbn48QqFAhs2bCiYMGGCulu3bu5arVZ86qmnbPPmzSsZDWrSpIm4fv36gunTp6s/++wzt6CgIHHx4sVm9iAiIrq18xl5WL4vEWsOX0G+teitUhCAzo3rYFBEIPqGBcBDy/62VLtI2oeopmAfIiKq7RxOETtOZ+C7fYnYc+5qyf1NfXV4IrI+Hm0TCD8D11ZSzVKj+hAREZF0svOtWH3oMpbvv4Qr2YUAAJkA9Arxw9NdGqJLkzrcIUYugYGIiMgFnUox4rvoS/jlaDIs9qLGiZ5aJZ6IDMbITsGo733rXbhEtRUDERGRi7A5nPjtZBq+25eImMS/FkmHBRrwdJeGGNgmEGqlXMIKiaTDQEREVMtl5Jqx8sBlrDhwCRnXmycqZAL6tQ7AmC4N0C7Yi9Ni5PIYiIiIaiFRFBGblIPv9iVi04lU2BxFzf999CqM6Fg0LebLRdJEJRiIiIhqEbPNgV+PpeC7fZdwItlYcn/7Bl4Y3bkB+rUKYPNEoltgICIiqgWScwrx/f5L+PFgErILbAAAN4UMg9oE4ukuDdEq6PZbjolcHQMREVENJYoi9l24hm+jE7E1Lh3O60diB3lqMOq+BhgeWR/e7m7SFklUQzAQERHVMPkWO34+kozv9iXibHpeyf1dm9bB6M4N0SvED3IerEpULgxEREQ1xMWr+fhuXyLWHLqCXIsdQNHhqo+3q4fRnRugmZ9e4gqJai4GIiKiaszpFPHH2Ux8E52IP85mltzfqK47RndugMfb14NBzXPFiO4VAxERUTVkLLThf9eP1Lh0rQBA0QGrPVr44ukuDXF/07qQcVqMqMIwEBERVSPJOYVYvCsBaw5fQaGt6KR5g1qBYR3q46nODdCgjrvEFRLVTgxERETVQKqxEJ/tPI9VMZdLmii29NdjdOeGGNw2EFo3vl0TVSb+DSMiklCa0YzPd53Hjwcvw+ooOmS1S5M6eL5nU3RuzJPmiaoKAxERkQTSTWZ8vvM8Vt4QhO5r7I2pvZrjvsZ1JK6OyPUwEBERVaEMkxmf70rADweTYLUXBaGODb0xtXczdGlSV+LqiFwXAxERURXIyDVj8a4LWHHgEizXg1CHBl6Y1rs5ujTh1BiR1BiIiIgqUWauBV/8kYDvD1yC2VYUhNoFe2Ja7+bo1rQugxBRNcFARERUCa7lWfDF7gtYvu9Syfb5iPpFQeiBZgxCRNUNAxERUQXKyrfii90J+C76ryDUpp4HpvZuju7NfRiEiKopBiIiogqQnW/Fl3su4NvoRBRYi4JQeD0PTO3VDD1a+DIIEVVzDERERPcgp8CKr/ZcwDd/JiL/ehBqFWTA1Iea46EQBiGimoKBiIjoLhgLbFiy9wKW/ZmIvOsnz4cGGDC1VzP0DvVjECKqYRiIiIjKwVhow9K9F7Fs70XkXg9CLf31mNqrOR4OYxAiqqkYiIiIysBktuHrvRexdO9F5JqLglALPz2m9mqGh8P8efI8UQ3HQEREdBu5ZhuW/ZmIJXsuwHQ9CDX30+GFh5qjXysGIaLagoGIiOgWzDYHvv7zIr744wKMhTYAQFNfHV54qBkGtA5gECKqZRiIiIhuIIoitsal4+2NcbicVQgAaOLjjikPNcMj4YGQMwgR1UoMRERE1yVk5uHNX+Ow+2wmAMDPoMJLD7fE4LZBDEJEtRwDERG5vFyzDQt3nMfXey/C7hShlAsYd39jPN+jKdxVfJskcgX8m05ELsvpFPHL0WS8u/k0MnMtAIAeLXzw+qNhaFTXXeLqiKgqMRARkUs6mWzE6+tOIjYpBwDQsI4Wrz8aip4t/aQtjIgkwUBERC4lK9+K/9tyBj/GJEEUAa2bHM/3bIpnujWCSiGXujwikggDERG5BLvDiRUHkvDh72dK+gkNbBOIV/u3RICHRuLqiEhqDEREVOvtv3ANb6w/hdNpuQCAkAAD3hwYho6NvCWujIiqCwYiIqq1UnIK8c6meGw4ngoA8NAoMbNPczzZMRgKuUzi6oioOmEgIqJax2xzYMmeC/hsZwIKbQ4IAjCiYzBm9mkBL3c3qcsjomqIgYiIag1RFLE9PgNvbYhDUlYBAKBDAy+8MTAMrYI8JK6OiKozBiIiqhUuXO8y/cf1LtO+ehVm9Q/BoIhACAK7TBPR7TEQEVGNlmexY+GOc/h670XYHEVdpp/p1hjP92wKHbtME1EZ8d2CiGokUbzeZXrTaWRc7zLdvYUPXn8kFI19dBJXR0Q1DQMREdU4J5ONmLP+FA5fygYANKijxeuPhKJnS19OjxHRXWEgIqIaIyvfig9+P4OVB4u6TGuUf3WZVivZZZqI7h4DERFVe3aHEz8cTMKHv5+FsdAGAHi0TSBmscs0EVUQBiIiqtZOp5kwY/UxnEoxAQBa+uvxxsAw3Ne4jsSVEVFtwkBERNWSwyniqz0X8NHvZ2F1OOGhUWJGn+YYwS7TRFQJGIiIqNq5dC0fM1Yfw6Hri6Z7hfjinSGt4atXS1wZEdVWDEREVG2IoogfDiZh3sZ4FFgd0KkUeP2RUAztUI+7x4ioUjEQEVG1kG4y46U1x0s6TXdq5I0PhrZBfW+txJURkStgICIiya0/loLXfjkJY6ENbgoZXnq4BcZ2bQSZjKNCRFQ1qs3KxLlz57oJgqCfPHmyqvi+wsJCTJgwQe3t7a3T6XT6wYMHa1JTU0u9QyYmJgp9+/bVaLVavY+Pj27atGkqm81W6ntv375dHhER4a5SqfRNmjTRLVmyRFlFPxYR3UZ2vhXP/xCLKSuPwFhoQ+sgD2yc3A3j7m/MMEREVapaBKL9+/fLlixZ4taqVSvnjfe/8MIL6o0bNypWrVpVuGPHjvzU1FRhyJAhJU1H7HY7BgwYoLVarcKePXvyly1bVrh8+XLl7NmzS0JVQkKCMHDgQG337t3tsbGx+ZMnT7ZMmDBBvWnTJnZxI5LQzjMZePiT3dhwPBVymYAXHmqGnyZ1QTM/vdSlEZELEkRRNElZQG5uLtq1a+f+6aefmufNm6dq06aNY+HChZacnBz4+vrqly9fXjh8+HA7AMTFxcnCwsLc9+7dW9C1a1fHhg0b5IMGDdJeuXIlLyAgQASATz/9VDl79mx1RkZGrkqlwowZM1SbN29WxMXF5Rc/59ChQzU5OTnC1q1bC8pSo9FohKenp95oNMJgMFTOC0HkIvItdszdGI+VB5MAAE183PHRsAi0qe8pbWFEVOuYTCZ4eHggJycn18PD47bXSj5CNHHiRHW/fv3sDz/8sOPG+2NiYuQ2mw19+vSxF98XGhrqrF+/vhgdHS0HgOjoaEVYWJizOAwBQL9+/ewmkwknTpyQAcCBAwfkDz30kP3G792nTx/7wYMH/3GEyGw2w2g0ltxMJkkzI1GtEZOYhX4L9pSEobFdG2HjlPsZhohIcpIuql6xYoXiyJEj8kOHDuX//WtpaWmCm5sbvLy8St3v6+srpqWlCQCQnp4u+Pr6ijd+3d/fXyx+/O2uMZlMKCgogFZ78w6WuXPnqubNm+d2rz8fERUx2xz4eOtZfLnnAkQRCPLU4P+GhqNLk7pSl0ZEBEDCQHTp0iVh2rRp6t9//71Ao6leZxFFRUVZXnzxRUvxf5tMJgQHB3NhA9FdOJlsxPTVR3E2PQ8AMLR9Pbz2aCgMau5tIKLqQ7JAdOjQIXlmZqbQoUMH9+L7HA4H9u7dK1+0aJHbpk2bCqxWK7Kzs0uNEmVkZAjFo0B+fn5iTExMqa0oxSNDN16TkZFx0zUGg+GWo0MAoFaroVazIy7RvbA7nFi0KwELtp+D3Smirs4N7zzWGn3C/KUujYjoJpKtIerdu7f92LFj+bGxsSW3du3aOZ944glbbGxsfseOHR1KpRJbt24tCW3x8fGyy5cvC126dHEAQJcuXeynTp2SFYcgANiyZYvCYDCgeMdap06dHDt27CgV/LZt26bo2LFjqTVLRFRxEjLz8K/F+/Dh1rOwO0U8HOaHLVMfYBgiompLshEig8GA8PDwUtvs3d3dxTp16ojF948ZM8Y2c+ZMtbe3d6HBYBCnTJmi7tSpk6Nr164OAOjbt6+jZcuWzlGjRmnmz59vTktLE+bMmaN69tlnrcUjPJMmTbIuXrzYbfr06apx48bZtm3bJl+7dq1i/fr1ZdphRkRl53SKWL7/Et7dHA+zzQm9WoE3B4bhsbZBPHqDiKq1at2pesGCBeZp06aphw0bprVYLOjVq5d90aJF5uKvKxQKbNiwoWDChAnqbt26uWu1WvGpp56yzZs3r2T9T5MmTcT169cXTJ8+Xf3ZZ5+5BQUFiYsXLzb379+fI0REFSglpxAvrjmGP89fAwB0a1oX8/8VjkDP6rVGkIjoViTvQ1QTsA8R0T8TRRE/H0nGnPWnkGu2Q62UYVb/EIzq1IDdpolIUuXpQ1StR4iIqHq7lmfBrJ9PYMupdABARH1PfDSsDRr76CSujIiofBiIiOiu/H4qDbN+PoGreVYoZAKm9mqGCQ82gUIueb9XIqJyYyAionIxmW1469c4rDl8BQDQwk+PD4e1Qaug2w9HExFVZwxERFRm+xKuYeb/jiE5pxCCAPzn/saY1rs51EqelUxENRsDERHdkSiK+GrPBby3+TScIlDfW4MPh0agYyNvqUsjIqoQDEREdFuFVgdeXnsc64+lAAAeb1cPbw4Kg07Ftw8iqj34jkZE/+hyVgGeXX4YcakmKGQCXn80FE/d14BNFomo1mEgIqJbij5/Fc/9EIvsAhvquLvh85Ht0KlxHanLIiKqFAxERFSKKIr4+s9EvLMpHg6niNZBHvjiqfbsOE1EtRoDERGVMNscePWnE/j5SDIAYEi7ILzzWGvuIiOiWo+BiIgAAMk5hXh2+SGcTDZBLhMwu38I/t21IdcLEZFLYCAiIuy/cA3PrYjFtXwrvLRKfDayHbo0qSt1WUREVYaBiMiFiaKI7/Zdwtsb4mB3iggNMOCLp9qjvrdW6tKIiKpUmQJRRESEe3m+qSAIWL9+fUH9+vXFuyuLiCqb2eZA1C8nS47gGBQRiPeGhEPjxvVCROR6yhSIjh8/LnvhhResOp3ujgFHFEXhgw8+cLNYLPdeHRFVilRjISYsP4xjV4yQCcCr/UIw7v5GXC9ERC6rzFNmL7/8stXf379MIz4LFixwu/uSiKgyxSRmYeL3h3E1zwpPrRKfPtkO3ZpxvRARubYyBaKEhIQ8X1/fMk9/nTx5Mi8oKIjTZUTViCiKWHEgCW+sPwW7U0RLfz2+fKoDgutwvRARUZkCUaNGjcoVbho0aMAwRFSNWOwOzFl3Cj/GXAYADAgPwP/9KxxaN+6rICICyhiIjh49KivrN4yIiHDefTlEVNHSTWZM+P4wjiTlQBCAlx5uiQkPNuZ6ISKiG5QpELVr185dEASI4q0Hfoq/JggCHA5HboVWSER37fClbEz8/jAyci0wqBVYOKIdHmzuI3VZRETVTpnXEFV2IURUsX48mITX1p2EzSGiuZ8OXz7VAQ3rlquDBhGRyyjzGqJZs2apBg8ebOvYsSOnxIiqMavdiTd/PYUVB5IAAH3D/PHBsDbQqbheiIjon5T5HTI5OVkYMGCA1s3NDQMGDLAPHDjQ1rt3b4dKparM+oioHDJyzZj0fSwOXcqGIAAzejfHcz2acr0QEdEdlHmx9LfffmtOS0vLW7FiRaFOpxOnT5+u9vHx0Q8ePFizbNky5bVr1yqzTiK6g6OXczBw4Z84dCkberUCS5/ugOd7NmMYIiIqA0EURdPdPvjUqVOydevWKX799VfFkSNH5B06dHA88sgj9pEjR9pq07EdRqMRnp6eeqPRCIPBIHU5RDdZfegyon45Cavdiaa+Onz5VHs09tFJXRYRkaRMJhM8PDyQk5OT6+Hhcdtr7ykQ3SgtLU0oDkfdunVzvPLKK9aK+L7VAQMRVVc2hxPzNsbjm+hEAEDvUD98NKwN9GqltIUREVUDkgSi2oyBiKqjq3kWPLciFgcuZgEApvZqhik9m0Em4xQZERFQvkBU7m0nAwcO1NxqTYIgCFCr1WjatKlz1KhRtpYtW3I3GlElOXHFiGeXH0KK0QydSoGPh0egd6if1GUREdVYZV5UXczDw0P8448/FEePHpULggBBEHDs2DH5H3/8obDb7Vi9erUiIiLCfffu3fLKKJjI1e08nYGhX0QjxWhG47ru+OW5rgxDRET3qNwjRH5+fuLw4cNtn3/+uVkuL8o8DocDkydPVut0OnH16tWF//nPf9SvvPKKKjo6uqDCKyZyYT/FXsGLa47D4RTxQHMffDqiLQxcL0REdM/KvYaobt26ur179xb8fUrs9OnTsq5du2qvXbuWd+zYMdmDDz7onpOTUyuO8eAaIqoOvtp9AfM2xQMAHmsbhPn/CodSXu5BXiIil1GeNUTlfjd1OBxCfHz8TY+Lj4+XOZ1OAQDUajV7nxBVEFEU8e6m+JIwNK5bI3w4tA3DEBFRBSr3lNmIESOs48ePV58/f97asWNHBwAcPHhQ/v7777uNHDnSCgA7d+6Uh4SEOCq6WCJXY3M48craE1gbewUA8Gq/lnj2wSYSV0VEVPuUOxAtWLDA4uvrK3744YduGRkZAgD4+vqKU6ZMsc6aNcsKAH379rX379/fXtHFErmSAqsdz62Ixc4zmZDLBLw3pDWGdqgvdVlERLXSPfUhMhqNAIA7zcvVdFxDRFUtp8CKf38TgyNJOVArZfhsRDs8FMKdZERE5VGpfYhuVNuDEJEUUnIKMfrrgzifkQcPjRJfj+mA9g28pS6LiKhWK3cgunr1qhAVFaXatWuXPDMzUxBFsdTq6aysrFqxs4xICuczcvHU0oNINZrhb1Dju2c6ormfXuqyiIhqvXIHopEjR6ovXLggGzNmjM3Pz0+UybjThagiHL6UjWe+jUFOgQ1NfNzx3TOdEOSpkbosIiKXUO5AFB0drfjjjz/y27Vrx6M5iCrIztMZmLjiMMw2JyLqe2LZmEh4ubtJXRYRkcsodyBq3ry5s7CwkE2GiCrIjd2nu7fwwecj20Hrdk/L+4iIqJzKPd/12WefFUZFRal27Nghv3r1qmA0GnHjjYjK7qvdFzB99TE4nCIeaxuEr0Z3YBgiIpJAud95vby8YDKZhN69e2tvvF8URQiCAIfDwUXVRHfgdIp477fT+HL3BQBF3adn9Q+BTMbBVyIiKdzNomqNUqkUly9fXujn5yfyiA6i8nE6Rcz+5SRWHkwCwO7TRETVQbkDUVxcnOzw4cP5ISEhXFRNVE5Op4hZP5/AjzGXIROA9x8PZ/dpIqJqoNxriNq1a+dISkrisBBROTmdIl5ee7wkDH00LIJhiIiomij3CNHzzz9vnTp1qnrGjBnW8PBwh5tb6a3BERERHDki+hvH9TC05vAVyATg4+ERGBQRJHVZRER0XbnPMpPJZDe1zRUEoVYvquZZZnQvHE4RL645hp9ikyGXCfhkeAQebRModVlERLVepZ5llpCQkHfXlRG5GIdTxMz/HcPPR4rC0H+faIsB4QFSl0VERH9T7kDUqFEjsTIKIapt7A4nZvzvGNYdTYFCJmDhk23RrzXDEBFRdVSmRdU///yzwmq1lvmb/vrrr4qCgoK7LoqoprM7nJi++q8w9OkIhiEiouqsTIHoX//6lyY7O7vMO8tGjhypSUlJ4U40ckl2hxNTVx3F+mNFYeizke3QtxXDEBFRdVamKTNRFPH000+rVSpVmb6p2Wwu03ULFy5UfvHFF25JSUkyAAgJCXG89tprlkceecQBAIWFhZg2bZp69erVCqvVKvTq1cu+aNEic0BAQMm0XWJiojBhwgT17t27Fe7u7uKoUaNs8+fPtyiVypLn2b59u3zGjBnq+Ph4Wb169cRXX33VMm7cOFuZiiQqB5vDiak/HsXGE6lQygV8NqId+oT5S10WERHdQZkC0ahRo8oVHp544gnbnVZzA0D9+vXFd99919K8eXOnKIpYtmyZcsiQIdrDhw/nt27d2vnCCy+oN2/erFi1alWhh4eHOHnyZPWQIUM0+/btKwAAu92OAQMGaP38/MQ9e/bkp6amCmPGjNEolUrMnz/fAgAJCQnCwIEDtePHj7euWLHCtnXrVvmECRPUgYGBzv79+zvK83MR3Y7N4cSUlUew+WQalHIBi0a2R69QP6nLIiKiMij3tvvK5u3trX/33XfNw4cPt/n6+uqXL19eOHz4cDtQ1CU7LCzMfe/evQVdu3Z1bNiwQT5o0CDtlStX8opHjT799FPl7Nmz1RkZGbkqlQozZsxQbd68WREXF5df/BxDhw7V5OTkCFu3bi3TQiduu6c7sdqdmLwyFltOpcNNLsOiUe3wUAjDEBGRlMqz7b7cnaori91ux4oVKxT5+fno2rWrIyYmRm6z2dCnTx978TWhoaHO+vXri9HR0XIAiI6OVoSFhTlvnELr16+f3WQy4cSJEzIAOHDggPyhhx6y3/hcffr0sR88eFD+T7WYzWYYjcaSm8lUrTIjVTNWuxPP//BXGPriqfYMQ0RENYzkgejYsWMynU6nV6vV+ueee06zZs2awlatWjnT0tIENzc3eHl5lbre19dXTEtLEwAgPT1d8PX1LdUGwN/fXwSAO11jMpnwTzvh5s6dq/L09NQX34KDg29qRkkEFIWhSSti8XtcOtwUMnw5uj16tPSVuiwiIionyQNRy5YtnbGxsXnR0dH548ePt/773/9Wnzx5UtK6oqKiLDk5ObnFt6SkpFrXfZvuncXuwKQVh7EtvigMfTW6A7q3YBgiIqqJyt2YsaKpVCo0b95cBCB27NjRcujQIfknn3ziNnz4cJvVakV2dnapUaKMjAyheBTIz89PjImJKbW9v3hk6MZrMjIybrrGYDBAq9Xesia1Wg21Wl2hPyfVLjaHE5O+j8X20xlQKWRY8nQH3N/MR+qyiIjoLlXYSEx2djY++eQT5Z2vvD1RFGGxWBAZGelQKpXYunVrSWiLj4+XXb58WejSpYsDALp06WI/deqUrDgEAcCWLVsUBoMBrVq1cgJAp06dHDt27CgV/LZt26bo2LEjd5jRXXFeP46jOAwtfTqSYYiIqIa75xGi33//Xb506VK39evXKzQajTh16tQyb9F/8cUXVf3797c3aNDAmZubK3z//ffK3bt3yzdt2mTx9PTEmDFjbDNnzlR7e3sXGgwGccqUKepOnTo5unbt6gCAvn37Olq2bOkcNWqUZv78+ea0tDRhzpw5qmeffdZaPMIzadIk6+LFi92mT5+uGjdunG3btm3ytWvXKtavX89W2lRuoijizV9PlXSgXjyqPbo1qyt1WUREdI/uaoTo0qVLwuuvv+7WsGFDXb9+/bSCIGDNmjUFaWlp5Tr4NTMzUxgzZowmJCRE16tXL+2hQ4fkmzZtKujbt68DABYsWGDu37+/fdiwYdoePXq4+/n5iT/99FNh8eMVCgU2bNhQIJfLxW7durmPHj1aM2rUKNu8efMsxdc0adJEXL9+fcH27dsVbdu2df/kk09UixcvNrMHEd2N/24/j2/3XQIAfDisDRdQExHVEmXuQ2S1WvHTTz8plixZ4hYdHS3v3bu3fcSIEbZRo0Zpjhw5kl88RVUbsQ8RAcB3+xLx+rpTAIA3Hg3FmK6NJK6IiIhupzx9iMo8ZRYUFKRr0aKFc8SIEbbVq1cXeHt7AwBGjRp1b9US1QDrjiZjzvqiMPTCQ80YhoiIapkyT5k5HA5BEAQIggC5/B97GhLVOjvPZGDG6mMQReDpzg0wtVczqUsiIqIKVuZAlJycnDt+/HjrqlWrlAEBAfrBgwdr1qxZoxAEHmpPtdfhS1mY+P1h2J0iBrYJxJxHw8A/80REtU+ZA5FGo8Ho0aPtu3btKjh27Fhey5YtnVOnTlXb7XbMnTtX9dtvv8ntdvudvxFRDXE6zYR/L4uB2eZE9xY++GBoG8hkDENERLXRXe0ya9asmfjee+9ZLl26lLd+/foCi8WCQYMGaf38/HQVXSCRFJKuFWD00oMwme1o38ALi0a2h5tC8sbuRERUScr8Dj9y5Ej1jYecxsbGyhwOBx555BHHzz//XJiUlJT38ssvWyulSqIqlJFrxlNfH0BGrgUt/PT4+ulIaNy4bo6IqDYr87Z7uVyuT05Ozis+EsNgMOhjY2PzmjZtKt7psTUdt927DmOhDU98uR/xqSbU99ZgzYQu8DPwGBciopqoPNvuyzxCJIribf+bqKYrtDow7tsYxKeaUFenwvKxnRiGiIhcBBdFEKHosNbnf4hFTGI29GoFvhvbEQ3ruktdFhERVZFynWV2/SBVESgaIYqPj5fn5eWV6lAdERFRaztWU+3kdIp4ac3xksNavx4TidBATo0SEbmScgWiPn36aG+cKhs0aJAGAARBgCiKEAQBDocjt4JrJKo0oiji7Y1x+PlIMuQyAYtGtUNkQ2+pyyIioipW5kCUkJBQroNbiWqCT3ecx7I/EwEAHwwNR8+WftIWREREkihzIMrNzRXCw8M5HUa1xqqYJHy49SwAYM6joXisbT2JKyIiIqmUeVF1RESEe2RkpHbRokXKG/sREdVE0QlXMfvnkwCA53s0xb95WCsRkUsrcyDasWNHQWhoqPPll19WBwYG6keNGqXetWsXu9VRjXMhMw8Tv48tOZ9sRp/mUpdEREQSK3Mg6t69u+Pbb781p6Sk5H788cfmS5cuyXr27Klt1qyZ+7x589xSUlJ4yBNVezkFVjzz7SEYC21oG+yJ+f8K52GtRERU/j5EOp0O48ePt+3Zs6fg9OnTeUOGDLEvXrzYrWHDhroBAwZoKqNIoopgczgx8ftYXLyajyBPDb58qgPUSg5yEhHRPTZmbN68ufjaa69ZXn31VYtOpxN/++23cm3jJ6oqoiji9XUnse/CNbi7ybF0TAf46FVSl0VERNXEXQeYnTt3yr/++mvlL7/8opTJZHj88cdtzzzzjK0iiyOqKEv3XsTKg5chE4CFI9qipT8bLxIR0V/KFYiuXLkifP3118rly5crExISZPfdd5/jo48+Mj/55JM2nU5XWTUS3ZPt8emYtykeADCrfwh7DRER0U3KHIj69Omj3blzp7xOnTriyJEjbePGjbOFhISwLxFVa/GpJkxZeQSiCDzZMRjPdOP2eiIiulmZA5FSqRRXrVpVOHDgQLtCwaVCVP1l5Jox7ttDyLc60KVJHbw1KIw7yoiI6JbKnGw2btxYWJmFEFUks82B/3x3GMk5hWhc1x2LRraHUn5PewiIiKgW4ycE1TqiKOLFNcdx9HIOPDRKLB0TCQ+tUuqyiIioGmMgolpnwfZz+PVYChQyAYtHtUejuu5Sl0RERNUcAxHVKuuPpeCTbecAAHMHt0LnJnUkroiIiGoCBiKqNWKTsjHzf8cAAP95oDGe6BgscUVERFRTlGlR9c8//1zmxdePPfaY/e7LIbo7yTmF+M93h2G1O9ErxA8v920pdUlERFSDlCnoPP7442U6o0wQBDgcjtx7K4mofPIsdjzzTQyu5lkQEmDAgiciIJdxez0REZVdmQKR0+lkyKFqyeEU8cLKIzidlou6OhWWPN0B7ir2ySIiovLhGiKq0d7bHI/tpzOgUsjw1ej2CPIs02AmERFRKXf1q3ReXh527typuHTpkmC1WkvNTUyfPt1aMaUR3d66o8n4as9FAMCHw9qgbbCXxBUREVFNVe5AdOjQIdkjjzyiLSwsFPLz8+Hl5SVeu3ZN0Gq18PHxERmIqCqcScvFK2tPAACe69EEj4QHSlwRERHVZOWeMps+fbp6wIAB9qysrFyNRoN9+/blX7x4Ma9t27aO+fPnmyujSKIbmcw2TPj+MAptDtzfrC6m924hdUlERFTDlTsQHT9+XD5z5kyrXC6HXC6HxWIRGjRoIL7//vuW2bNnqyqjSKJiTqeIGauP4eLVfAR5arDgibbcUUZERPes3IFIoVCIMlnRw3x8fJyXLl0SAMDT01NMTk7mIm2qVIv+SMDWuHS4yWVYNKodvN3dpC6JiIhqgXKvIWrTpo3z4MGDshYtWjjvv/9+x5w5c1RXr161Ll++3C00NNRRGUUSAcCec5n48PczAIC3BoUhvJ6ntAUREVGtUe4RnXfeecccEBAgXv93i6enJ55//nnN1atXhcWLF3MNEVWK5JxCTFl5BE4ReCKyPo/lICKiClXuEaJOnTo5i//d399f3Lp1a0HFlkRUmtnmwMTvDyO7wIbWQR54Y2CY1CUREVEtU+4Rou7du2uzs7Nvut9oNKJ79+7aCqmK6AZv/noKx68Y4alVYtGodlAr5VKXREREtUy5A9Hu3bvlFovlpm09hYWFwp9//slPKqpQq2KSsPLgZQgC8N8n2qKeFzM3ERFVvDJPmR09erQkPJ06dUqWlpYmFv+33W7H5s2bFYGBgeKtH01UfieuGPHaulMAgJl9WuCB5j4SV0RERLVVmQNRu3bt3AVBgCAI6N27902/pms0Gnz88cdcVE0Vwlhgw8QVh2G1O9ErxA8TH2widUlERFSLlTkQJSQk5ImiiKZNm+r27duX7+vrWzIa5ObmBj8/P1Gh4CnjdO9EUcTLa4/jSnYhgr21+HBYG8jYfJGIiCpRmRNMo0aNRABwOp25lVcOEfD9/kv47VQalHIBn45oCw+NUuqSiIiolrurIZ1z584JH3/8sSo+Pl4GAKGhoY6pU6damzVrxjVEdE9OpRjx9oZ4AMAr/ULYfJGIiKpEuXeZbdq0Sd6qVStdTEyMLDw83BEeHu44ePCgvHXr1rrffvuNu8zoruVZ7Jj8wxFYHU70CvHF2K4NpS6JiIhchCCKoqk8D2jTpo1779697R988IHlxvtnzpyp2rZtm+Lo0aP5FVui9IxGIzw9PfVGoxEGg0HqcmolURQxffUx/HwkGQEeamyacj+8eE4ZERHdA5PJBA8PD+Tk5OR6eHjc9tpyjxCdOXNGNn78eNvf7x83bpzt9OnTPNyV7sqaw1fw85FkyGUC/vtkW4YhIiKqUuUOMHXr1hWPHDly0+OOHDki8/Hx4RoiKrfzGbl4/Xq/oWm9miGyobfEFRERkasp86Lq119/3e3ll1+2jh071jpx4kRNQkKCpWvXrg4A2Lt3r/zDDz9UTZkyxXKn70N0I7PNgedWHEGhzYFuTetiYvemUpdEREQuqMwjRPPmzVPl5uYKb7zxhnXWrFmWzz//3K1nz57anj17ahctWuQWFRVlmTNnjrU8T/7222+7tW/f3l2v1+t9fHx0jz76qKZ451qxwsJCTJgwQe3t7a3T6XT6wYMHa1JTU0s1pUlMTBT69u2r0Wq1eh8fH920adNUNlvpWb3t27fLIyIi3FUqlb5Jkya6JUuWcC93NfDWhjicSc9FXZ0bPhreBnL2GyIiIgmUORCJYtFsmEwmw4svvmhNTk7Oy8nJyc3JyclNTk7OmzFjhlUmK98M3O7duxUTJ060RkdH52/ZsqXAZrPh4Ycf1ubl5ZVc88ILL6g3btyoWLVqVeGOHTvyU1NThSFDhmiKv2632zFgwACt1WoV9uzZk79s2bLC5cuXK2fPnq0qviYhIUEYOHCgtnv37vbY2Nj8yZMnWyZMmKDetGkTd8VJaMPxFPxwIAmCAHw8PAK+erXUJRERkYsq8y4zmUymT01NzfPz86u0dULp6emCv7+/bseOHQU9evRw5OTkwNfXV798+fLC4cOH2wEgLi5OFhYW5r53796Crl27OjZs2CAfNGiQ9sqVK3kBAQEiAHz66afK2bNnqzMyMnJVKhVmzJih2rx5syIuLq5kB9zQoUM1OTk5wtatWwvuVBd3mVW8S9fy8ch/9yLXYsdzPZrgxYdbSl0SERHVMpW2y6xly5Y6b29v/e1u91K40WgEANSpU0cEgJiYGLnNZkOfPn3sxdeEhoY669evL0ZHR8sBIDo6WhEWFuYsDkMA0K9fP7vJZMKJEydkAHDgwAH5Qw89ZL/xufr06WM/ePDgLUeIzGYzjEZjyc1kKldnAroDm8OJKSuPINdiR4cGXpjWq7nUJRERkYsrV6fq1157zeLp6VkpI0QOhwMvvPCCunPnzo7w8HAnAKSlpQlubm7w8vIqda2vr6+YlpYmAEWjSjeeqwYA/v7+YvHjb3eNyWRCQUEBtNrSZ9XOnTtXNW/ePO77riQLd5zHsStGGNQKLHiyLRRydmsgIiJplSsQjRgxwlYcNiraxIkT1XFxcfI9e/ZI3tgxKirK8uKLL5bsmDOZTAgODr6n0S8qEpuUjc92ngcAzH2sNYI8NXd4BBERUeUrcyAShMrb/TNx4kT1pk2bFH/88Ud+cHBwSeDy9/cXrVYrsrOzS40SZWRkCMXBzM/PT4yJiSlVXPHI0I3XZGRk3HSNwWC4aXQIANRqNdRqLvCtaPkWO6atOgqHU8SgiEAMbBModUlEREQA7mKXWUVyOp2YOHGiet26dYrt27cXNGnSpNSTREZGOpRKJbZu3VoS3OLj42WXL18WunTp4gCALl262E+dOiUrDkEAsGXLFoXBYECrVq2cANCpUyfHjh07SoW/bdu2KTp27Oio8B+K/tHcjfG4dK0AgR5qvDWoldTlEBERlSjzCJHT6cyt6CefOHGietWqVcqffvqpQK/XiykpKQIAeHp6ilqtFp6enhgzZoxt5syZam9v70KDwSBOmTJF3alTJ0dxU8i+ffs6WrZs6Rw1apRm/vz55rS0NGHOnDmqZ5991lo8yjNp0iTr4sWL3aZPn64aN26cbdu2bfK1a9cq1q9ff8cdZlQxtsWlY+XBJADAB8PawEPDNlBERFR9lPtw1wp9ckG45bqcr776yjxu3DgbUNSYcdq0aerVq1crLRYLevXqZV+0aJE5MDCwZDTp4sWLwoQJE9R79uxRaLVa8amnnrLNnz/folT+9aG7fft2+fTp09WnT5+WBQUFibNmzbIUP8edcNv9vbmaZ0HfT3bjap4V47o1QtQjoVKXRERELqA82+4lDUQ1BQPR3RNFEeO/O4Rt8Rlo6a/HL891hVrJfphERFT5KvW0e6LyWBVzGdviM+Aml+Hj4REMQ0REVC0xEFGlSbyaj7c2xAEAZvRpjpAAjq4REVH1xEBElcLucGLa6qMosDrQqZE3xt3fWOqSiIiI/hEDEVWKz3cl4EhSDvQqBT4cxlPsiYioemMgogp37HIOFmw/BwB4a3AY6nnd3PySiIioOmEgogpltjkwfXVRN+oBrQMwOCJI6pKIiIjuiIGIKtTH284iITMfPnoV5g5uValHvhAREVUUBiKqMEeSsvHV7gsAgHceaw0vdzeJKyIiIiobBiKqEGabAy+uOQ6nCAyOCETvUD+pSyIiIiozBiKqEAu2n8P5jDzU1akw59EwqcshIiIqFwYiumdHL+fgiz8SAADvPNaKU2VERFTjMBDRPTHbHHjxf8fgFIFBEYHoE+YvdUlERETlxkBE9+S/28/hXEYe6urc8AanyoiIqIZiIKK7djLZiC+u7yqbO5i7yoiIqOZiIKK7Ync48fLa40UNGMMD0LcVp8qIiKjmYiCiu/L1nxdxKsUED42SU2VERFTjMRBRuSVdK8BHW88CAGb3D4GPXiVxRURERPeGgYjKRRRFzPr5BMw2Jzo3roOhHepJXRIREdE9YyCiclkbm4y9569CpZDhnSGteVYZERHVCgxEVGZX8yyYuzEOAPBCr2ZoVNdd4oqIiIgqBgMRldlbv8Yhp8CGkAADxt/fWOpyiIiIKgwDEZXJrjMZWH8sBTIBeP/x1lDK+UeHiIhqD36q0R2ZbQ68vu4UAGBMl0YIr+cpbUFEREQVjIGI7mjRrgQkZRXAz6DCtN7NpC6HiIiowjEQ0W1dvJqPRddPsn/tkVDo1UqJKyIiIqp4DET0j0RRxJz1p2C1O3F/s7oY0DpA6pKIiIgqBQMR/aPNJ9Ow+2wm3OQyvDkwjD2HiIio1mIgolvKs9jx1q9FPYcmdG+Cxj46iSsiIiKqPAxEdEsLtp1FmsmMYG8tJnVvInU5RERElYqBiG5yOs2Er/9MBAC8OSgMaqVc2oKIiIgqGQMRlSKKIuasOwWHU0TfMH/0aOErdUlERESVjoGISvntZBoOXMyCSiFD1CMhUpdDRERUJRiIqITZ5sC8TfEAgGcfbIJ6XlqJKyIiIqoaDERUYsmeC7iSXQh/gxoTHuThrURE5DoYiAgAkGY04/NdRR2pX+3fElo3hcQVERERVR0GIgIAzP/tNAqsDrRv4IWBbQKlLoeIiKhKMRARYpOy8dORZADA64+EsiM1ERG5HAYiF+d0iiUdqf/Vvh7a1PeUtiAiIiIJMBC5uF+OJuPo5Ry4u8nx0sMtpC6HiIhIEgxELqzQ6sD7v50GADzXsyl8DWqJKyIiIpIGA5EL+/rPi0g3WRDkqcHYro2kLoeIiEgyDEQu6lqeBYuub7N/8eEWPK+MiIhcGgORi1q44zzyLHaEBRq4zZ6IiFweA5ELSryaj+/3XwIAzOofApmM2+yJiMi1MRC5oP/bcgZ2p4gHm/uga9O6UpdDREQkOQYiF3Pscg42nkiFIBQd0UFEREQMRC7ng9/PAAAeiwhCS3+DxNUQERFVDwxELuTAhWvYc+4qFDIBU3s1l7ocIiKiaoOByEWIolgyOjQ8sj6C62glroiIiKj6YCByEbvOZiImMRsqhQyTezaTuhwiIqJqhYHIBYiiiA+vjw6N7twA/h48ooOIiOhGkgainTt3yvv3768JCAjQCYKgX7t2reLGrzudTsyaNUvl7++v02g0+h49emjPnDlTquZr167hiSee0BgMBr2np6d+zJgx6tzc3FLPc/ToUVnXrl21arVaX69ePd0777zjVgU/XrWx5VQaTiab4O4mx8TuTaUuh4iIqNqRNBDl5+cjPDzcuXDhQvOtvv7uu++6ff75526ff/65ed++ffnu7u5i3759tYWFhSXXPPnkk9q4uDjZb7/9VrBu3bqCvXv3yseNG6cp/rrRaMTDDz+sDQ4OdsbExOS///775rlz56o+//xzZRX8iJITRRELtp8HAPy7ayN4u7tUFiQiIioTQRRFk9RFAIAgCPo1a9YUPv7443agaHQoMDBQN3XqVOsrr7xiBYCcnBz4+/vrly5dWjhy5Ej7qVOnZK1atXLfv39/fqdOnZwAsHHjRvmjjz6qTUpKyqtXr564cOFC5Zw5c9Spqam5KpUKADBz5kzV+vXrFWfPns0vS21GoxGenp56o9EIg6FmbVXfGpeO8d8dgrubHHtf7gkvBiIiInIRJpMJHh4eyMnJyfXw8LjttdV2DdGFCxeE9PR0oXfv3vbi+zw9PREZGenYt2+fHAD+/PNPuaenJ4rDEAD06dPHIZPJsH//fjkA7N+/X961a1d7cRgCgL59+9rPnTsny8rKuuVzm81mGI3GkpvJVC0yY7mJooj/bj8HABjdpSHDEBER0T+otoEoNTVVBgD+/v7ijff7+vqKaWlpMgBIS0sTfHx8nDd+XalUwsvLS0xNTRWuXyPz8/Mr9T2Kv2fxc/zd3LlzVZ6envriW3BwsL7ifrKqs+tMJk4kG6FRyjGuWyOpyyEiIqq2qm0gklJUVJQlJycnt/iWlJSUe+dHVS9Fa4eKRoee6twAdXSqOzyCiIjIdSnufIk0AgICnEDRKFBQUFDJCE9GRobQpk0bB1A00pOZmVkq1NlsNmRnZwsBAQHi9Wuc6enppY5zT0tLE258jr9Tq9VQq2v21vQ9567i6OUcqBQyjL+/sdTlEBERVWvVdoSocePGop+fn7ht27aS0GY0GhETEyPv3LmzAwC6du3qyMnJwcGDB0t+jm3btsmdTifuu+8+BwDcd999jj///FNhtVpLvvfvv/+uaNasmdPb27sKf6Kq9dnOop1lIzoFw0fP0SEiIqLbkTQQ5ebm4vDhw7LDhw/LAODChQuyw4cPyxITEwWZTIbJkydb33vvPdXPP/+sOHbsmGzUqFGagIAAcciQIXYACAsLc/bu3dvxn//8R7Nv3z7Z7t275VOmTFEPHTrUXq9ePREAnnrqKZtSqRT//e9/q0+cOCH74YcfFJ999pnb1KlTrberrSY7kpSNAxezoJAJ+M8DHB0iIiK6E0mnzA4ePCjv1atXyaFaL730kgqAatSoUbbly5ebX331VWt+fr4wYcIEtdFoFDp37uzYvHlzgUZT0mYIK1euLJg0aZKmT58+7jKZDIMHD7Z9+umnJX2NPD09sWXLloLnnntOHRkZ6V6nTh1x1qxZlkmTJtmq9qetOl/uvgAAGBQRhAAPzR2uJiIiomrTh6g6q0l9iC5ezUfPD3dBFIEtUx9AC/8auUGOiIjontWKPkR0d77acwGiCPRs6cswREREVEYMRLVIZq4Faw5fAQBMeLCJxNUQERHVHAxEtci30Ymw2p1oG+yJyIZeUpdDRERUYzAQ1RIFVjuW778EAHj2gcYQBOEOjyAiIqJiDES1xE+xyTAW2hDsrUXvUH+pyyEiIqpRGIhqAadTxLI/LwIAxnRpCLmMo0NERETlwUBUC+w5fxUJmfnQqRQY2qGe1OUQERHVOAxEtUDx6NDQDvWgVyslroaIiKjmYSCq4S5ezceuM5kQBODpzg2lLoeIiKhGYiCq4VZc31nWvbkPGtZ1l7gaIiKimomBqAYrtDqw+tBlAMBTnRtIXA0REVHNxUBUg/16LAUmsx31vTV4sLmv1OUQERHVWAxENZQoiiWNGEd2asCt9kRERPeAgaiGOpFsxIlkI9wUMgzrUF/qcoiIiGo0BqIaauXBJABA/1b+8HZ3k7gaIiKimo2BqAbKs9ix7mgKAODJjsESV0NERFTzMRDVQOuPpqDA6kBjH3d0bOQtdTlEREQ1HgNRDVQ8XTaiYzBPtSciIqoADEQ1zMnixdRyGYa047llREREFYGBqIYpbsTYJ8yPi6mJiIgqCANRDWK2OfDLkWQAwPBIbrUnIiKqKAxENcjvcekwme0I8tSgS5O6UpdDRERUazAQ1SCrY4qmyx5vX4+dqYmIiCoQA1ENcSW7AH8mXAUADG3PxdREREQViYGohlh7OBmiCHRpUgf1vbVSl0NERFSrMBDVAE6niDWxRdNlQztwdIiIiKiiMRDVAAcTs3A5qxA6lQJ9wwKkLoeIiKjWYSCqAf536AoA4NE2AdC4ySWuhoiIqPZhIKrm8i12bD6ZCgD4FxdTExERVQoGompu44nUooNc67qjXbCX1OUQERHVSgxE1dz/rh/V8a8O9XiQKxERUSVhIKrGEq/mIyYxGzIBGNKW02VERESVhYGoGvvf4aLRofub+cDfQy1xNURERLUXA1E15XCKWHu46CDXYR14kCsREVFlYiCqpnafzUSayQwvrRK9Qn2lLoeIiKhWYyCqplYeTAIADGlXDyoFew8RERFVJgaiaijdZMb20xkAgCciOV1GRERU2RiIqqFVMZfhcIro2NAbzfz0UpdDRERU6zEQVTN2h7NkuuzJThwdIiIiqgoMRNXM73HpSDWaUVfnhv6teZArERFRVWAgqmaW/XkRADCiYzAXUxMREVURBqJq5ODFLMQkZsNNLsPI+xpIXQ4REZHLYCCqRhbuOAeg6NwyPwM7UxMREVUVBqJqIjrhKvacuwq5TMDEB5tIXQ4REZFLYSCqBgqtDrz2y0kAwMhOwajvrZW4IiIiItfCQCQxY4ENL/x4BAmZ+fDVqzCjdwupSyIiInI5CqkLcGVHL+dg2OJ9sDqccJPLsOCJtvDQKqUui4iIyOUwEEkoJEAPlVKGxj7ueGtQK3Rs5C11SURERC6JgUhCKoUc26c/CB+9CoIgSF0OERGRy2Igkpgvt9cTERFJjouqiYiIyOUxEBEREZHLc6lAtGDBAmWDBg10arVaHxkZqd23b59L/fxERER0ay4TCH744QfFSy+9pI6KirIcOnQoPzw83Nm/f3/3tLQ0rmYmIiJycS4TiD7++GO3sWPH2saPH29r1aqV88svvzRrNBpxyZIlNzX+MZvNMBqNJTeTySRFyURERFRFXCIQWSwWHDlyRN6rVy978X1yuRw9e/a079+/X/736+fOnavy9PTUF9+Cg4P1VVsxERERVSWXCESZmZmCw+GAn5+feOP9vr6+Ynp6+k1TZlFRUZacnJzc4ltSUlJu1VVLREREVY19iG5BrVZDrWZ/ICIiIlfhEiNEPj4+olwux99HgzIyMoS/jxoRERGR63GJQKRSqdC2bVvH9u3bS0bEHA4Hdu7cqbjvvvscUtZGRERE0nOZKbNp06ZZx44dq+nQoYOjU6dOjo8//titoKBAeOaZZ2xS10ZERETScplANGLECHtGRob5jTfeUKWnpwvh4eGOjRs3FgQEBHDKjIiIyMUJoiiyyc4dGI1GeHp66o1GIwwGg9TlEBERURmYTCZ4eHggJycn18PD47bXuswI0b0QxaJBJDZoJCIiqjmKP7eLP8dvh4GoDHJzi9oQ1a9fX+JKiIiIqLxyc3Ph6el522s4ZVYGdrsdqamp0Ol0kMkqdmOeyWRCcHCwPikpKZfTcZWHr3PV4OtcNfg6Vx2+1lWjsl5np9OJvLw8BAQEQKG4/RgQR4jKQKFQVProkMFgwJ3mN+ne8XWuGnydqwZf56rD17pqVMbr7OXlVabrXKIPEREREdHtMBARERGRy2MgkphKpcLs2bOtKpVK6lJqNb7OVYOvc9Xg61x1+FpXjerwOnNRNREREbk8jhARERGRy2MgIiIiIpfHQEREREQuj4GIiIiIXB4DkYQWLFigbNCggU6tVusjIyO1+/bt4/+PCvb222+7tW/f3l2v1+t9fHx0jz76qCY+Pp6vcyWbO3eumyAI+smTJ3NrTgW7fPmy8OSTT6q9vb11Go1GHxYW5n7gwAH+ma5Adrsdr776qqphw4Y6jUajb9y4sW7OnDluTqdT6tJqvJ07d8r79++vCQgI0AmCoF+7dm2pBtFOpxOzZs1S+fv76zQajb5Hjx7aM2fOVMmfb/4lksgPP/ygeOmll9RRUVGWQ4cO5YeHhzv79+/vnpaWJkhdW22ye/duxcSJE63R0dH5W7ZsKbDZbHj44Ye1eXl5UpdWa+3fv1+2ZMkSt1atWvHTo4JlZWWhW7du7kqlEhs3biw4efJk3gcffGD29va+88mVVGbvvPOO25dffqn873//az516lTeu+++a/7oo49Un3zyiZvUtdV0+fn5CA8Pdy5cuNB8q6+/++67bp9//rnb559/bt63b1++u7u72LdvX21hYWGl18Zt9xKJjIzUdujQwblo0SIzADgcDtSvX183adIka1RUlFXq+mqr9PR0wd/fX7djx46CHj16OKSup7bJzc1Fu3bt3D/99FPzvHnzVG3atHEsXLjQInVdtcXMmTNV+/btk//5558FUtdSm/Xr10/j5+cnfvPNNyUf2oMHD9ZoNBpx5cqVt/wgp/ITBEG/Zs2awscff9wOFI0OBQYG6qZOnWp95ZVXrACQk5MDf39//dKlSwtHjhxpr8x6OEIkAYvFgiNHjsh79epV8j9XLpejZ8+e9v3798ulrK22MxqNAIA6derwN+pKMHHiRHW/fv3sDz/8MMNmJdiwYYOiffv2jiFDhmh8fHx0bdq0cV+0aJFS6rpqm86dOzt27typOH36tAwAYmNjZdHR0fJ+/fpV6geyq7tw4YKQnp4u9O7du+R19vT0RGRkpGPfvn2V/tnIw10lkJmZKTgcDvj5+ZX6UPb19RWraq7UFTkcDrzwwgvqzp07O8LDwzmdU8FWrFihOHLkiPzQoUP5UtdSWyUmJsq+/PJLtylTplhnzZplOXjwoHz69OlqlUqFsWPH2qSur7aYPXu21WQyCaGhoe5yuRwOhwNvvvmmZfTo0QxElSg1NVUGAP7+/jd9NqalpVX6ZyMDEbmMiRMnquPi4uR79uzhB3YFu3TpkjBt2jT177//XqDRaKQup9ZyOp1o166dY/78+RYA6NChg/PUqVOyL774QslAVHF+/PFHxY8//qhcvnx5YatWrZxHjhyRT58+XRUUFCTyda69OBohAR8fH1EulyM9Pb3UAuqMjAzh76NGVDEmTpyo3rRpk2LHjh35wcHBfI0r2KFDh+SZmZlChw4d3BUKhV6hUOj37Nkj/+yzz9wUCoXebucv1hXB399fDAkJKTW62bJlS+fly5f5Xl6BXn75ZfWLL75oGTlypL1NmzbOMWPG2KZMmWJ97733uKi6EgUEBDgB4O+bizIyMgR/f/9KH9XnXyIJqFQqtG3b1rF9+/aSETqHw4GdO3cq7rvvPq69qEBOpxMTJ05Ur1u3TrF9+/aCJk2aMAxVgt69e9uPHTuWHxsbW3Jr166d84knnrDFxsbmKxQcjK4InTt3dpw9e7bU+/a5c+dkwcHBnAKuQAUFBZDJSn88yuVyiCLfPipT48aNRT8/P3Hbtm0lbxhGoxExMTHyzp07V/pnI9+lJDJt2jTr2LFjNR06dHB06tTJ8fHHH7sVFBQIzzzzDIdjK9DEiRPVq1atUv70008Fer1eTElJEQDA09NT1Gq1UpdXaxgMBvx9XZa7u7tYp04dkeu1Ks60adMs999/v/tbb73l9sQTT9gOHDggX7p0qduiRYsqf0+yCxkwYID9vffeUzVo0EBs1aqVIzY2Vr5gwQK3p59+mu/P9yg3Nxc3hvoLFy7IDh8+LKtTp47YsGFDcfLkydb33ntP1bx5c2fjxo2dUVFRqoCAAHHIkCGVPszMbfcS+uSTT5QfffSRKj09XQgPD3csWLDA0qVLF44QVSBBEPS3uv+rr74yjxs3jm9uleiBBx7Qctt9xVu3bp1i1qxZqoSEBFmDBg2cU6dOtU6cOJF/liuQyWTC7NmzVevWrVNmZmYKAQEB4rBhw2xvvvmmRaVir9F7sX37dnmvXr1u+m101KhRtuXLl5udTieioqJUS5cuVRqNRqFz586ORYsWmVu2bFnpv1gxEBEREZHL4xoiIiIicnkMREREROTyGIiIiIjI5TEQERERkctjICIiIiKXx0BERERELo+BiIiIiFweAxERERG5PAYiIqp2nnrqKfWjjz6qqernXbJkiVIQBL0gCPrJkyfftiVxgwYNdB988EGFHvZZ/Nyenp637LBORJWHZ5kRUZX6p+NUis2ePdu6cOFCs1QHaRoMBsTHx+fpdLoqLyA5OTlv5cqVyrfffpvnQxBVMQYiIqpSycnJecX/vnLlSuVbb72lio+PL7lPr9eLer10AySCICAwMFCSNBYYGCh6eHjwSHUiCXDKjIiqVGBgoFh88/DwEIsDSPFNr9ffNGX2wAMPaCdNmqSePHmyysvLS+/r66tbtGiRMi8vD6NHj1br9Xp9kyZNdBs2bJDf+FzHjx+X9enTR6vT6fS+vr66ESNGqDMzM4Xy1pyWlib0799fo9Fo9A0bNtR99913N/0yOX/+fLewsDB3d3d3fb169XTPPvusOjc3FwCQl5cHg8GgX7VqVanHrV27VuHu7q43mXikJJHUGIiIqEb4/vvvlXXq1BH379+fP2nSJOvkyZPVjz/+uKZz586OQ4cO5ffq1cv+9NNPa/Lz8wEA2dnZeOihh7QRERGOgwcP5m/atKkgPT1dNnTo0HKvTXr66afVV65ckW3btq1g9erVBYsWLXL7e7CSyWRYsGCB+cSJE3nLli0r3Llzp3zmzJlqANDpdBg6dKht2bJlyhsfs2zZMuVjjz1mMxgM9/DKEFFFYCAiohqhdevWjjfeeMPaokULZ1RUlFWtVqNu3brixIkTbS1atHDOmTPHkpWVJRw9elQOAAsWLHBr06aNc/78+ZbQ0FBnhw4dnMuWLSv8448/5KdPny7ze9/p06dlv//+u+LLL78s7Nq1q6Njx47OpUuXmgsLC0tdN3PmTGuvXr0cjRs3Fnv37u14++23LWvWrCkZERo3bpx127ZtiuTkZAEoGnXasmWLYuzYsbYKeomI6B4wEBFRjdC6dWtn8b8rFAp4e3uLrVq1KrnP399fBICMjAwBAI4fPy7fvXu3XKfT6YtvoaGhOgA4f/58mafN4uLiZAqFApGRkSXPFRoa6vT09Cx13ZYtW+Q9evTQBgYG6vR6vX7MmDGarKwsoXjEqnPnzs6QkBBn8SjRd999pwwODha7d+/uuIuXg4gqGAMREdUISqWy1GJjQRCgVP41AyWTFb2dOZ1FuSUvL0/o37+/PTY2Nu/G25kzZ/IqOoRcuHBBGDRokLZ169aO//3vf4UxMTH5CxYsMAOA1WotuW7s2LHW7777TgkA3377rXL06NHW4rqJSFrcZUZEtVLbtm0dP//8s6JRo0bijcGpvEJCQpx2ux0xMTGy++67zwkA8fHxspycnJJrDh06JHc6nfj4448tcnnRuu4ff/zxpvfXp59+2jZr1iz1Rx995Hb69GnZv//9b06XEVUT/NWEiGqlyZMnW7Ozs4Xhw4dr9u/fLzt37pywadMm+ejRo9V2u73M3yckJMTZu3dvx4QJEzTR0dHygwcPysaNG6fWaP5am920aVOnzWbDJ5984nb+/Hnhm2++UX711Vc3NW309vbGoEGD7K+++qrqoYcecgQHB3OLPVE1wUBERLVSvXr1xL179xY4HA7069fPvU2bNrpp06apPT09xfJOU33zzTeFAQEBzp49e2r/9a9/acePH2/z8fEpCTPt2rVzzp8/3/LBBx+4hYeH61asWKGcN2+e5Vbf65lnnrFarVaMHTvWequvE5E0BFEU2QCDiAhFR3fMnDlTnZOTk1tZz/HNN98oZ8yYoUpJSclTqW5uSF0VNRDRzbiGiIjoBkajETqdTv/ss89aP/zww1uO8tyN/Px8pKSkCO+//77buHHjbLcKQzqdTm+326FWqyvqaYmojDhCRER0nclkQlpamgAAXl5euHFa7F5FRUWp3n//fbdu3bo51q9fX3Cr40nOnj0rAIBcLkeTJk24voioCjEQERERkcvjomoiIiJyeQxERERE5PIYiIiIiMjlMRARERGRy2MgIiIiIpfHQEREREQuj4GIiIiIXB4DEREREbm8/wdwP9T6LDL75QAAAABJRU5ErkJggg==\n", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "import matplotlib.pyplot as plt\n", - "plt.plot(t_stamp, total_vfa)\n", - "plt.xlabel(\"Time [day]\")\n", - "plt.ylabel(\"Total VFA [mg/l]\")" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.13" - }, - "varInspector": { - "cols": { - "lenName": 16, - "lenType": 16, - "lenVar": 40 - }, - "kernels_config": { - "python": { - "delete_cmd_postfix": "", - "delete_cmd_prefix": "del ", - "library": "var_list.py", - "varRefreshCmd": "print(var_dic_list())" - }, - "r": { - "delete_cmd_postfix": ") ", - "delete_cmd_prefix": "rm(", - "library": "var_list.r", - "varRefreshCmd": "cat(var_dic_list()) " - } - }, - "types_to_exclude": [ - "module", - "function", - "builtin_function_or_method", - "instance", - "_Feature" - ], - "window_display": false - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/docs/source/tutorials/12_Chlorination.ipynb b/docs/source/tutorials/12_Chlorination.ipynb new file mode 100644 index 00000000..7b416768 --- /dev/null +++ b/docs/source/tutorials/12_Chlorination.ipynb @@ -0,0 +1,1046 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "28c4658c", + "metadata": {}, + "source": [ + "# Process Design Example: Chlorination \n", + "\n", + "- **Prepared by:**\n", + " \n", + " - [Yalin Li](https://qsdsan.readthedocs.io/en/latest/authors/Yalin_Li.html)\n", + " - [Philipp Steiner](https://www.eawag.ch/en/aboutus/portrait/organisation/staff/profile/philipp-steiner/show/)\n", + " - [Eva Reynaert](https://www.eawag.ch/en/aboutus/portrait/organisation/staff/profile/eva-reynaert/show/)\n", + "\n", + "- **Covered topics:**\n", + "\n", + " - [1. Design Algorithms](#s1)\n", + " - [2. Process Algorithms](#s2)\n", + " - [3. Unit Classes](#s3)\n", + " - [4. System, TEA, and LCA](#s4)" + ] + }, + { + "cell_type": "markdown", + "id": "90d4bb2f", + "metadata": {}, + "source": [ + "---\n", + "### Note\n", + "This tutorial is under active development." + ] + }, + { + "cell_type": "markdown", + "id": "903ee36f", + "metadata": {}, + "source": [ + "---" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "8e608f02", + "metadata": {}, + "outputs": [], + "source": [ + "# Add the path to your cloned repos\n", + "import os, sys\n", + "coding_path = os.path.abspath(os.path.join(sys.path[0], '../../../../'))\n", + "for abbr in ('tmo', 'bst', 'qs'):\n", + " sys.path.append(os.path.join(coding_path, abbr))" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "3dc1138e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "This tutorial was made with qsdsan vNone.\n" + ] + } + ], + "source": [ + "import qsdsan as qs\n", + "print(f'This tutorial was made with qsdsan v{qs.__version__}.')" + ] + }, + { + "cell_type": "markdown", + "id": "efa4852f", + "metadata": {}, + "source": [ + "### Summary\n", + "In this example, we will show how we can set up a chlorination process in `QSDsan`, which would include a contact zone, mixing/storage tanks for the chemical sodium hypochlorite (NaOCl) and treated water, and pumps (contact zone, NaOCl dosing, water storage).\n", + "\n", + "The algorithms largely follows the methodoly presented in Jones et al., Life cycle environmental impacts of disinfection technologies used in small drinking water systems. *Environmental Science & Technology*, **2018**, *52* (5), 2998-3007. https://doi.org/10.1021/acs.est.7b04448" + ] + }, + { + "cell_type": "markdown", + "id": "b7f9ccfc", + "metadata": {}, + "source": [ + "## 1. Design Algorithms " + ] + }, + { + "cell_type": "markdown", + "id": "de048b24", + "metadata": {}, + "source": [ + "### 1.1. Contact zone" + ] + }, + { + "cell_type": "markdown", + "id": "ac3a9505", + "metadata": {}, + "source": [ + "In the contact zone, chlorine (in the form of NaOCl) is added and reacts with the influent stream to inactivate microorganims (e.g., viruses, bacteria, protoza). In this example, the contact zone is modeled as a serpentine tubing." + ] + }, + { + "cell_type": "markdown", + "id": "29d2f234", + "metadata": {}, + "source": [ + "To determine the amount of NaOCl to be added, we will need to calculate the CT (concentration$*$time) values required by the inactivation target." + ] + }, + { + "cell_type": "markdown", + "id": "e5e4fdaa", + "metadata": {}, + "source": [ + "Let's assume that we will use the following table from U.S. Environmental Protection Agency to determine the CT (in min-mg/L) for 4-log inactivation of viruses by free chlorine (Table B-2 on Page B-3 in this [Disinfection Profiling and Benchmarking Technical Guidance Manual](https://www.epa.gov/system/files/documents/2022-02/disprof_bench_3rules_final_508.pdf))." + ] + }, + { + "cell_type": "markdown", + "id": "c7f08d0a", + "metadata": {}, + "source": [ + "| Temperature (°C) | pH=6-9 | pH=10 |\n", + "| :-: | :-: | :-: |\n", + "| 0.5 | 12 | 90 |\n", + "| 5 | 8 | 60 |\n", + "| 10 | 6 | 45 |\n", + "| 15 | 4 | 30 |\n", + "| 20 | 3 | 22 |\n", + "| 25 | 2 | 15 |" + ] + }, + { + "cell_type": "markdown", + "id": "11e1d04c", + "metadata": {}, + "source": [ + "With the CT value, the desired contact time $T_{contact}$ can be calculated from the desired residual chlorine concentration $C_{res}$ (see Section 2.1): \n", + "$$\n", + "T_{contact}[min] = \\frac{CT [\\frac{mg*min}{L}]}{C_{res}[\\frac{mg}{L}]}\n", + "$$\n", + " \n", + "To get the required detention time $T_{DT}$, the desired contact time needs to be corrected by a baffling factor (BF) that accounts for potential short-circuiting:\n", + "\n", + "$$\n", + "T_{DT} [min] = \\frac{T_{contact}}{BF} \n", + "$$\n", + "\n", + "A BF value of 0.7 is typical for the serpentine tubing configuration." + ] + }, + { + "cell_type": "markdown", + "id": "c524fc12", + "metadata": {}, + "source": [ + "Dimensions of the serpentine tubing can then be calculated from the $T_{DT}$:\n", + "\n", + "$$\n", + "T_{DT} [min] = \\frac{L_p}{v} = L_p * \\frac{\\pi*{(\\frac{d_p}{2})^2}}{Q} = (AS*d_p) * \\frac{\\pi*{(\\frac{d_p}{2})^2}}{Q}\n", + "$$\n", + "\n", + "where:\n", + "- $L_p$ and $d_p$ are the length and diameter of the pipe (both in m), respectively\n", + "- AS is the aspect ratio as in $\\frac{L_p}{d_p}$, recommended to be ≧160 by the Colorado Department of Public Health and Environment as in page 16 of this [Baffling Factor Guidance Manual](https://www.colorado.gov/pacific/sites/default/files/CDPHE%20Baffling%20Factor%20Guidance%20Manual.pdf)\n", + "- Q and v are the volumetric flow rate and velocity of the influent stream, respectively" + ] + }, + { + "cell_type": "markdown", + "id": "13b2bd02", + "metadata": {}, + "source": [ + "Solve for $d_p$:\n", + "\n", + "$$\n", + "d_p [m] = (\\frac{4T_{DT}*Q}{\\pi*AS})^{1/3}\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "e28c8246", + "metadata": {}, + "source": [ + "Then we can calculate the amount of material needed:\n", + "\n", + "$$\n", + "V_{PVC} [m^3] = \\pi * L_p * ((\\frac{d_p}{2}+t_{pipe})^2 - d_p^2)\n", + "$$\n", + "\n", + "where $t_{pipe}$ is the thickness of the pipe." + ] + }, + { + "cell_type": "markdown", + "id": "a903a2e1", + "metadata": {}, + "source": [ + "### 1.2. Chlorine tank" + ] + }, + { + "cell_type": "markdown", + "id": "01ed4c90", + "metadata": {}, + "source": [ + "A cylindrical tank will be used for the storage of the NaOCl solution. For a certain refill inteval $t_{refill}$, volume of the storage tank for 15 wt% NaOCl solution will be:\n", + " \n", + "$$\n", + "V_{NaOCl_{sol}}[m^3] = \\frac{M_{Cl_2}[\\frac{kg}{hr}]*t_{refill}[day]*24[\\frac{hr}{day}]*\\frac{MW_{NaOCl}}{MW_{Cl_2}}}{0.15*\\rho_{sol}[\\frac{kg}{m^3}]}\n", + "$$\n", + " \n", + "where:\n", + "- $M_{Cl_2}$ is the mass flowrate of $Cl_2$ (can be calculated from $C_{res}$, refer to Section 2.1.)\n", + "- $MW_{Cl_2}$ and $MW_{NaOCl}$ are the molar mass of $Cl_2$ (70.91 $\\frac{g}{mol}$) and NaOCl (74.44 $\\frac{g}{mol}$), respectively\n", + "- $\\rho_{sol}$ is the density of a 15% NaOCl solution (1200 $\\frac{kg}{m^3}$)" + ] + }, + { + "cell_type": "markdown", + "id": "a585fe1b", + "metadata": {}, + "source": [ + "Given that\n", + "\n", + "$$\n", + "V_{NaOCl_{sol}} = \\frac{\\pi}{4}d_{cyl}^2*h_{cyl} = \\frac{\\pi}{4}d_{cyl}^2*AS*d_{cyl} = \\frac{\\pi}{2}d_{cyl}^3\n", + "$$\n", + "\n", + "The diameter of the cylinder tank needed to hold this volume is:\n", + " \n", + "$$\n", + "d_{cyl} = \\sqrt[3]{\\frac{2*V_{NaOCl_{sol}}}{\\pi}}\n", + "$$\n", + "\n", + "The corresponding PVC volume is:\n", + "\n", + "$$\n", + "V_{wall} = \\pi*h_{cyl}*((d_{cyl}+2*t_{cyl})^2-d_{cyl}^2) = \\pi*AS*d_{cyl}*((d_{cyl}+2*t_{cyl})^2-d_{cyl}^2)\n", + "$$\n", + "\n", + "$$\n", + "V_{floor} = \\pi*(d_{cyl}+2*t_{cyl})^2*t_{cyl}\n", + "$$\n", + "\n", + "$$\n", + "V_{PVC} [m^3] = V_{wall}+V_{floor}\n", + "$$\n", + "\n", + "where:\n", + "- $h_{cyl}$, $d_{cyl}$, and $t_{cyl}$ are the height, inner diameter, and wall thickness of the cylindrical tank (all in m), respectively\n", + "- AS is the aspect ratio as in $\\frac{h_cyl}{d_cyl}$" + ] + }, + { + "cell_type": "markdown", + "id": "73664ea6", + "metadata": {}, + "source": [ + "### 1.3. Pumps" + ] + }, + { + "cell_type": "markdown", + "id": "564e9603", + "metadata": {}, + "source": [ + "For the design of the pumps, we will use the general algorithms in the `WWTpump` class in `QSDsan` (despite of the name, the pump algorithms are not limited to wastewater treatment settings)." + ] + }, + { + "cell_type": "markdown", + "id": "3954fc5f", + "metadata": {}, + "source": [ + "## 2. Process Algorithms " + ] + }, + { + "cell_type": "markdown", + "id": "fcd1a1e5", + "metadata": {}, + "source": [ + "### 2.1. Chlorine dose" + ] + }, + { + "cell_type": "markdown", + "id": "c02f398c", + "metadata": {}, + "source": [ + "Based on the following equation to take into account the amount of chlorine lost to reactions with organics (quantified as the total organic carbon, TOC and ultraviolet absorbance, UVA), we can back-calculate $C_0$ using $C_{res}$:\n", + "\n", + "$$\n", + "C_{res} = -0.8404C_0*ln\\frac{C_0}{C_{res}} - 0.404TOC [\\frac{mg_{C}}{L}]*T_{contact}*(\\frac{C_0}{UVA [1/cm]})^{-0.9108} + C_0\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "5f38c936", + "metadata": {}, + "source": [ + "\n", + " \n", + "- Q1: I'm not sure how TOC and UVA are quantified (e.g., units for them in the equation above)? \n", + "- Q2: Are there two solutions of $C_0$ at a certain $C_{res}$? If so, we probably would want to use the lower value.\n", + " - We might want to double-check the results get from ``scipy`` vs. ``flexsolve``\n", + " \n", + "" + ] + }, + { + "cell_type": "markdown", + "id": "c5a87356", + "metadata": {}, + "source": [ + "With $C_0$ solved, we will know how much NaOCl we need to add to achieve the desired CT:\n", + "\n", + "$$\n", + "M_{Cl_2}[\\frac{kg}{hr}] = Q[\\frac{m^3}{hr}]*C_0[\\frac{g}{m^3}]*\\frac{1[kg]}{1000[g]}\n", + "$$\n", + "\n", + "where:\n", + "- $M_{Cl_2}$ is the mass flowrate of $Cl_2$\n", + "- $Q$ is the volumetric flowrate of the influent" + ] + }, + { + "cell_type": "markdown", + "id": "69b8d85f", + "metadata": {}, + "source": [ + "### 2.2. Pumping energy" + ] + }, + { + "cell_type": "markdown", + "id": "87c78ea5", + "metadata": {}, + "source": [ + "Pumping energy can be calculated based on the flow rate and head pressure/loss as:\n", + "\n", + "$$\n", + "P [kW] = \\frac{mgH}{1000\\eta}\n", + "$$\n", + "\n", + "where:\n", + "- $m$ is mass flow rate in $[\\frac{kg}{s}]$\n", + "- $H$ is the head pressure/loss $[m]$\n", + "- $\\eta$ is the typical pump efficiency (set to 60%)" + ] + }, + { + "cell_type": "markdown", + "id": "a6659f39", + "metadata": {}, + "source": [ + "#### 2.2.1. For the contact zone" + ] + }, + { + "cell_type": "markdown", + "id": "6a7a1048", + "metadata": {}, + "source": [ + "In the case of serpentine tubing, head loss is the sum of the major head loss ($H_f$; due to friction) and minor head loss ($H_m$; due to bends in flow):\n", + "\n", + "$$\n", + "H [m] = H_f + H_m\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "2996a35b", + "metadata": {}, + "source": [ + "For the major head loss, the [Hazen-Williams equation](https://en.wikipedia.org/wiki/Hazen%E2%80%93Williams_equation) can be used (coefficients from [here](https://www.engineeringtoolbox.com/hazen-williams-water-d_797.html)):\n", + "\n", + "$$\n", + "H_f = \\frac{0.2083*(\\frac{100*Q}{C})^{1.852}}{100*d_p^{4.8655}} * L_p\n", + "$$\n", + "\n", + "where C is the roughness coefficient and assumed to be 150 for PVC." + ] + }, + { + "cell_type": "markdown", + "id": "7d9d7b46", + "metadata": {}, + "source": [ + "The minor head loss can be calculated as:\n", + "\n", + "$$\n", + "H_m = \\frac{\\epsilon*v^2}{2g} * N_{bend}\n", + "$$\n", + "\n", + "where:\n", + "- $\\epsilon$ is the minor loss coefficient and assumed to be 1.5\n", + "- $N_{bend}$ is the number of bends can be calculated by dividing the total length by the segment length\n", + "\n", + "$N_{bend}$ can be calculated as\n", + "\n", + "$$\n", + "N_{bend} = \\frac{L_p}{L_{seg}}\n", + "$$\n", + "\n", + "and the segment length $L_{seg}$ can be calculated based on the segment length-to-diameter ratio (recommended to be ≦40 by the Colorado Department of Public Health and Environment as in page 15 of this [Baffling Factor Guidance Manual](https://www.colorado.gov/pacific/sites/default/files/CDPHE%20Baffling%20Factor%20Guidance%20Manual.pdf))." + ] + }, + { + "cell_type": "markdown", + "id": "9f0a999c", + "metadata": {}, + "source": [ + "#### 2.2.2. For the storage tank" + ] + }, + { + "cell_type": "markdown", + "id": "aa0e0f0c", + "metadata": {}, + "source": [ + "For the cylindrical storage tank, there is no minor head loss, therefore the total head loss only comes from the friction loss. However, we need to consider head pressure needed for clorine addition, which is assumed to be 70.3 m. Therefore, the total head needed is\n", + "\n", + "$$\n", + "H = H_f + H_p = \\frac{0.2083*(\\frac{100*Q}{C})^{1.852}}{100*d_{cyl}^{4.8655}} * h_{cyl} + 70.3\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "2f3ff298", + "metadata": {}, + "source": [ + "[Back to top](#top)" + ] + }, + { + "cell_type": "markdown", + "id": "b6a928f2", + "metadata": {}, + "source": [ + "## 3. Unit Classes " + ] + }, + { + "cell_type": "markdown", + "id": "d5691410", + "metadata": {}, + "source": [ + "### 3.1. Contact zone" + ] + }, + { + "cell_type": "markdown", + "id": "bbe29d99", + "metadata": {}, + "source": [ + "For the contact zone, we need to create a new class. Check out the tutorials on `SanUnit` ([basic](https://qsdsan.readthedocs.io/en/latest/tutorials/4_SanUnit_basic.html), [advanced](https://qsdsan.readthedocs.io/en/latest/tutorials/5_SanUnit_advanced.html)) for how to make a new `SanUnit` subclass." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "2081965a", + "metadata": {}, + "outputs": [], + "source": [ + "from warnings import warn\n", + "from math import log, pi, ceil\n", + "from flexsolve import IQ_interpolation\n", + "from qsdsan import SanUnit, Construction\n", + "from qsdsan.sanunits import WWTpump\n", + "\n", + "class ContactZone(SanUnit):\n", + " '''\n", + " Contact zone for water disinfection using chlorine (in the form of sodium hypochlorite, NaOCl).\n", + "\n", + " Parameters\n", + " ----------\n", + " ins : Iterable(obj)\n", + " Influent stream, NaOCl (updated upon unit simulation).\n", + " outs : obj\n", + " Disinfected stream.\n", + " target_CT : float\n", + " Desired CT (concentration*time) for microorganism in min-mg/L.\n", + " C_res : float\n", + " Desired residual concentration of disinfectant in mg/L.\n", + " UVA : float\n", + " Disinfection credit from UVA.\n", + " PVC_thickness : float\n", + " Thickness of the PVC material in m.\n", + "\n", + " References\n", + " ----------\n", + " [1] Jones et al., Life cycle environmental impacts of disinfection technologies\n", + " used in small drinking water systems.\n", + " Environmental Science & Technology, 2018, 52 (5), 2998-3007.\n", + " https://doi.org/10.1021/acs.est.7b04448\n", + " [2] Disinfection Profiling and Benchmarking Technical Guidance Manual.\n", + " U.S. Environmental Protection Agency.\n", + " https://www.epa.gov/system/files/documents/2022-02/disprof_bench_3rules_final_508.pdf\n", + " [3] Baffling Factor Guidance Manual.\n", + " Colorado Department of Public Health and Environment.\n", + " https://www.colorado.gov/pacific/sites/default/files/CDPHE%20Baffling%20Factor%20Guidance%20Manual.pdf\n", + "\n", + " Examples\n", + " --------\n", + " Here we will skip this as we will show how to use it later.\n", + " '''\n", + "\n", + " _N_ins = 2 # influent stream, NaOCl solution\n", + " _N_outs = 1 # disinfected water\n", + " baffling_factor = 0.7\n", + " aspect_ratio = 160 # length over diamteter\n", + " segment_L_to_dia = 40 # segment length to diameter ratio\n", + " C = 150 # roughness coefficient\n", + " epsilon = 1.5 # minor loss coefficient\n", + " pump_eff = 0.6\n", + "\n", + " def __init__(self, ID='', ins=None, outs=(), thermo=None, init_with='WasteStream',\n", + " target_CT=4, # based on the table, set default at T=15°C and pH=6-9\n", + " C_res=10, UVA=1, #!!! need to update\n", + " PVC_thickness=0.005,\n", + " **kwargs):\n", + " SanUnit.__init__(self, ID, ins, outs, thermo, init_with)\n", + " self.target_CT = target_CT\n", + " self.C_res= C_res\n", + " self.UVA = UVA\n", + " self.PVC_thickness = PVC_thickness\n", + " for attr, val in kwargs: setattr(self, kwargs)\n", + "\n", + " # To consider LCA impacts from the construction material\n", + " self.construction = (\n", + " Construction('ContactZone_PVC', linked_unit=self,\n", + " item='PVC', quantity_unit='kg'),\n", + " Construction('ContactZone_SS', linked_unit=self,\n", + " item='StainlessSteel', quantity_unit='kg'),\n", + " )\n", + "\n", + " # Pump\n", + " ID = self.ID\n", + " eff = self.outs[0]\n", + " self.pump = WWTpump(\n", + " ID=ID+'_pump', ins=eff.proxy(eff.ID+'_proxy'),\n", + " pump_type='', # use the generic pump algorithm\n", + " N_pump=1, capacity_factor=1, include_pump_cost=True,\n", + " include_building_cost=False, include_OM_cost=False,\n", + " )\n", + "\n", + " # Target function to solve C_0 -->\n", + " #i first thought the found result was not matching another solver,\n", + " # however the equation just has 2 solutions.\n", + " # We'll probably have to discuss with Eva, which solution to use.\n", + " @staticmethod\n", + " def _C_res_at_C_0(C_0, TOC, contact_time, UVA, C_res):\n", + " C_res2 = -0.8404*C_0*log(C_0/C_res) - 0.404*TOC*contact_time*(C_0/UVA)**(-0.9108) + C_0\n", + " return C_res2-C_res\n", + "\n", + " # Implement process algorithms\n", + " def _run(self):\n", + " inf, naocl = self.ins\n", + " eff, = self.outs\n", + "\n", + " # Calculate contact time and C_0\n", + " TOC = inf.TOC # in mg/L\n", + " UVA = self.UVA\n", + " C_res = self.C_res\n", + " contact_time = self.target_CT / self.C_res\n", + " try:\n", + " C_0 = IQ_interpolation( # in mg/L\n", + " f=self._C_res_at_C_0, x0=C_res, x1=100*C_res, # assume that C_0 won't be >100X of C_res\n", + " ytol=1e-6, args=(TOC, contact_time, UVA, C_res),\n", + " checkbounds=False)\n", + " except:\n", + " warn('Could not find C_0 for the specified values of TOC, contact_time, UVA and C_res.'\n", + " 'C_0 is assumed to be the same as C_res, resullts may be faulty!')\n", + " C_0 = C_res # assumed\n", + "\n", + " C_naocl = C_0/70.91*74.44 # 1-to-1 molar conversion of C_0 (for Cl2) to NaOCl\n", + " naocl.imass['NaOCl'] = m_naocl = inf.F_vol * C_naocl / 1000 # m3*mg/L/1000 = kg\n", + " naocl.imass['Water'] = m_naocl/0.15 - m_naocl\n", + "\n", + " eff.mix_from(self.ins)\n", + " eff.imass['NaOCl'] *= C_res/C_0 # account for the consumed NaOCl\n", + "\n", + " _units = { # units of measure for the design parameters\n", + " 'Pipe diameter': 'm',\n", + " 'Pipe length': 'm',\n", + " 'Total PVC': 'm3',\n", + " 'Pump head': 'm',\n", + " 'Pump stainless steel': 'kg',\n", + " }\n", + "\n", + " # Implement design algorithms\n", + " def _design(self):\n", + " D = self.design_results\n", + "\n", + " # Pipe dimensions\n", + " contact_time=self.target_CT / self.C_res\n", + " t_DT = contact_time / self.baffling_factor # theoretical detention time\n", + " Q = self.F_vol_in # m3/hr\n", + " t_PVC, AS, C = self.PVC_thickness, self.aspect_ratio, self.C\n", + " dia = (4*t_DT*Q/(pi*AS))**(1/3)\n", + " dia_out = dia + 2*t_PVC\n", + " D['Pipe diameter'] = dia\n", + " L_p = D['Pipe length'] = dia * AS\n", + " V_PVC = D['Total PVC'] = pi * L_p * ((dia_out/2)**2-dia**2)\n", + "\n", + " # Pump head\n", + " H_f = 0.2083*(100*Q/C)**1.852/(100*dia**4.8655)*L_p # m\n", + " v = Q/(pi*dia**2)\n", + " N_bend = ceil(L_p/(dia*self.segment_L_to_dia))\n", + " H_m = self.epsilon*v**2/(2*9.81)*N_bend\n", + " H = D['Pump head'] = H_f + H_m\n", + "\n", + " # Pump\n", + " pump = self.pump\n", + " pump.simulate()\n", + " m_ss = D['Pump stainless steel'] = pump.design_results['Pump stainless steel']\n", + " self.power_utility.rate = self.F_mass_in*9.81*H/(1000*self.pump_eff)\n", + "\n", + " #!!! Will need CAPEX/OPEX/impacts of the UVA lights as well\n", + "\n", + " # Construction materials for TEA/LCA\n", + " self.construction[0].quantity = V_PVC\n", + " self.construction[1].quantity = m_ss\n", + " self.add_construction(add_cost=True) # this will add PVC and SS cost\n", + "\n", + " _F_BM_default = {\n", + " 'PVC': 1,\n", + " 'StainlessSteel': 1,\n", + " 'Pump': 1.18*(1+0.007/100),\n", + " }\n", + " def _cost(self):\n", + " C = self.baseline_purchase_costs\n", + " C['Pump'] = self.pump.baseline_purchase_costs['Pump']" + ] + }, + { + "cell_type": "markdown", + "id": "d336dead", + "metadata": {}, + "source": [ + "### 3.2. ChlorineTank" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "1af670fd", + "metadata": {}, + "outputs": [], + "source": [ + "from qsdsan.sanunits import MixTank\n", + "\n", + "class ChlorineTank(MixTank):\n", + " '''\n", + " A subclass of `MixTank` with an auxiliary pump for chlorine storage.\n", + "\n", + " Parameters\n", + " ----------\n", + " ins : Iterable(obj)\n", + " NaOCl, water.\n", + " outs : obj\n", + " NaOCl solution.\n", + " t_refill : float\n", + " Tank refill interval in d.\n", + " head_pressure : float\n", + " Assumed head pressure for the pump in m.\n", + " PVC_thickness : float\n", + " Thickness of the PVC material in m.\n", + "\n", + " See Also\n", + " --------\n", + " `qsdsan.sanunits.MixTank `_\n", + " '''\n", + "\n", + " aspect_ratio = 2 # height over diameter\n", + " C = 150 # roughness coefficient\n", + " pump_eff = 0.6\n", + "\n", + " def __init__(self, ID='', ins=None, outs=(), thermo=None, init_with='WasteStream',\n", + " t_refill=7, head_pressure=70.3, PVC_thickness=0.02, **kwargs):\n", + " MixTank.__init__(self, ID, ins, outs, thermo)\n", + " self.head_pressure = head_pressure\n", + " self.PVC_thickness = PVC_thickness\n", + " for attr, val in kwargs: setattr(self, kwargs)\n", + "\n", + " # To consider LCA impacts from the construction material\n", + " self.construction = (\n", + " Construction('ChlorineTank_PVC', linked_unit=self,\n", + " item='PVC', quantity_unit='kg'),\n", + " Construction('ChlorineTank_SS', linked_unit=self,\n", + " item='StainlessSteel', quantity_unit='kg'),\n", + " )\n", + " eff = self.outs[0]\n", + " self.pump = WWTpump(\n", + " ID=self.ID+'_pump', ins=eff.proxy(eff.ID+'_proxy'),\n", + " pump_type='', # use the generic pump algorithm\n", + " N_pump=1, capacity_factor=1, include_pump_cost=True,\n", + " include_building_cost=False, include_OM_cost=False,\n", + " )\n", + "\n", + " def _run(self):\n", + " naocl, water = self.ins # NaOCl dose will be adjusted when assesmbling the system\n", + " eff = self.outs[0]\n", + " naocl.copy_flow(eff, IDs=('NaOCl',))\n", + " water.copy_flow(eff, IDs=('Water',))\n", + "\n", + "\n", + " _units = { # units of measure for the design parameters\n", + " 'Tank diameter': 'm',\n", + " 'Tank height': 'm',\n", + " 'Total PVC': 'm3',\n", + " 'Pump head': 'm',\n", + " 'Pump stainless steel': 'kg',\n", + " }\n", + " def _design(self):\n", + " MixTank._design(self)\n", + " D = self.design_results\n", + " eff = self.outs[0]\n", + "\n", + " # Cylindrical tank\n", + " V_naocl = self.ins[0].F_vol #!!! the simulated density is ~1.1 g/mL, want to use 1.2?\n", + " AS, t_PVC = self.aspect_ratio, self.PVC_thickness\n", + " dia = 2*((V_naocl/(pi*AS))**(1/3))\n", + " dia_out = dia + 2*self.PVC_thickness\n", + "\n", + " D['Tank diameter'] = dia\n", + " h_cyl = D['Tank height'] = dia * AS\n", + " V_wall = pi*h_cyl*(dia_out**2-dia**2)\n", + " V_floor = pi * dia_out**2 * t_PVC\n", + " V_PVC = D['Total PVC'] = V_wall + V_floor\n", + "\n", + " # Pump\n", + " Q = eff.F_vol\n", + " C = self.C\n", + " pump = self.pump\n", + " H_f = 0.2083*(100*Q/C)**1.852/(100*dia**4.8655) * h_cyl # m\n", + " H_p = self.head_pressure\n", + " D['Pump head'] = H_f + H_p\n", + " pump.simulate()\n", + " m_ss = D['Pump stainless steel'] = pump.design_results['Pump stainless steel']\n", + "\n", + " # # This is if want to use the default algorithms for calculating electricity usage,\n", + " # # it's more conservative (i.e., the efficiency is lower)\n", + " # pump._H_f = H_f * 3.28 # ft\n", + " # pump._H_p = H_p * 3.28 # ft\n", + "\n", + " # Construction materials for TEA/LCA\n", + " self.construction[0].quantity = V_PVC\n", + " self.construction[1].quantity = m_ss\n", + " self.add_construction(add_cost=True) # this will add PVC and SS cost\n", + "\n", + " _F_BM_default = {\n", + " 'PVC': 1,\n", + " 'StainlessSteel': 1,\n", + " 'Pump': 1.18*(1+0.007/100),\n", + " }\n", + " def _cost(self):\n", + " MixTank._cost(self) #!!! this will also add the cost for a stainless steel tank\n", + " pump = self.pump\n", + " self.baseline_purchase_costs['Pump'] = pump.baseline_purchase_costs['Pump']\n", + " H = self.design_results['Pump head']\n", + " self.power_utility.rate += self.F_mass_in*9.81*H/(1000*self.pump_eff)" + ] + }, + { + "cell_type": "markdown", + "id": "476ac60d", + "metadata": {}, + "source": [ + "\n", + " \n", + "- Q3: Density of the simulated NaOCl solution is ~1.1 g/mL instead of 1.2, which will make the design more conservative (since volume is larger), do we want to stick to the 1.2?\n", + " - Related, when calculating tank volume, we typically considers a \"working volume\" factor (<1, our default is 0.8) since we don't want to fill the tank 100% full. So the actual volume will be $\\frac{V_{calculated}}{factor}$, do we want to do the same for this storage tank?\n", + " \n", + "" + ] + }, + { + "cell_type": "markdown", + "id": "62d9849e", + "metadata": {}, + "source": [ + "## 4. System, TEA, and LCA " + ] + }, + { + "cell_type": "markdown", + "id": "650a5f5c", + "metadata": {}, + "source": [ + "Finally it's time to create and simulate the entire system." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "68423ea8", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/yalinli_cabbi/Library/CloudStorage/OneDrive-Personal/Coding/bst/biosteam/_unit.py:635: RuntimeWarning: the purchase cost item, 'Tanks', has no defined bare-module factor in the 'ChlorineTank.F_BM' dictionary; bare-module factor now has a default value of 1\n", + " warn(warning)\n" + ] + } + ], + "source": [ + "# Identify the components needed for simulation\n", + "import qsdsan as qs\n", + "from qsdsan import Component, Components, set_thermo, WasteStream, \\\n", + " System, SimpleTEA, ImpactIndicator, ImpactItem, StreamImpactItem, LCA\n", + "\n", + "# Set up components to be used in simulation\n", + "kwargs = {\n", + " 'phase': 'l',\n", + " 'particle_size': 'Soluble',\n", + " 'degradability': 'Undegradable',\n", + " 'organic': False,\n", + "}\n", + "H2O = Component('H2O', **kwargs)\n", + "\n", + "kwargs['phase'] = 's'\n", + "kwargs['particle_size'] = 'Particulate'\n", + "NaOCl = Component('NaOCl', **kwargs)\n", + "NaOCl.copy_models_from(qs.Component('HOCl', **kwargs), ['V']) # this gives a rho of ~1.1 g/mL for 15 wt% solution\n", + "\n", + "cmps = Components([H2O, NaOCl])\n", + "cmps.compile()\n", + "cmps.set_alias('H2O', 'Water')\n", + "set_thermo(cmps)\n", + "\n", + "# # Redundant codes, remove after module done\n", + "# HCl = Component('HCl', **kwargs)\n", + "# HOCl = Component('HOCl', **kwargs)\n", + "# NH3 = Component('NH3', **kwargs) # assumed to be liquefied NH3\n", + "# cmps = Components([H2O, NaOCl, HCl, HOCl, NH3])\n", + "# cmps.set_alias('NH3', 'Ammonia')\n", + "# s = WasteStream(Water=85, NaOCl=15, units='kg/hr')\n", + "\n", + "\n", + "# Impact items for LCA, values all made-up now\n", + "GWP = ImpactIndicator('GWP', unit='kg CO2')\n", + "PVC = ImpactItem('PVC', GWP=1, price=1)\n", + "StainlessSteel = ImpactItem('StainlessSteel', GWP=5, price=5)\n", + "NaOCl_item = StreamImpactItem('naocl_item', GWP=2)\n", + "e_item = ImpactItem('e_item', functional_unit='kWh', GWP=1.1)\n", + "\n", + "# Streams\n", + "influent = WasteStream('influent', Water=100, units='kg/hr') # an assumed fake stream\n", + "naocl = WasteStream('naocl', price=1, stream_impact_item=NaOCl_item, units='kg/hr') # price is made-up\n", + "water = WasteStream('water')\n", + "disinfected = WasteStream('disinfected')\n", + "\n", + "U1 = ContactZone('U1', ins=(influent, 'naocl_solution'), outs=disinfected)\n", + "U2 = ChlorineTank('U2', ins=(naocl, water), outs=1-U1)\n", + "\n", + "sys = System('sys', path=(U1, U2))\n", + "sys.simulate()\n", + "\n", + "tea = SimpleTEA(sys, discount_rate=0.5, income_tax=0.3, lifetime=10)\n", + "\n", + "get_e_item_quantity = lambda: (sys.get_electricity_consumption()-sys.get_electricity_production())*tea.lifetime\n", + "lca = LCA(sys, lifetime=tea.lifetime, e_item=get_e_item_quantity)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "2fd30f57", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "450.8329582699148" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# TEA results\n", + "def get_price():\n", + " price = tea.solve_price(disinfected)\n", + " price = price*disinfected.F_mass/disinfected.F_vol # per m3\n", + " return price\n", + "\n", + "get_price()" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "72440dfe", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "37102.10326916433" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# LCA results\n", + "def get_impact():\n", + " impact = lca.get_total_impacts(time=1)['GWP'] # per hour\n", + " impact = impact/disinfected.F_vol # per m3\n", + " return impact\n", + "\n", + "get_impact()" + ] + }, + { + "cell_type": "markdown", + "id": "8bdf18d1", + "metadata": {}, + "source": [ + "\n", + " \n", + "We need the following data for TEA/LCA (below are ones I can think of now, there might be more)\n", + "- Lifetime of the equipment and TEA/LCA\n", + "- TEA\n", + " - Costs of the unit. If we don't have the cost for the entire unit, we can calculate based on the materials (and we would need the unit costs of PVC/stainless steel), as well as the UVA lights\n", + " - Costs of NaOCl (pure vs. solution?)\n", + " - Electricity usage of UVA lights\n", + " - Other assumptions like discount rate, income tax, etc.\n", + "- LCA\n", + " - Life cycle inventory assessment method (e.g., ReCiPe) with the corresponding characterization factors for materials (PVC, stainless steel, UVA lights), chemicals (NaOCl), and electricity. I only used GWP here as an example, we can do any number of LCIA methods/indicators you like.\n", + " \n", + "" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "33c60ab6", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a83ecc29", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "df2274a3", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "257b7c8a", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a592d8a3", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7e7599b4", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "275115c2", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "9d9a485a", + "metadata": {}, + "source": [ + "[Back to top](#top)" + ] + } + ], + "metadata": { + "interpreter": { + "hash": "5c4384bbfe0fafd87c455cafafefa588d87617773c75dc9eb96f43c39a856362" + }, + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.12" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/source/tutorials/1_Helpful_Basics.ipynb b/docs/source/tutorials/1_Helpful_Basics.ipynb index 1f4b8afa..81bc154b 100644 --- a/docs/source/tutorials/1_Helpful_Basics.ipynb +++ b/docs/source/tutorials/1_Helpful_Basics.ipynb @@ -9,8 +9,8 @@ "\n", "- **Prepared by:**\n", " \n", - " - [Yalin Li](https://qsdsan.readthedocs.io/en/latest/CONTRIBUTING.html)\n", - " - [Joy Zhang](https://qsdsan.readthedocs.io/en/latest/CONTRIBUTING.html)\n", + " - [Yalin Li](https://qsdsan.readthedocs.io/en/latest/authors/Yalin_Li.html)\n", + " - [Joy Zhang](https://qsdsan.readthedocs.io/en/latest/authors/Joy_Zhang.html)\n", " \n", "- **Covered topics:**\n", "\n", @@ -19,9 +19,9 @@ " \n", "- **Video demo:**\n", "\n", - " - [Yalin Li](https://qsdsan.readthedocs.io/en/latest/CONTRIBUTING.html)\n", + " - [Yalin Li](https://qsdsan.readthedocs.io/en/latest/authors/Yalin_Li.html)\n", " \n", - "To run tutorials in your browser, go to this [Binder page](https://mybinder.org/v2/gh/QSD-Group/QSDsan-env/main?urlpath=git-pull%3Frepo%3Dhttps%253A%252F%252Fgithub.com%252FQSD-group%252FQSDsan%26urlpath%3Dtree%252FQSDsan%252Fdocs%252Fsource%252Ftutorials%26branch%3Dmain).\n", + "To run tutorials in your browser, go to this [Binder page](https://mybinder.org/v2/gh/QSD-Group/QSDsan/main?filepath=%2Fdocs%2Fsource%2Ftutorials).\n", " \n", "You can also watch a video demo on [YouTube](https://www.youtube.com/watch?v=g8mXWycdi4E) (subscriptions & likes appreciated!)." ] @@ -690,36 +690,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" - }, - "varInspector": { - "cols": { - "lenName": 16, - "lenType": 16, - "lenVar": 40 - }, - "kernels_config": { - "python": { - "delete_cmd_postfix": "", - "delete_cmd_prefix": "del ", - "library": "var_list.py", - "varRefreshCmd": "print(var_dic_list())" - }, - "r": { - "delete_cmd_postfix": ") ", - "delete_cmd_prefix": "rm(", - "library": "var_list.r", - "varRefreshCmd": "cat(var_dic_list()) " - } - }, - "types_to_exclude": [ - "module", - "function", - "builtin_function_or_method", - "instance", - "_Feature" - ], - "window_display": false + "version": "3.8.12" } }, "nbformat": 4, diff --git a/docs/source/tutorials/2_Component.ipynb b/docs/source/tutorials/2_Component.ipynb index 078db8c2..3e667b23 100644 --- a/docs/source/tutorials/2_Component.ipynb +++ b/docs/source/tutorials/2_Component.ipynb @@ -8,8 +8,8 @@ "\n", "- **Prepared by:**\n", " \n", - " - [Yalin Li](https://qsdsan.readthedocs.io/en/latest/CONTRIBUTING.html)\n", - " - [Joy Zhang](https://qsdsan.readthedocs.io/en/latest/CONTRIBUTING.html)\n", + " - [Yalin Li](https://qsdsan.readthedocs.io/en/latest/authors/Yalin_Li.html)\n", + " - [Joy Zhang](https://qsdsan.readthedocs.io/en/latest/authors/Joy_Zhang.html)\n", "\n", "- **Covered topics:**\n", "\n", @@ -22,7 +22,7 @@ "\n", " - [Tori Morgan](https://qsdsan.readthedocs.io/en/latest/authors/Tori_Morgan.html)\n", " \n", - "To run tutorials in your browser, go to this [Binder page](https://mybinder.org/v2/gh/QSD-Group/QSDsan-env/main?urlpath=git-pull%3Frepo%3Dhttps%253A%252F%252Fgithub.com%252FQSD-group%252FQSDsan%26urlpath%3Dtree%252FQSDsan%252Fdocs%252Fsource%252Ftutorials%26branch%3Dmain).\n", + "To run tutorials in your browser, go to this [Binder page](https://mybinder.org/v2/gh/QSD-Group/QSDsan/main?filepath=%2Fdocs%2Fsource%2Ftutorials).\n", "\n", "You can also watch a video demo on [YouTube](https://www.youtube.com/watch?v=1OlGsjbqUX8) (subscriptions & likes appreciated!)." ] @@ -1575,35 +1575,6 @@ "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.13" - }, - "varInspector": { - "cols": { - "lenName": 16, - "lenType": 16, - "lenVar": 40 - }, - "kernels_config": { - "python": { - "delete_cmd_postfix": "", - "delete_cmd_prefix": "del ", - "library": "var_list.py", - "varRefreshCmd": "print(var_dic_list())" - }, - "r": { - "delete_cmd_postfix": ") ", - "delete_cmd_prefix": "rm(", - "library": "var_list.r", - "varRefreshCmd": "cat(var_dic_list()) " - } - }, - "types_to_exclude": [ - "module", - "function", - "builtin_function_or_method", - "instance", - "_Feature" - ], - "window_display": false } }, "nbformat": 4, diff --git a/docs/source/tutorials/3_WasteStream.ipynb b/docs/source/tutorials/3_WasteStream.ipynb index 8434a853..6fb337ee 100644 --- a/docs/source/tutorials/3_WasteStream.ipynb +++ b/docs/source/tutorials/3_WasteStream.ipynb @@ -8,8 +8,8 @@ "\n", "- **Prepared by:**\n", " \n", - " - [Yalin Li](https://qsdsan.readthedocs.io/en/latest/CONTRIBUTING.html)\n", - " - [Joy Zhang](https://qsdsan.readthedocs.io/en/latest/CONTRIBUTING.html)\n", + " - [Yalin Li](https://qsdsan.readthedocs.io/en/latest/authors/Yalin_Li.html)\n", + " - [Joy Zhang](https://qsdsan.readthedocs.io/en/latest/authors/Joy_Zhang.html)\n", "\n", "- **Covered topics:**\n", "\n", @@ -20,7 +20,7 @@ "\n", " - [Hannah Lohman](https://qsdsan.readthedocs.io/en/latest/authors/Hannah_Lohman.html)\n", " \n", - "To run tutorials in your browser, go to this [Binder page](https://mybinder.org/v2/gh/QSD-Group/QSDsan-env/main?urlpath=git-pull%3Frepo%3Dhttps%253A%252F%252Fgithub.com%252FQSD-group%252FQSDsan%26urlpath%3Dtree%252FQSDsan%252Fdocs%252Fsource%252Ftutorials%26branch%3Dmain).\n", + "To run tutorials in your browser, go to this [Binder page](https://mybinder.org/v2/gh/QSD-Group/QSDsan/main?filepath=%2Fdocs%2Fsource%2Ftutorials).\n", "\n", "You can also watch a video demo on [YouTube](https://youtu.be/yCOZ0F6E1Sw) (subscriptions & likes appreciated!)." ] @@ -1012,36 +1012,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" - }, - "varInspector": { - "cols": { - "lenName": 16, - "lenType": 16, - "lenVar": 40 - }, - "kernels_config": { - "python": { - "delete_cmd_postfix": "", - "delete_cmd_prefix": "del ", - "library": "var_list.py", - "varRefreshCmd": "print(var_dic_list())" - }, - "r": { - "delete_cmd_postfix": ") ", - "delete_cmd_prefix": "rm(", - "library": "var_list.r", - "varRefreshCmd": "cat(var_dic_list()) " - } - }, - "types_to_exclude": [ - "module", - "function", - "builtin_function_or_method", - "instance", - "_Feature" - ], - "window_display": false + "version": "3.8.12" } }, "nbformat": 4, diff --git a/docs/source/tutorials/4_SanUnit_basic.ipynb b/docs/source/tutorials/4_SanUnit_basic.ipynb index 7837358c..6eaf6216 100644 --- a/docs/source/tutorials/4_SanUnit_basic.ipynb +++ b/docs/source/tutorials/4_SanUnit_basic.ipynb @@ -8,7 +8,7 @@ "\n", "- **Prepared by:**\n", " \n", - " - [Yalin Li](https://qsdsan.readthedocs.io/en/latest/CONTRIBUTING.html)\n", + " - [Yalin Li](https://qsdsan.readthedocs.io/en/latest/authors/Yalin_Li.html)\n", "\n", "- **Covered topics:**\n", "\n", @@ -17,9 +17,9 @@ " \n", "- **Video demo:**\n", "\n", - " - [Tori Morgan](https://qsdsan.readthedocs.io/en/latest/CONTRIBUTING.html)\n", + " - [Tori Morgan](https://qsdsan.readthedocs.io/en/latest/authors/Tori_Morgan.html)\n", " \n", - "To run tutorials in your browser, go to this [Binder page](https://mybinder.org/v2/gh/QSD-Group/QSDsan-env/main?urlpath=git-pull%3Frepo%3Dhttps%253A%252F%252Fgithub.com%252FQSD-group%252FQSDsan%26urlpath%3Dtree%252FQSDsan%252Fdocs%252Fsource%252Ftutorials%26branch%3Dmain).\n", + "To run tutorials in your browser, go to this [Binder page](https://mybinder.org/v2/gh/QSD-Group/QSDsan/main?filepath=%2Fdocs%2Fsource%2Ftutorials).\n", "\n", "You can also watch a video demo on [YouTube](https://youtu.be/s9zr0rCX3UY) (subscriptions & likes appreciated!)." ] @@ -1113,35 +1113,6 @@ "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.13" - }, - "varInspector": { - "cols": { - "lenName": 16, - "lenType": 16, - "lenVar": 40 - }, - "kernels_config": { - "python": { - "delete_cmd_postfix": "", - "delete_cmd_prefix": "del ", - "library": "var_list.py", - "varRefreshCmd": "print(var_dic_list())" - }, - "r": { - "delete_cmd_postfix": ") ", - "delete_cmd_prefix": "rm(", - "library": "var_list.r", - "varRefreshCmd": "cat(var_dic_list()) " - } - }, - "types_to_exclude": [ - "module", - "function", - "builtin_function_or_method", - "instance", - "_Feature" - ], - "window_display": false } }, "nbformat": 4, diff --git a/docs/source/tutorials/5_SanUnit_advanced.ipynb b/docs/source/tutorials/5_SanUnit_advanced.ipynb index 7c0a4179..4ba7fdf3 100644 --- a/docs/source/tutorials/5_SanUnit_advanced.ipynb +++ b/docs/source/tutorials/5_SanUnit_advanced.ipynb @@ -8,7 +8,7 @@ "\n", "- **Prepared by:**\n", " \n", - " - [Yalin Li](https://qsdsan.readthedocs.io/en/latest/CONTRIBUTING.html)\n", + " - [Yalin Li](https://qsdsan.readthedocs.io/en/latest/authors/Yalin_Li.html)\n", "\n", "- **Covered topics:**\n", "\n", @@ -19,9 +19,9 @@ " \n", "- **Video demo:**\n", "\n", - " - [Hannah Lohman](https://qsdsan.readthedocs.io/en/latest/CONTRIBUTING.html)\n", + " - [Hannah Lohman](https://qsdsan.readthedocs.io/en/latest/authors/Hannah_Lohman.html)\n", " \n", - "To run tutorials in your browser, go to this [Binder page](https://mybinder.org/v2/gh/QSD-Group/QSDsan-env/main?urlpath=git-pull%3Frepo%3Dhttps%253A%252F%252Fgithub.com%252FQSD-group%252FQSDsan%26urlpath%3Dtree%252FQSDsan%252Fdocs%252Fsource%252Ftutorials%26branch%3Dmain).\n", + "To run tutorials in your browser, go to this [Binder page](https://mybinder.org/v2/gh/QSD-Group/QSDsan/main?filepath=%2Fdocs%2Fsource%2Ftutorials).\n", "\n", "You can also watch a video demo on [YouTube](https://youtu.be/G20J2U8g7Dg) (subscriptions & likes appreciated!)." ] @@ -1614,35 +1614,6 @@ "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.13" - }, - "varInspector": { - "cols": { - "lenName": 16, - "lenType": 16, - "lenVar": 40 - }, - "kernels_config": { - "python": { - "delete_cmd_postfix": "", - "delete_cmd_prefix": "del ", - "library": "var_list.py", - "varRefreshCmd": "print(var_dic_list())" - }, - "r": { - "delete_cmd_postfix": ") ", - "delete_cmd_prefix": "rm(", - "library": "var_list.r", - "varRefreshCmd": "cat(var_dic_list()) " - } - }, - "types_to_exclude": [ - "module", - "function", - "builtin_function_or_method", - "instance", - "_Feature" - ], - "window_display": false } }, "nbformat": 4, diff --git a/docs/source/tutorials/6_System.ipynb b/docs/source/tutorials/6_System.ipynb index b95c555a..875fb145 100644 --- a/docs/source/tutorials/6_System.ipynb +++ b/docs/source/tutorials/6_System.ipynb @@ -8,7 +8,7 @@ "\n", "* **Prepared by:**\n", " \n", - " - [Yalin Li](https://qsdsan.readthedocs.io/en/latest/CONTRIBUTING.html)\n", + " - [Yalin Li](https://qsdsan.readthedocs.io/en/latest/authors/Yalin_Li.html)\n", "\n", "* **Covered topics:**\n", "\n", @@ -17,9 +17,9 @@ "\n", "- **Video demo:**\n", "\n", - " - [Tori Morgan](https://qsdsan.readthedocs.io/en/latest/CONTRIBUTING.html)\n", + " - [Tori Morgan](https://qsdsan.readthedocs.io/en/latest/authors/Tori_Morgan.html)\n", "\n", - "To run tutorials in your browser, go to this [Binder page](https://mybinder.org/v2/gh/QSD-Group/QSDsan-env/main?urlpath=git-pull%3Frepo%3Dhttps%253A%252F%252Fgithub.com%252FQSD-group%252FQSDsan%26urlpath%3Dtree%252FQSDsan%252Fdocs%252Fsource%252Ftutorials%26branch%3Dmain).\n", + "To run tutorials in your browser, go to this [Binder page](https://mybinder.org/v2/gh/QSD-Group/QSDsan/main?filepath=%2Fdocs%2Fsource%2Ftutorials).\n", "\n", "You can also watch a video demo on [YouTube](https://youtu.be/iIx28JkNjQ8) (subscriptions & likes appreciated!)." ] @@ -668,36 +668,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" - }, - "varInspector": { - "cols": { - "lenName": 16, - "lenType": 16, - "lenVar": 40 - }, - "kernels_config": { - "python": { - "delete_cmd_postfix": "", - "delete_cmd_prefix": "del ", - "library": "var_list.py", - "varRefreshCmd": "print(var_dic_list())" - }, - "r": { - "delete_cmd_postfix": ") ", - "delete_cmd_prefix": "rm(", - "library": "var_list.r", - "varRefreshCmd": "cat(var_dic_list()) " - } - }, - "types_to_exclude": [ - "module", - "function", - "builtin_function_or_method", - "instance", - "_Feature" - ], - "window_display": false + "version": "3.8.12" } }, "nbformat": 4, diff --git a/docs/source/tutorials/7_TEA.ipynb b/docs/source/tutorials/7_TEA.ipynb index cfcdb544..559b9b74 100755 --- a/docs/source/tutorials/7_TEA.ipynb +++ b/docs/source/tutorials/7_TEA.ipynb @@ -8,7 +8,7 @@ "\n", "* **Prepared by:**\n", " \n", - " - [Yalin Li](https://qsdsan.readthedocs.io/en/latest/CONTRIBUTING.html)\n", + " - [Yalin Li](https://qsdsan.readthedocs.io/en/latest/authors/Yalin_Li.html)\n", "\n", "* **Covered topics:**\n", "\n", @@ -17,9 +17,9 @@ " \n", "- **Video demo:**\n", "\n", - " - [Hannah Lohman](https://qsdsan.readthedocs.io/en/latest/CONTRIBUTING.html)\n", + " - [Hannah Lohman](https://qsdsan.readthedocs.io/en/latest/authors/Hannah_Lohman.html)\n", "\n", - "To run tutorials in your browser, go to this [Binder page](https://mybinder.org/v2/gh/QSD-Group/QSDsan-env/main?urlpath=git-pull%3Frepo%3Dhttps%253A%252F%252Fgithub.com%252FQSD-group%252FQSDsan%26urlpath%3Dtree%252FQSDsan%252Fdocs%252Fsource%252Ftutorials%26branch%3Dmain).\n", + "To run tutorials in your browser, go to this [Binder page](https://mybinder.org/v2/gh/QSD-Group/QSDsan/main?filepath=%2Fdocs%2Fsource%2Ftutorials).\n", "\n", "You can also watch a video demo on [YouTube](https://youtu.be/v3qNNZypTKY) (subscriptions & likes appreciated!)." ] @@ -729,35 +729,6 @@ "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.13" - }, - "varInspector": { - "cols": { - "lenName": 16, - "lenType": 16, - "lenVar": 40 - }, - "kernels_config": { - "python": { - "delete_cmd_postfix": "", - "delete_cmd_prefix": "del ", - "library": "var_list.py", - "varRefreshCmd": "print(var_dic_list())" - }, - "r": { - "delete_cmd_postfix": ") ", - "delete_cmd_prefix": "rm(", - "library": "var_list.r", - "varRefreshCmd": "cat(var_dic_list()) " - } - }, - "types_to_exclude": [ - "module", - "function", - "builtin_function_or_method", - "instance", - "_Feature" - ], - "window_display": false } }, "nbformat": 4, diff --git a/docs/source/tutorials/8_LCA.ipynb b/docs/source/tutorials/8_LCA.ipynb index 998c86a0..0c4819d8 100644 --- a/docs/source/tutorials/8_LCA.ipynb +++ b/docs/source/tutorials/8_LCA.ipynb @@ -8,7 +8,7 @@ "\n", "* **Prepared by:**\n", "\n", - " - [Yalin Li](https://qsdsan.readthedocs.io/en/latest/CONTRIBUTING.html)\n", + " - [Yalin Li](https://qsdsan.readthedocs.io/en/latest/authors/Yalin_Li.html)\n", "\n", "* **Covered topics:**\n", "\n", @@ -19,9 +19,9 @@ "\n", "- **Video demo:**\n", "\n", - " - [Tori Morgan](https://qsdsan.readthedocs.io/en/latest/CONTRIBUTING.html)\n", + " - [Tori Morgan](https://qsdsan.readthedocs.io/en/latest/authors/Tori_Morgan.html)\n", "\n", - "To run tutorials in your browser, go to this [Binder page](https://mybinder.org/v2/gh/QSD-Group/QSDsan-env/main?urlpath=git-pull%3Frepo%3Dhttps%253A%252F%252Fgithub.com%252FQSD-group%252FQSDsan%26urlpath%3Dtree%252FQSDsan%252Fdocs%252Fsource%252Ftutorials%26branch%3Dmain).\n", + "To run tutorials in your browser, go to this [Binder page](https://mybinder.org/v2/gh/QSD-Group/QSDsan/main?filepath=%2Fdocs%2Fsource%2Ftutorials).\n", "\n", "You can also watch a video demo on [YouTube](https://youtu.be/ULmFYO8nTrM) (subscriptions & likes appreciated!)." ] @@ -1148,36 +1148,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" - }, - "varInspector": { - "cols": { - "lenName": 16, - "lenType": 16, - "lenVar": 40 - }, - "kernels_config": { - "python": { - "delete_cmd_postfix": "", - "delete_cmd_prefix": "del ", - "library": "var_list.py", - "varRefreshCmd": "print(var_dic_list())" - }, - "r": { - "delete_cmd_postfix": ") ", - "delete_cmd_prefix": "rm(", - "library": "var_list.r", - "varRefreshCmd": "cat(var_dic_list()) " - } - }, - "types_to_exclude": [ - "module", - "function", - "builtin_function_or_method", - "instance", - "_Feature" - ], - "window_display": false + "version": "3.8.12" } }, "nbformat": 4, diff --git a/docs/source/tutorials/9_Uncertainty_and_Sensitivity_Analyses.ipynb b/docs/source/tutorials/9_Uncertainty_and_Sensitivity_Analyses.ipynb index 97c51a74..ba80082b 100644 --- a/docs/source/tutorials/9_Uncertainty_and_Sensitivity_Analyses.ipynb +++ b/docs/source/tutorials/9_Uncertainty_and_Sensitivity_Analyses.ipynb @@ -9,7 +9,7 @@ "\n", "* **Prepared by:**\n", "\n", - " - [Yalin Li](https://qsdsan.readthedocs.io/en/latest/CONTRIBUTING.html)\n", + " - [Yalin Li](https://qsdsan.readthedocs.io/en/latest/authors/Yalin_Li.html)\n", "\n", "* **Covered topics:**\n", "\n", @@ -18,9 +18,9 @@ " \n", "- **Video demo:**\n", "\n", - " - [Hannah Lohman](https://qsdsan.readthedocs.io/en/latest/CONTRIBUTING.html)\n", + " - [Hannah Lohman](https://qsdsan.readthedocs.io/en/latest/authors/Hannah_Lohman.html)\n", "\n", - "To run tutorials in your browser, go to this [Binder page](https://mybinder.org/v2/gh/QSD-Group/QSDsan-env/main?urlpath=git-pull%3Frepo%3Dhttps%253A%252F%252Fgithub.com%252FQSD-group%252FQSDsan%26urlpath%3Dtree%252FQSDsan%252Fdocs%252Fsource%252Ftutorials%26branch%3Dmain).\n", + "To run tutorials in your browser, go to this [Binder page](https://mybinder.org/v2/gh/QSD-Group/QSDsan/main?filepath=%2Fdocs%2Fsource%2Ftutorials).\n", "\n", "You can also watch a video demo on [YouTube](https://youtu.be/_pIfUEda2jc) (subscriptions & likes appreciated!)." ] @@ -767,36 +767,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" - }, - "varInspector": { - "cols": { - "lenName": 16, - "lenType": 16, - "lenVar": 40 - }, - "kernels_config": { - "python": { - "delete_cmd_postfix": "", - "delete_cmd_prefix": "del ", - "library": "var_list.py", - "varRefreshCmd": "print(var_dic_list())" - }, - "r": { - "delete_cmd_postfix": ") ", - "delete_cmd_prefix": "rm(", - "library": "var_list.r", - "varRefreshCmd": "cat(var_dic_list()) " - } - }, - "types_to_exclude": [ - "module", - "function", - "builtin_function_or_method", - "instance", - "_Feature" - ], - "window_display": false + "version": "3.8.12" } }, "nbformat": 4, diff --git a/docs/source/tutorials/Tutorial_11.ipynb b/docs/source/tutorials/Tutorial_11.ipynb new file mode 100644 index 00000000..545c1773 --- /dev/null +++ b/docs/source/tutorials/Tutorial_11.ipynb @@ -0,0 +1,401 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "9b7ba848", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "This tutorial was made with qsdsan v1.2.5 and exposan v1.2.5\n" + ] + } + ], + "source": [ + "import qsdsan as qs, exposan\n", + "print(f'This tutorial was made with qsdsan v{qs.__version__} and exposan v{exposan.__version__}')" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "a31c8f69", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "System: bsm1_sys\n", + "ins...\n", + "[0] wastewater\n", + " phase: 'l', T: 293.15 K, P: 101325 Pa\n", + " flow (kmol/hr): S_I 23.1\n", + " S_S 53.4\n", + " X_I 39.4\n", + " X_S 155\n", + " X_BH 21.7\n", + " S_NH 1.34\n", + " S_ND 0.381\n", + " ... 4.26e+04\n", + "outs...\n", + "[0] effluent\n", + " phase: 'l', T: 293.15 K, P: 101325 Pa\n", + " flow: 0\n", + "[1] WAS\n", + " phase: 'l', T: 293.15 K, P: 101325 Pa\n", + " flow: 0\n" + ] + } + ], + "source": [ + "# Let's load the BSM1 system first\n", + "from exposan import bsm1\n", + "bsm1.load()\n", + "sys = bsm1.sys\n", + "sys.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "5fe1776f", + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + "A1CSTR:c->A2CSTR:c\n", + "\n", + "\n", + "\n", + " ws1\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "A2CSTR:c->O1CSTR:c\n", + "\n", + "\n", + "\n", + " ws3\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "O1CSTR:c->O2CSTR:c\n", + "\n", + "\n", + "\n", + " ws5\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "O2CSTR:c->O3CSTR:c\n", + "\n", + "\n", + "\n", + " ws7\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "O3CSTR:c->A1CSTR:c\n", + "\n", + "\n", + "\n", + " RWW\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "O3CSTR:c->C1Flat bottom circular clarifier:c\n", + "\n", + "\n", + "\n", + " treated\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "C1Flat bottom circular clarifier:c->A1CSTR:c\n", + "\n", + "\n", + "\n", + " RAS\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "C1Flat bottom circular clarifier:c-> effluent:w\n", + "\n", + "\n", + " effluent\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "C1Flat bottom circular clarifier:c-> WAS:w\n", + "\n", + "\n", + " WAS\n", + "\n", + "\n", + "\n", + "\n", + "\n", + " wastewater:e->A1CSTR:c\n", + "\n", + "\n", + " wastewater\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "A1CSTR\n", + "\n", + "\n", + "A1CSTR\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "A2CSTR\n", + "\n", + "\n", + "A2CSTR\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "O1CSTR\n", + "\n", + "\n", + "O1CSTR\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "O2CSTR\n", + "\n", + "\n", + "O2CSTR\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "O3CSTR\n", + "\n", + "\n", + "O3CSTR\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "C1Flat bottom circular clarifier\n", + "\n", + "\n", + "C1Flat bottom circular clarifier\n", + "\n", + "\n", + "\n", + "\n", + "\n", + " wastewater\n", + "\n", + "\n", + "\n", + "\n", + " effluent\n", + "\n", + "\n", + "\n", + "\n", + " WAS\n", + "\n", + "\n", + "\n", + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# The BSM1 system is composed of 5 CSTRs in series,\n", + "# followed by a flat-bottom circular clarifier.\n", + "sys.diagram()\n", + "# sys.units" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "98d2662c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# We can verify that by\n", + "sys.isdynamic" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "e2c64ce0", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{: True,\n", + " : True,\n", + " : True,\n", + " : True,\n", + " : True,\n", + " : True}" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# This is because the system contains at least one dynamic SanUnit\n", + "{u: u.isdynamic for u in sys.units}\n", + "\n", + "# If we disable dynamic simulation, then `simulate` would work as usual\n", + "# sys.isdynamic = False\n", + "# sys.simulate()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d8cc6e48", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "C:\\Users\\joy_c\\anaconda3\\envs\\tut\\lib\\site-packages\\qsdsan\\sanunits\\_suspended_growth_bioreactor.py:44: NumbaPerformanceWarning: \u001b[1m\u001b[1m'@' is faster on contiguous arrays, called on (array(float64, 1d, A), array(float64, 2d, A))\u001b[0m\u001b[0m\n", + " flow_in = Q_ins @ C_ins / V_arr\n", + "C:\\Users\\joy_c\\anaconda3\\envs\\tut\\lib\\site-packages\\numba\\core\\typing\\npydecl.py:913: NumbaPerformanceWarning: \u001b[1m'@' is faster on contiguous arrays, called on (array(float64, 1d, A), array(float64, 2d, A))\u001b[0m\n", + " warnings.warn(NumbaPerformanceWarning(msg))\n" + ] + } + ], + "source": [ + "# Let's try simulating the BSM1 system from day 0 to day 50\n", + "sys.simulate(t_span=(0, 50), method='BDF', state_reset_hook='reset_cache')\n", + "sys.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ba51c0b9", + "metadata": {}, + "outputs": [], + "source": [ + "# This shows the units/streams whose state variables are kept track of\n", + "# during dynamic simulations.\n", + "sys.scope.subjects" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c4c0bdfd", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f1d690bf", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c05808bc", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "37df12a9", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python [conda env:tut]", + "language": "python", + "name": "conda-env-tut-py" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.0" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/source/tutorials/assets/_bkm.tsv b/docs/source/tutorials/_bkm.tsv similarity index 100% rename from docs/source/tutorials/assets/_bkm.tsv rename to docs/source/tutorials/_bkm.tsv diff --git a/docs/source/tutorials/_index.rst b/docs/source/tutorials/_index.rst index c32424f2..6fa4cbe7 100644 --- a/docs/source/tutorials/_index.rst +++ b/docs/source/tutorials/_index.rst @@ -28,7 +28,6 @@ Topical Tutorials 9_Uncertainty_and_Sensitivity_Analyses 10_Process 11_Dynamic_Simulation - 12_Anaerobic_Digestion_Model_No_1 Additional Resources diff --git a/docs/source/tutorials/assets/adm1.jpg b/docs/source/tutorials/assets/adm1.jpg deleted file mode 100644 index f3d7dd9826e21d7ee3ffd893f756eb09ce2340c2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 57220 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

6~~`7R!=dSzH|E4~^-pmk1P95Rbhi&6!1O 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 z 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 E0cg66#Q*>R diff --git a/qsdsan/_impact_item.py b/qsdsan/_impact_item.py index 395bfb1c..98fc476a 100644 --- a/qsdsan/_impact_item.py +++ b/qsdsan/_impact_item.py @@ -414,7 +414,7 @@ def load_from_file(cls, path_or_dict, index_col=None): This Excel should have multiple sheets: - The "info" sheet should have three columns: "ID" (e.g., Cement) \ - "functional_unit" (e.g., kg), and "kind" ("ImpactItem" or "StreamImpactItem") \ + "functional_unit" (e.g., kg), and "kind" ("ImpactItem" or "StreamImpactItem") of different impact items. - The remaining sheets should contain characterization factors of \ diff --git a/qsdsan/sanunits/_abstract.py b/qsdsan/sanunits/_abstract.py index 0b29272e..8a99237e 100644 --- a/qsdsan/sanunits/_abstract.py +++ b/qsdsan/sanunits/_abstract.py @@ -58,11 +58,10 @@ class Mixer(SanUnit, BSTMixer): _graphics = BSTMixer._graphics def __init__(self, ID='', ins=None, outs=(), thermo=None, init_with='WasteStream', F_BM_default=None, isdynamic=False, - rigorous=False, conserve_phases=False): + rigorous=False): SanUnit.__init__(self, ID, ins, outs, thermo, init_with, F_BM_default=F_BM_default, isdynamic=isdynamic) self.rigorous = rigorous - self.conserve_phases = conserve_phases @property diff --git a/requirements.txt b/requirements.txt index 37a3c2d7..d58c4abe 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,4 +13,7 @@ sphinx-copybutton sphinx-design furo nbsphinx -pandoc \ No newline at end of file +pandoc +ipywidgets +jupyterlab-widgets +widgetsnbextension \ No newline at end of file diff --git a/setup.py b/setup.py index 57034c09..62bbe8bd 100644 --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ setup( name='qsdsan', packages=['qsdsan'], - version='1.3.1', + version='1.3.0', license='UIUC', author='Quantitative Sustainable Design Group', author_email='quantitative.sustainable.design@gmail.com', From 15a61f938547a496c7623138d99563bfcd040949 Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Tue, 14 Nov 2023 19:42:22 -0600 Subject: [PATCH 223/424] Revert "workaround to prevent negatives/better error prompting" This reverts commit 9a439ce1065c594b70edfc783ba8d65cf6c18328. --- qsdsan/sanunits/_clarifier.py | 13 ++++++------- qsdsan/sanunits/_sludge_treatment.py | 4 ++-- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index 2bc5c6c4..8461ce3f 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -52,7 +52,7 @@ def _settling_flux(X, v_max, v_max_practical, X_min, rh, rp, n0): # v = min(v_max_practical, v_max*(exp(-rh*X_star) - exp(-rp*X_star))) # return X*max(v, 0) -# Assign a bare module of 1 to all +# Asign a bare module of 1 to all default_F_BM = { 'Wall concrete': 1., 'Slab concrete': 1., @@ -1040,13 +1040,12 @@ def _f_i(self): fx = xcod/self._mixed.COD corr = self._corr HRT = self._HRT - # n_COD should not be negative - n_COD = max(0, corr*(2.88*fx - 0.118)*(1.45 + 6.15*np.log(HRT*24*60))) + n_COD = corr*(2.88*fx - 0.118)*(1.45 + 6.15*np.log(HRT*24*60)) f_i = 1 - (n_COD/100) return f_i def _run(self): - uf, of = self.outs # underflow, overflow + uf, of = self.outs cmps = self.components mixed = self._mixed mixed.mix_from(self.ins) @@ -1336,7 +1335,7 @@ def yt(t, QC_ins, dQC_ins): class PrimaryClarifier(SanUnit): """ - Primary clarifier adapted from the design of thickener as defined in BSM2. [1] + Primary clarifier adapted from the design of thickener as defined in BSM-2. [1] ---------- ID : str ID for the Primary Clarifier. The default is ''. @@ -1533,7 +1532,7 @@ def _cal_thickener_factor(self, TSS_in): if thickener_factor<1: thickener_factor=1 return thickener_factor - else: raise ValueError(f'Influent TSS is not valid ({TSS_in:.2f} mg/L).') + else: return None def _cal_parameters(self, thickener_factor): if thickener_factor<1: @@ -1898,7 +1897,7 @@ def _cost(self): base_flow_v_notch_weir = 10 # in m3/hr C['v notch weir'] = D['Number of clarifiers']*base_cost_v_notch_weir*(clarifier_flow/base_flow_v_notch_weir)**0.6 - # Pump (construction and maintenance) + # Pump (construction and maintainance) pumps = self.pumps add_OPEX = self.add_OPEX pump_cost = 0. diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py index 565e5534..9425d9e0 100644 --- a/qsdsan/sanunits/_sludge_treatment.py +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -24,7 +24,7 @@ __all__ = ('Thickener', 'Centrifuge', 'Incinerator') -# Assign a bare module of 1 to all +# Asign a bare module of 1 to all default_F_BM = { 'Wall concrete': 1., 'Slab concrete': 1., @@ -239,7 +239,7 @@ def _cal_thickener_factor(self, TSS_in): if thickener_factor<1: thickener_factor=1 return thickener_factor - else: raise ValueError(f'Influent TSS is not valid ({TSS_in:.2f} mg/L).') + else: return None def _cal_parameters(self, thickener_factor): if thickener_factor<1: From 6e329a1eeb591a37b8b49f4972b6e2308b9cc389 Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Wed, 15 Nov 2023 13:04:01 -0600 Subject: [PATCH 224/424] Revert "Revert "Merge branch 'main' of https://github.com/QSD-Group/QSDsan into metro"" This reverts commit 105eb63ec5ea3fc42c45b2b9b26f998d50b65551. --- README.rst | 10 +- docs/source/api/sanunits/_index.rst | 1 - .../api/sanunits/encapsulation_bioreactor.rst | 4 - docs/source/conf.py | 14 +- docs/source/images/custom_binder_logo.svg | 1 + docs/source/tutorials/0_Quick_Overview.ipynb | 35 +- docs/source/tutorials/10_Process.ipynb | 1808 ++--------------- .../tutorials/11_Dynamic_Simulation.ipynb | 1765 ++++++++-------- .../12_Anaerobic_Digestion_Model_No_1.ipynb | 1538 ++++++++++++++ docs/source/tutorials/12_Chlorination.ipynb | 1046 ---------- docs/source/tutorials/1_Helpful_Basics.ipynb | 39 +- docs/source/tutorials/2_Component.ipynb | 35 +- docs/source/tutorials/3_WasteStream.ipynb | 37 +- docs/source/tutorials/4_SanUnit_basic.ipynb | 35 +- .../source/tutorials/5_SanUnit_advanced.ipynb | 35 +- docs/source/tutorials/6_System.ipynb | 37 +- docs/source/tutorials/7_TEA.ipynb | 35 +- docs/source/tutorials/8_LCA.ipynb | 37 +- ...Uncertainty_and_Sensitivity_Analyses.ipynb | 37 +- docs/source/tutorials/Tutorial_11.ipynb | 401 ---- docs/source/tutorials/_index.rst | 1 + docs/source/tutorials/{ => assets}/_bkm.tsv | 0 docs/source/tutorials/assets/adm1.jpg | Bin 0 -> 57220 bytes legacy_files/binder.yaml | Bin 0 -> 924 bytes qsdsan/_impact_item.py | 2 +- qsdsan/sanunits/_abstract.py | 3 +- qsdsan/sanunits/_activated_sludge_process.py | 24 +- .../sanunits/_suspended_growth_bioreactor.py | 182 +- requirements.txt | 5 +- setup.py | 2 +- 30 files changed, 3109 insertions(+), 4060 deletions(-) delete mode 100644 docs/source/api/sanunits/encapsulation_bioreactor.rst create mode 100644 docs/source/images/custom_binder_logo.svg create mode 100644 docs/source/tutorials/12_Anaerobic_Digestion_Model_No_1.ipynb delete mode 100644 docs/source/tutorials/12_Chlorination.ipynb delete mode 100644 docs/source/tutorials/Tutorial_11.ipynb rename docs/source/tutorials/{ => assets}/_bkm.tsv (100%) create mode 100644 docs/source/tutorials/assets/adm1.jpg create mode 100644 legacy_files/binder.yaml diff --git a/README.rst b/README.rst index 17f65f5b..f444b684 100644 --- a/README.rst +++ b/README.rst @@ -31,8 +31,8 @@ QSDsan: Quantitative Sustainable Design for Sanitation and Resource Recovery Sys :target: https://codecov.io/gh/QSD-Group/QSDsan .. Binder launch of tutorials -.. image:: https://mybinder.org/badge_logo.svg - :target: https://mybinder.org/v2/gh/QSD-Group/QSDsan/main?filepath=%2Fdocs%2Fsource%2Ftutorials +.. image:: ./docs/source/images/custom_binder_logo.svg + :target: https://mybinder.org/v2/gh/QSD-Group/QSDsan-env/main?urlpath=git-pull%3Frepo%3Dhttps%253A%252F%252Fgithub.com%252FQSD-group%252FQSDsan%26urlpath%3Dtree%252FQSDsan%252Fdocs%252Fsource%252Ftutorials%26branch%3Dmain .. Email subscription form .. image:: https://img.shields.io/badge/news-subscribe-F3A93C?style=flat&logo=rss @@ -162,4 +162,8 @@ References .. [2] Li, Y.; Trimmer, J.T.; Hand, S.; Zhang, X.; Chambers, K.G.; Lohman, H.A.C.; Shi, R.; Byrne, D.M.; Cook, S.M.; Guest, J.S. Quantitative Sustainable Design (QSD): A Methodology for the Prioritization of Research, Development, and Deployment of Technologies. (Tutorial Review) Environ. Sci.: Water Res. Technol. 2022, 8 (11), 2439–2465. https://doi.org/10.1039/D2EW00431C. -.. [3] Cortés-Peña, Y.; Kumar, D.; Singh, V.; Guest, J.S. BioSTEAM: A Fast and Flexible Platform for the Design, Simulation, and Techno-Economic Analysis of Biorefineries under Uncertainty. ACS Sustainable Chem. Eng. 2020, 8 (8), 3302–3310. https://doi.org/10.1021/acssuschemeng.9b07040. \ No newline at end of file +.. [3] Cortés-Peña, Y.; Kumar, D.; Singh, V.; Guest, J.S. BioSTEAM: A Fast and Flexible Platform for the Design, Simulation, and Techno-Economic Analysis of Biorefineries under Uncertainty. ACS Sustainable Chem. Eng. 2020, 8 (8), 3302–3310. https://doi.org/10.1021/acssuschemeng.9b07040. + + +.. Custom launch badges: https://mybinder.readthedocs.io/en/latest/howto/badges.html +.. binder_badge: https://img.shields.io/badge/launch-binder%20%7C%20tutorial-579ACA.svg?logo=data:image/png;base64,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 diff --git a/docs/source/api/sanunits/_index.rst b/docs/source/api/sanunits/_index.rst index 1c1eab87..72aa4069 100644 --- a/docs/source/api/sanunits/_index.rst +++ b/docs/source/api/sanunits/_index.rst @@ -37,7 +37,6 @@ Individual Unit Operations CropApplication DynamicInfluent ElectrochemicalCell - encapsulation_bioreactor Excretion Flash heat_exchanging diff --git a/docs/source/api/sanunits/encapsulation_bioreactor.rst b/docs/source/api/sanunits/encapsulation_bioreactor.rst deleted file mode 100644 index bc4d9476..00000000 --- a/docs/source/api/sanunits/encapsulation_bioreactor.rst +++ /dev/null @@ -1,4 +0,0 @@ -Encapsulation Bioreactor -======================== -.. automodule:: qsdsan.sanunits._encapsulation_bioreactor - :members: \ No newline at end of file diff --git a/docs/source/conf.py b/docs/source/conf.py index 2e7bbf8b..524df415 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -31,7 +31,7 @@ # built documents. # # The short X.Y version. -version = '1.3.0' +version = '1.3.1' # The full version, including alpha/beta/rc tags. release = version @@ -127,10 +127,10 @@ # -- External mapping ------------------------------------------------------- intersphinx_mapping = { - 'biosteam': ('https://biosteam.readthedocs.io/en/latest', None), - 'thermosteam': ('https://biosteam.readthedocs.io/en/latest', None), - 'BioSTEAM': ('https://biosteam.readthedocs.io/en/latest', None), - 'Thermosteam': ('https://biosteam.readthedocs.io/en/latest', None), - 'scipy': ('https://docs.scipy.org/doc/scipy/', None), - 'SALib': ('https://salib.readthedocs.io/en/latest', None), + 'biosteam': ('https://biosteam.readthedocs.io', None), + 'thermosteam': ('https://biosteam.readthedocs.io', None), + 'BioSTEAM': ('https://biosteam.readthedocs.io', None), + 'Thermosteam': ('https://biosteam.readthedocs.io', None), + 'scipy': ('https://docs.scipy.org/doc/scipy', None), + 'SALib': ('https://salib.readthedocs.io', None), } diff --git a/docs/source/images/custom_binder_logo.svg b/docs/source/images/custom_binder_logo.svg new file mode 100644 index 00000000..00919718 --- /dev/null +++ b/docs/source/images/custom_binder_logo.svg @@ -0,0 +1 @@ +launch: binder | tutoriallaunchbinder | tutorial \ No newline at end of file diff --git a/docs/source/tutorials/0_Quick_Overview.ipynb b/docs/source/tutorials/0_Quick_Overview.ipynb index ed9e66f4..5f4341ec 100644 --- a/docs/source/tutorials/0_Quick_Overview.ipynb +++ b/docs/source/tutorials/0_Quick_Overview.ipynb @@ -9,9 +9,9 @@ "\n", "- **Prepared by:**\n", " \n", - " - [Yalin Li](https://qsdsan.readthedocs.io/en/latest/authors/Yalin_Li.html)\n", + " - [Yalin Li](https://qsdsan.readthedocs.io/en/latest/CONTRIBUTING.html)\n", " \n", - "To run tutorials in your browser, go to this [Binder page](https://mybinder.org/v2/gh/QSD-Group/QSDsan/main?filepath=%2Fdocs%2Fsource%2Ftutorials)." + "To run tutorials in your browser, go to this [Binder page](https://mybinder.org/v2/gh/QSD-Group/QSDsan-env/main?urlpath=git-pull%3Frepo%3Dhttps%253A%252F%252Fgithub.com%252FQSD-group%252FQSDsan%26urlpath%3Dtree%252FQSDsan%252Fdocs%252Fsource%252Ftutorials%26branch%3Dmain)." ] }, { @@ -605,7 +605,36 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.12" + "version": "3.9.13" + }, + "varInspector": { + "cols": { + "lenName": 16, + "lenType": 16, + "lenVar": 40 + }, + "kernels_config": { + "python": { + "delete_cmd_postfix": "", + "delete_cmd_prefix": "del ", + "library": "var_list.py", + "varRefreshCmd": "print(var_dic_list())" + }, + "r": { + "delete_cmd_postfix": ") ", + "delete_cmd_prefix": "rm(", + "library": "var_list.r", + "varRefreshCmd": "cat(var_dic_list()) " + } + }, + "types_to_exclude": [ + "module", + "function", + "builtin_function_or_method", + "instance", + "_Feature" + ], + "window_display": false } }, "nbformat": 4, diff --git a/docs/source/tutorials/10_Process.ipynb b/docs/source/tutorials/10_Process.ipynb index 7cc73a09..5d1069ff 100644 --- a/docs/source/tutorials/10_Process.ipynb +++ b/docs/source/tutorials/10_Process.ipynb @@ -9,7 +9,7 @@ "\n", "- **Prepared by:**\n", " \n", - " - [Joy Zhang](https://qsdsan.readthedocs.io/en/latest/authors/Joy_Zhang.html)\n", + " - [Joy Zhang](https://qsdsan.readthedocs.io/en/latest/CONTRIBUTING.html)\n", " \n", "- **Covered topics:**\n", "\n", @@ -20,9 +20,9 @@ " \n", "- **Video demo:**\n", "\n", - " - [Joy Zhang](https://qsdsan.readthedocs.io/en/latest/authors/Joy_Zhang.html)\n", + " - [Joy Zhang](https://qsdsan.readthedocs.io/en/latest/CONTRIBUTING.html)\n", " \n", - "To run tutorials in your browser, go to this [Binder page](https://mybinder.org/v2/gh/QSD-Group/QSDsan/main?filepath=%2Fdocs%2Fsource%2Ftutorials).\n", + "To run tutorials in your browser, go to this [Binder page](https://mybinder.org/v2/gh/QSD-Group/QSDsan-env/main?urlpath=git-pull%3Frepo%3Dhttps%253A%252F%252Fgithub.com%252FQSD-group%252FQSDsan%26urlpath%3Dtree%252FQSDsan%252Fdocs%252Fsource%252Ftutorials%26branch%3Dmain).\n", " \n", "You can also watch a video demo on YouTube ([part 1](https://youtu.be/r9HrfTH9_Tg), [part 2](https://youtu.be/noVSJboqSuc)) (subscriptions & likes appreciated!)." ] @@ -82,15 +82,7 @@ "execution_count": 2, "id": "3dc1138e", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "This tutorial was made with qsdsan v1.2.5.\n" - ] - } - ], + "outputs": [], "source": [ "import qsdsan as qs\n", "print(f'This tutorial was made with qsdsan v{qs.__version__}.')" @@ -117,24 +109,10 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 3, "id": "61b1bd62", "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "Image(url='https://lucid.app/publicSegments/view/2c231fa2-6065-46b9-83af-a790ce38b6c0/image.png', width=600)" ] @@ -158,21 +136,10 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 4, "id": "75766cf7", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "qsdsan._process.Process" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# If you check\n", "qs.Process\n", @@ -192,21 +159,10 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 5, "id": "50db4564", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# If you check\n", "qs.processes" @@ -222,37 +178,10 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 6, "id": "fc717b78", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "('DiffusedAeration',\n", - " 'create_asm1_cmps',\n", - " 'ASM1',\n", - " 'create_asm2d_cmps',\n", - " 'ASM2d',\n", - " 'create_adm1_cmps',\n", - " 'ADM1',\n", - " 'non_compet_inhibit',\n", - " 'substr_inhibit',\n", - " 'T_correction_factor',\n", - " 'pH_inhibit',\n", - " 'Hill_inhibit',\n", - " 'rhos_adm1',\n", - " 'Decay',\n", - " 'KineticReaction',\n", - " 'create_pm2_cmps',\n", - " 'PM2')" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# To see the list of objects that can be directly imported from this folder\n", "qs.processes.__all__" @@ -270,39 +199,12 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 7, "id": "6e6931bb", "metadata": { "scrolled": true }, - "outputs": [ - { - "data": { - "text/plain": [ - "{'__all__',\n", - " '__builtins__',\n", - " '__cached__',\n", - " '__doc__',\n", - " '__file__',\n", - " '__loader__',\n", - " '__name__',\n", - " '__package__',\n", - " '__path__',\n", - " '__spec__',\n", - " '_adm1',\n", - " '_aeration',\n", - " '_asm1',\n", - " '_asm2d',\n", - " '_decay',\n", - " '_kinetic_reaction',\n", - " '_pm2'}" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# You can see other attributes of the `qs.processes` folder with the `dir` function\n", "set(dir(qs.processes)) - set(qs.processes.__all__)" @@ -318,21 +220,10 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 8, "id": "82751cf8", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# For example, `_asm1.py` is a script in the `processes` folder.\n", "qs.processes._asm1" @@ -363,18 +254,10 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 9, "id": "7e4b98a4", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "CompiledComponents([S_I, S_S, X_I, X_S, X_BH, X_BA, X_P, S_O, S_NO, S_NH, S_ND, X_ND, S_ALK, S_N2, H2O])\n" - ] - } - ], + "outputs": [], "source": [ "# Before we get to the subsections, let's get ready by loading ASM1-related objects in qsdsan\n", "from qsdsan.processes import create_asm1_cmps, ASM1\n", @@ -394,27 +277,10 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 10, "id": "2b7ccd5b", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Thermo(\n", - " chemicals=CompiledComponents([S_I, S_S, X_I, X_S, X_BH, X_BA, X_P, S_O, S_NO, S_NH, S_ND, X_ND, S_ALK, S_N2, H2O]),\n", - " mixture=Mixture(\n", - " rule='ideal', ...\n", - " include_excess_energies=False\n", - " ),\n", - " Gamma=DortmundActivityCoefficients,\n", - " Phi=IdealFugacityCoefficients,\n", - " PCF=MockPoyintingCorrectionFactors\n", - ")\n" - ] - } - ], + "outputs": [], "source": [ "# By default, the thermo is set with this `CompiledComponents` object upon its creation.\n", "# We can verify that by calling the `get_thermo` function\n", @@ -423,21 +289,10 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 11, "id": "948340c9", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(qsdsan._process.CompiledProcesses,)" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# Next we need to create an instance of the ASM1 model\n", "# We can see that `ASM1` is a subclass of `CompiledProcesses`, so it can be used for demonstration\n", @@ -447,7 +302,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 12, "id": "7357bed8", "metadata": { "scrolled": true @@ -460,20 +315,12 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 13, "id": "bdf27943", "metadata": { "scrolled": true }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "ASM1([aero_growth_hetero, anox_growth_hetero, aero_growth_auto, decay_hetero, decay_auto, ammonification, hydrolysis, hydrolysis_N])\n" - ] - } - ], + "outputs": [], "source": [ "# Without getting into the details of ASM1, we will leave all parameters at their default values \n", "asm1 = ASM1()\n", @@ -503,43 +350,10 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 14, "id": "25a826a8", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Process: aero_growth_hetero\n", - "[stoichiometry] S_S: -1.0/Y_H\n", - " X_BH: 1.00\n", - " S_O: 1.0*(Y_H - 1.0)/Y_H\n", - " S_NH: -0.0800\n", - " S_ALK: -0.0686\n", - "[reference] X_BH\n", - "[rate equation] S_NH*S_O*S_S*X_BH*mu_H/((K_N...\n", - "[parameters] Y_H: 0.67\n", - " Y_A: 0.24\n", - " f_P: 0.08\n", - " mu_H: 4\n", - " K_S: 10\n", - " K_O_H: 0.2\n", - " K_NO: 0.5\n", - " b_H: 0.3\n", - " mu_A: 0.5\n", - " K_NH: 1\n", - " K_O_A: 0.4\n", - " b_A: 0.05\n", - " eta_g: 0.8\n", - " k_a: 0.05\n", - " k_h: 3\n", - " K_X: 0.1\n", - " eta_h: 0.8\n", - "[dynamic parameters] \n" - ] - } - ], + "outputs": [], "source": [ "# Let's take the 0th process in `asm1` as an example to learn about `Process`:\n", "# p1 = asm1.tuple[0]\n", @@ -573,25 +387,10 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 15, "id": "baea578a", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'S_S': -1.49253731343284,\n", - " 'X_BH': 1.00000000000000,\n", - " 'S_O': -0.492537313432836,\n", - " 'S_NH': -0.0800000000000000,\n", - " 'S_ALK': -0.0685997415522571}" - ] - }, - "execution_count": 16, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# For example, we can retrieve information on the stoichiometry of this process\n", "p1.stoichiometry" @@ -611,21 +410,10 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 16, "id": "acb5b8e0", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'X_BH'" - ] - }, - "execution_count": 17, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# This information can also be accessed by calling the `ref_component` property.\n", "p1.ref_component" @@ -633,26 +421,12 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 17, "id": "d0612896", "metadata": { "scrolled": true }, - "outputs": [ - { - "data": { - "text/latex": [ - "$\\displaystyle \\frac{4.0 S_{NH} S_{O} S_{S} X_{BH}}{\\left(S_{NH} + 1.0\\right) \\left(S_{O} + 0.2\\right) \\left(S_{S} + 10.0\\right)}$" - ], - "text/plain": [ - "4.0*S_NH*S_O*S_S*X_BH/((S_NH + 1.0)*(S_O + 0.2)*(S_S + 10.0))" - ] - }, - "execution_count": 18, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# Another defining characteristics of a process is its rate equation, which is stored as a\n", "# property of the `Process` object\n", @@ -671,24 +445,10 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 18, "id": "0ce7b4df", "metadata": {}, - "outputs": [ - { - "data": { - "text/latex": [ - "$\\displaystyle \\frac{S_{NH} S_{O} S_{S} X_{BH} \\mu_{H}}{\\left(K_{NH} + S_{NH}\\right) \\left(K_{O H} + S_{O}\\right) \\left(K_{S} + S_{S}\\right)}$" - ], - "text/plain": [ - "S_NH*S_O*S_S*X_BH*mu_H/((K_NH + S_NH)*(K_O_H + S_O)*(K_S + S_S))" - ] - }, - "execution_count": 19, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# If we access the private attribute\n", "p1._rate_equation" @@ -704,7 +464,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 19, "id": "50b77ac8", "metadata": {}, "outputs": [], @@ -722,21 +482,10 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 20, "id": "0142f901", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "array([1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.])" - ] - }, - "execution_count": 21, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# Now that we've understood the required input to the rate equation, we can try evaluating the\n", "# process rate. Let's try with all component concentrations equal to 1.\n", @@ -755,21 +504,10 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 21, "id": "aceafb27", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "0.15151515151515152" - ] - }, - "execution_count": 22, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# Note that the `rate_equation` attribute only stores the formula.\n", "# The evaluation of process rate is done through the `rate_function` attribute, which is rendered\n", @@ -791,37 +529,10 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 22, "id": "62dc9537", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'Y_H': 0.67,\n", - " 'Y_A': 0.24,\n", - " 'f_P': 0.08,\n", - " 'mu_H': 4.0,\n", - " 'K_S': 10.0,\n", - " 'K_O_H': 0.2,\n", - " 'K_NO': 0.5,\n", - " 'b_H': 0.3,\n", - " 'mu_A': 0.5,\n", - " 'K_NH': 1.0,\n", - " 'K_O_A': 0.4,\n", - " 'b_A': 0.05,\n", - " 'eta_g': 0.8,\n", - " 'k_a': 0.05,\n", - " 'k_h': 3.0,\n", - " 'K_X': 0.1,\n", - " 'eta_h': 0.8}" - ] - }, - "execution_count": 23, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# For `asm1` and the individual processes within `asm1`, parameters are stored as a dictionary\n", "p1.parameters\n", @@ -839,37 +550,10 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 23, "id": "0ab063bf", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'Y_H': 0.8,\n", - " 'Y_A': 0.24,\n", - " 'f_P': 0.08,\n", - " 'mu_H': 6.0,\n", - " 'K_S': 8.0,\n", - " 'K_O_H': 0.2,\n", - " 'K_NO': 0.5,\n", - " 'b_H': 0.3,\n", - " 'mu_A': 0.5,\n", - " 'K_NH': 1.0,\n", - " 'K_O_A': 0.4,\n", - " 'b_A': 0.05,\n", - " 'eta_g': 0.8,\n", - " 'k_a': 0.05,\n", - " 'k_h': 3.0,\n", - " 'K_X': 0.1,\n", - " 'eta_h': 0.8}" - ] - }, - "execution_count": 24, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# If you update parameter values for `p1`, the same parameters will be updated accordingly for\n", "# `asm1` and any other processes in `asm1`.\n", @@ -879,21 +563,10 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 24, "id": "c69a557c", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "0.2777777777777778" - ] - }, - "execution_count": 25, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# If you evaluate process rate or stoichiometry again with the same input, \n", "# you should now expect different output.\n", @@ -911,21 +584,10 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 25, "id": "b7115278", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "('COD', 'charge', 'N')" - ] - }, - "execution_count": 26, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "p1.conserved_for" ] @@ -940,20 +602,10 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 26, "id": "772e7e78", "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "C:\\Users\\joy_c\\anaconda3\\envs\\tut\\lib\\site-packages\\qsdsan\\_process.py:499: UserWarning: The following materials aren't strictly conserved by the stoichiometric coefficients. A positive value means the material is created, a negative value means the material is destroyed:\n", - " charge: -5.20417042793042E-18\n", - " warn(\"The following materials aren't strictly conserved by the \"\n" - ] - } - ], + "outputs": [], "source": [ "# No return indicates that all materials in `conserved_for` are conserved.\n", "# Otherwise, a warning or a `RuntimeError` will be raised.\n", @@ -983,21 +635,10 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 27, "id": "b5bde6b2", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(qsdsan._process.Processes,)" - ] - }, - "execution_count": 28, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "qs.CompiledProcesses.__bases__" ] @@ -1012,21 +653,10 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 28, "id": "3ee5e0dd", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 29, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# Let's verify that\n", "isinstance(asm1, qs.CompiledProcesses)" @@ -1042,18 +672,10 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 29, "id": "9f6ae9b6", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Processes([aero_growth_hetero, anox_growth_hetero, aero_growth_auto, decay_hetero, decay_auto, ammonification, hydrolysis, hydrolysis_N])\n" - ] - } - ], + "outputs": [], "source": [ "asm1 = qs.Processes(asm1.tuple)\n", "asm1.show()" @@ -1061,30 +683,12 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": 30, "id": "3b1e8036", "metadata": { "scrolled": true }, - "outputs": [ - { - "data": { - "text/plain": [ - "{'aero_growth_hetero': ,\n", - " 'anox_growth_hetero': ,\n", - " 'aero_growth_auto': ,\n", - " 'decay_hetero': ,\n", - " 'decay_auto': ,\n", - " 'ammonification': ,\n", - " 'hydrolysis': ,\n", - " 'hydrolysis_N': }" - ] - }, - "execution_count": 31, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# Let's see what difference \"decompiling\" made\n", "asm1.__dict__" @@ -1100,191 +704,12 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": 31, "id": "48443f1d", "metadata": { "scrolled": true }, - "outputs": [ - { - "data": { - "text/plain": [ - "{'aero_growth_hetero': ,\n", - " 'anox_growth_hetero': ,\n", - " 'aero_growth_auto': ,\n", - " 'decay_hetero': ,\n", - " 'decay_auto': ,\n", - " 'ammonification': ,\n", - " 'hydrolysis': ,\n", - " 'hydrolysis_N': ,\n", - " 'tuple': (,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ),\n", - " 'size': 8,\n", - " 'IDs': ('aero_growth_hetero',\n", - " 'anox_growth_hetero',\n", - " 'aero_growth_auto',\n", - " 'decay_hetero',\n", - " 'decay_auto',\n", - " 'ammonification',\n", - " 'hydrolysis',\n", - " 'hydrolysis_N'),\n", - " '_index': {'aero_growth_hetero': 0,\n", - " 'anox_growth_hetero': 1,\n", - " 'aero_growth_auto': 2,\n", - " 'decay_hetero': 3,\n", - " 'decay_auto': 4,\n", - " 'ammonification': 5,\n", - " 'hydrolysis': 6,\n", - " 'hydrolysis_N': 7},\n", - " '_components': CompiledComponents([S_I, S_S, X_I, X_S, X_BH, X_BA, X_P, S_O, S_NO, S_NH, S_ND, X_ND, S_ALK, S_N2, H2O]),\n", - " '_parameters': {'Y_H': 0.8,\n", - " 'Y_A': 0.24,\n", - " 'f_P': 0.08,\n", - " 'mu_H': 6.0,\n", - " 'K_S': 8.0,\n", - " 'K_O_H': 0.2,\n", - " 'K_NO': 0.5,\n", - " 'b_H': 0.3,\n", - " 'mu_A': 0.5,\n", - " 'K_NH': 1.0,\n", - " 'K_O_A': 0.4,\n", - " 'b_A': 0.05,\n", - " 'eta_g': 0.8,\n", - " 'k_a': 0.05,\n", - " 'k_h': 3.0,\n", - " 'K_X': 0.1,\n", - " 'eta_h': 0.8},\n", - " '_dyn_params': {},\n", - " '_stoichiometry': [[0,\n", - " -1.25000000000000,\n", - " 0,\n", - " 0,\n", - " 1.00000000000000,\n", - " 0,\n", - " 0,\n", - " -0.250000000000000,\n", - " 0,\n", - " -0.0800000000000000,\n", - " 0,\n", - " 0,\n", - " -0.0685997415522571,\n", - " 0,\n", - " 0],\n", - " [0,\n", - " -1.25000000000000,\n", - " 0,\n", - " 0,\n", - " 1.00000000000000,\n", - " 0,\n", - " 0,\n", - " 0,\n", - " -0.0875000000000000,\n", - " -0.0800000000000001,\n", - " 0,\n", - " 0,\n", - " 0.00643122577052393,\n", - " 0.0875000000000000,\n", - " 0],\n", - " [0,\n", - " 0,\n", - " 0,\n", - " 0,\n", - " 0,\n", - " 1.00000000000000,\n", - " 0,\n", - " -18.0476190476190,\n", - " 4.16666666666667,\n", - " -4.24666666666667,\n", - " 0,\n", - " 0,\n", - " -7.21440615324570,\n", - " 0,\n", - " 0],\n", - " [0,\n", - " 0,\n", - " 0,\n", - " 0.920000000000000,\n", - " -1.00000000000000,\n", - " 0,\n", - " 0.0800000000000000,\n", - " 0,\n", - " 0,\n", - " 0,\n", - " 0,\n", - " 0.0752000000000000,\n", - " 0,\n", - " 0,\n", - " 0],\n", - " [0,\n", - " 0,\n", - " 0,\n", - " 0.920000000000000,\n", - " 0,\n", - " -1.00000000000000,\n", - " 0.0800000000000000,\n", - " 0,\n", - " 0,\n", - " 0,\n", - " 0,\n", - " 0.0752000000000000,\n", - " 0,\n", - " 0,\n", - " 0],\n", - " [0,\n", - " 0,\n", - " 0,\n", - " 0,\n", - " 0,\n", - " 0,\n", - " 0,\n", - " 0,\n", - " 0,\n", - " 1.00000000000000,\n", - " -1.00000000000000,\n", - " 0,\n", - " 0.857496769403214,\n", - " 0,\n", - " 0],\n", - " [0, 1.0, 0, -1.0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n", - " [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.0, -1.0, 0, 0, 0]],\n", - " '_stoichio_lambdified': None,\n", - " '_rate_equations': (S_NH*S_O*S_S*X_BH*mu_H/((K_NH + S_NH)*(K_O_H + S_O)*(K_S + S_S)),\n", - " K_O_H*S_NH*S_NO*S_S*X_BH*eta_g*mu_H/((K_NH + S_NH)*(K_NO + S_NO)*(K_O_H + S_O)*(K_S + S_S)),\n", - " S_NH*S_O*X_BA*mu_A/((K_NH + S_NH)*(K_O_A + S_O)),\n", - " X_BH*b_H,\n", - " X_BA*b_A,\n", - " S_ND*X_BH*k_a,\n", - " X_BH*X_S*k_h*(K_O_H*S_NO*eta_h/((K_NO + S_NO)*(K_O_H + S_O)) + S_O/(K_O_H + S_O))/(K_X*X_BH + X_S),\n", - " X_BH*X_ND*k_h*(K_O_H*S_NO*eta_h/((K_NO + S_NO)*(K_O_H + S_O)) + S_O/(K_O_H + S_O))/(K_X*X_BH + X_S)),\n", - " '_production_rates': [0,\n", - " -1.25*K_O_H*S_NH*S_NO*S_S*X_BH*eta_g*mu_H/((K_NH + S_NH)*(K_NO + S_NO)*(K_O_H + S_O)*(K_S + S_S)) - 1.25*S_NH*S_O*S_S*X_BH*mu_H/((K_NH + S_NH)*(K_O_H + S_O)*(K_S + S_S)) + 1.0*X_BH*X_S*k_h*(K_O_H*S_NO*eta_h/((K_NO + S_NO)*(K_O_H + S_O)) + S_O/(K_O_H + S_O))/(K_X*X_BH + X_S),\n", - " 0,\n", - " 0.92*X_BA*b_A - 1.0*X_BH*X_S*k_h*(K_O_H*S_NO*eta_h/((K_NO + S_NO)*(K_O_H + S_O)) + S_O/(K_O_H + S_O))/(K_X*X_BH + X_S) + 0.92*X_BH*b_H,\n", - " 1.0*K_O_H*S_NH*S_NO*S_S*X_BH*eta_g*mu_H/((K_NH + S_NH)*(K_NO + S_NO)*(K_O_H + S_O)*(K_S + S_S)) + 1.0*S_NH*S_O*S_S*X_BH*mu_H/((K_NH + S_NH)*(K_O_H + S_O)*(K_S + S_S)) - 1.0*X_BH*b_H,\n", - " 1.0*S_NH*S_O*X_BA*mu_A/((K_NH + S_NH)*(K_O_A + S_O)) - 1.0*X_BA*b_A,\n", - " 0.08*X_BA*b_A + 0.08*X_BH*b_H,\n", - " -0.25*S_NH*S_O*S_S*X_BH*mu_H/((K_NH + S_NH)*(K_O_H + S_O)*(K_S + S_S)) - 18.047619047619*S_NH*S_O*X_BA*mu_A/((K_NH + S_NH)*(K_O_A + S_O)),\n", - " -0.0875*K_O_H*S_NH*S_NO*S_S*X_BH*eta_g*mu_H/((K_NH + S_NH)*(K_NO + S_NO)*(K_O_H + S_O)*(K_S + S_S)) + 4.16666666666667*S_NH*S_O*X_BA*mu_A/((K_NH + S_NH)*(K_O_A + S_O)),\n", - " -0.0800000000000001*K_O_H*S_NH*S_NO*S_S*X_BH*eta_g*mu_H/((K_NH + S_NH)*(K_NO + S_NO)*(K_O_H + S_O)*(K_S + S_S)) + 1.0*S_ND*X_BH*k_a - 0.08*S_NH*S_O*S_S*X_BH*mu_H/((K_NH + S_NH)*(K_O_H + S_O)*(K_S + S_S)) - 4.24666666666667*S_NH*S_O*X_BA*mu_A/((K_NH + S_NH)*(K_O_A + S_O)),\n", - " -1.0*S_ND*X_BH*k_a + 1.0*X_BH*X_ND*k_h*(K_O_H*S_NO*eta_h/((K_NO + S_NO)*(K_O_H + S_O)) + S_O/(K_O_H + S_O))/(K_X*X_BH + X_S),\n", - " 0.0752*X_BA*b_A - 1.0*X_BH*X_ND*k_h*(K_O_H*S_NO*eta_h/((K_NO + S_NO)*(K_O_H + S_O)) + S_O/(K_O_H + S_O))/(K_X*X_BH + X_S) + 0.0752*X_BH*b_H,\n", - " 0.00643122577052393*K_O_H*S_NH*S_NO*S_S*X_BH*eta_g*mu_H/((K_NH + S_NH)*(K_NO + S_NO)*(K_O_H + S_O)*(K_S + S_S)) + 0.857496769403214*S_ND*X_BH*k_a - 0.0685997415522571*S_NH*S_O*S_S*X_BH*mu_H/((K_NH + S_NH)*(K_O_H + S_O)*(K_S + S_S)) - 7.2144061532457*S_NH*S_O*X_BA*mu_A/((K_NH + S_NH)*(K_O_A + S_O)),\n", - " 0.0875*K_O_H*S_NH*S_NO*S_S*X_BH*eta_g*mu_H/((K_NH + S_NH)*(K_NO + S_NO)*(K_O_H + S_O)*(K_S + S_S)),\n", - " 0],\n", - " '_rate_function': None}" - ] - }, - "execution_count": 32, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "asm1.compile()\n", "asm1.__dict__" @@ -1300,181 +725,10 @@ }, { "cell_type": "code", - "execution_count": 33, + "execution_count": 32, "id": "027cbd6b", "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "

\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
S_IS_SX_IX_SX_BH...S_NDX_NDS_ALKS_N2H2O
aero_growth_hetero0-1.25001...00-0.068600
anox_growth_hetero0-1.25001...000.006430.08750
aero_growth_auto00000...00-7.2100
decay_hetero0000.92-1...00.0752000
decay_auto0000.920...00.0752000
ammonification00000...-100.85700
hydrolysis010-10...00000
hydrolysis_N00000...1-1000
\n", - "

8 rows × 15 columns

\n", - "
" - ], - "text/plain": [ - " S_I S_S X_I X_S X_BH ... S_ND X_ND S_ALK S_N2 H2O\n", - "aero_growth_hetero 0 -1.25 0 0 1 ... 0 0 -0.0686 0 0\n", - "anox_growth_hetero 0 -1.25 0 0 1 ... 0 0 0.00643 0.0875 0\n", - "aero_growth_auto 0 0 0 0 0 ... 0 0 -7.21 0 0\n", - "decay_hetero 0 0 0 0.92 -1 ... 0 0.0752 0 0 0\n", - "decay_auto 0 0 0 0.92 0 ... 0 0.0752 0 0 0\n", - "ammonification 0 0 0 0 0 ... -1 0 0.857 0 0\n", - "hydrolysis 0 1 0 -1 0 ... 0 0 0 0 0\n", - "hydrolysis_N 0 0 0 0 0 ... 1 -1 0 0 0\n", - "\n", - "[8 rows x 15 columns]" - ] - }, - "execution_count": 33, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# For example, the stoichiometric coefficients of all processes are compiled into a table that\n", "# is in consistent format as a Petersen matrix\n", @@ -1483,88 +737,10 @@ }, { "cell_type": "code", - "execution_count": 34, + "execution_count": 33, "id": "18541f64", "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
rate_equation
aero_growth_hetero6.0*S_NH*S_O*S_S*X_BH/((S_NH + ...
anox_growth_hetero0.96*S_NH*S_NO*S_S*X_BH/((S_NH ...
aero_growth_auto0.5*S_NH*S_O*X_BA/((S_NH + 1.0)...
decay_hetero0.3*X_BH
decay_auto0.05*X_BA
ammonification0.05*S_ND*X_BH
hydrolysis3.0*X_BH*X_S*(0.16*S_NO/((S_NO ...
hydrolysis_N3.0*X_BH*X_ND*(0.16*S_NO/((S_NO...
\n", - "
" - ], - "text/plain": [ - " rate_equation\n", - "aero_growth_hetero 6.0*S_NH*S_O*S_S*X_BH/((S_NH + ...\n", - "anox_growth_hetero 0.96*S_NH*S_NO*S_S*X_BH/((S_NH ...\n", - "aero_growth_auto 0.5*S_NH*S_O*X_BA/((S_NH + 1.0)...\n", - "decay_hetero 0.3*X_BH\n", - "decay_auto 0.05*X_BA\n", - "ammonification 0.05*S_ND*X_BH\n", - "hydrolysis 3.0*X_BH*X_S*(0.16*S_NO/((S_NO ...\n", - "hydrolysis_N 3.0*X_BH*X_ND*(0.16*S_NO/((S_NO..." - ] - }, - "execution_count": 34, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# Similarly for the rate equations\n", "asm1.rate_equations" @@ -1572,21 +748,10 @@ }, { "cell_type": "code", - "execution_count": 35, + "execution_count": 34, "id": "a872533e", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "array([0.278, 0.03 , 0.179, 0.3 , 0.05 , 0.05 , 2.515, 2.515])" - ] - }, - "execution_count": 35, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# More importantly, the `rate_function` attribute of a `CompiledProcesses` now outputs an array\n", "# with each element corresponding orderly to the individual processes\n", @@ -1613,123 +778,10 @@ }, { "cell_type": "code", - "execution_count": 36, + "execution_count": 35, "id": "81541f71", "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
rate_of_production
S_I0
S_S-1.2*S_NH*S_NO*S_S*X_BH/((S_NH ...
X_I0
X_S0.046*X_BA - 3.0*X_BH*X_S*(0.16...
X_BH0.96*S_NH*S_NO*S_S*X_BH/((S_NH ...
X_BA0.5*S_NH*S_O*X_BA/((S_NH + 1.0)...
X_P0.004*X_BA + 0.024*X_BH
S_O-1.5*S_NH*S_O*S_S*X_BH/((S_NH +...
S_NO-0.084*S_NH*S_NO*S_S*X_BH/((S_N...
S_NH0.05*S_ND*X_BH - 0.076800000000...
S_ND-0.05*S_ND*X_BH + 3.0*X_BH*X_ND...
X_ND0.00376*X_BA - 3.0*X_BH*X_ND*(0...
S_ALK0.0428748384701607*S_ND*X_BH + ...
S_N20.084*S_NH*S_NO*S_S*X_BH/((S_NH...
H2O0
\n", - "
" - ], - "text/plain": [ - " rate_of_production\n", - "S_I 0\n", - "S_S -1.2*S_NH*S_NO*S_S*X_BH/((S_NH ...\n", - "X_I 0\n", - "X_S 0.046*X_BA - 3.0*X_BH*X_S*(0.16...\n", - "X_BH 0.96*S_NH*S_NO*S_S*X_BH/((S_NH ...\n", - "X_BA 0.5*S_NH*S_O*X_BA/((S_NH + 1.0)...\n", - "X_P 0.004*X_BA + 0.024*X_BH\n", - "S_O -1.5*S_NH*S_O*S_S*X_BH/((S_NH +...\n", - "S_NO -0.084*S_NH*S_NO*S_S*X_BH/((S_N...\n", - "S_NH 0.05*S_ND*X_BH - 0.076800000000...\n", - "S_ND -0.05*S_ND*X_BH + 3.0*X_BH*X_ND...\n", - "X_ND 0.00376*X_BA - 3.0*X_BH*X_ND*(0...\n", - "S_ALK 0.0428748384701607*S_ND*X_BH + ...\n", - "S_N2 0.084*S_NH*S_NO*S_S*X_BH/((S_NH...\n", - "H2O 0" - ] - }, - "execution_count": 36, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# This matrix operation is already streamlined for `CompiledProcesses` objects, you can see the \n", "# mathematical form of the rates of production as a function of component concentrations.\n", @@ -1738,23 +790,10 @@ }, { "cell_type": "code", - "execution_count": 37, + "execution_count": 36, "id": "b508e20f", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "array([ 0.000e+00, 2.131e+00, 0.000e+00, -2.193e+00, 7.407e-03,\n", - " 1.286e-01, 2.800e-02, -3.292e+00, 7.415e-01, -7.329e-01,\n", - " 2.465e+00, -2.489e+00, -1.264e+00, 2.593e-03, 0.000e+00])" - ] - }, - "execution_count": 37, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# To evaluate the rates of production for all components, all you need to\n", "# do is to call the `production_rates_eval` method.\n", @@ -1829,18 +868,10 @@ }, { "cell_type": "code", - "execution_count": 45, + "execution_count": 37, "id": "cad5a69b", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "CompiledComponents([X_S, S_S, O2, CO2, X_B, H2O])\n" - ] - } - ], + "outputs": [], "source": [ "# Load the default set of components\n", "cmps_all = qs.Components.load_default()\n", @@ -1868,21 +899,10 @@ }, { "cell_type": "code", - "execution_count": 39, + "execution_count": 38, "id": "494eb3e8", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 39, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# Now we can check if their `measured_as` attributes are correctly set\n", "# X_S.measured_as == 'COD'\n", @@ -1892,21 +912,10 @@ }, { "cell_type": "code", - "execution_count": 40, + "execution_count": 39, "id": "2de7d166", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "-1.0" - ] - }, - "execution_count": 40, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# Then you can check relevant `i_` properties of the components\n", "# For example, O2 should have a negative COD content, or more specifically -1 gCOD/gO2\n", @@ -1927,7 +936,7 @@ }, { "cell_type": "code", - "execution_count": 47, + "execution_count": 40, "id": "7b264f76", "metadata": {}, "outputs": [], @@ -1957,7 +966,7 @@ }, { "cell_type": "code", - "execution_count": 48, + "execution_count": 41, "id": "1906bcd6", "metadata": {}, "outputs": [], @@ -1976,7 +985,7 @@ }, { "cell_type": "code", - "execution_count": 49, + "execution_count": 42, "id": "7b7437a4", "metadata": {}, "outputs": [], @@ -2005,7 +1014,7 @@ }, { "cell_type": "code", - "execution_count": 50, + "execution_count": 43, "id": "4f98d569", "metadata": {}, "outputs": [], @@ -2028,7 +1037,7 @@ }, { "cell_type": "code", - "execution_count": 51, + "execution_count": 44, "id": "c7ce74d2", "metadata": {}, "outputs": [], @@ -2049,24 +1058,10 @@ }, { "cell_type": "code", - "execution_count": 52, + "execution_count": 45, "id": "e7facea8", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Process: hydrolysis\n", - "[stoichiometry] X_S: -1\n", - " S_S: 1\n", - "[reference] X_S\n", - "[rate equation] X_S*k_hyd\n", - "[parameters] k_hyd: k_hyd\n", - "[dynamic parameters] \n" - ] - } - ], + "outputs": [], "source": [ "# Upon initiation, the parameters are stored as symbols. We still need to set values to them\n", "# before we can evalute process rate.\n", @@ -2075,28 +1070,10 @@ }, { "cell_type": "code", - "execution_count": 53, + "execution_count": 46, "id": "9d199cf0", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Process: growth\n", - "[stoichiometry] S_S: -1/y_B\n", - " O2: (y_B - 1.0)/y_B\n", - " CO2: 0.002*(160.0 - 183.0*y_B)/y_B\n", - " X_B: 1.00\n", - "[reference] X_B\n", - "[rate equation] S_S*X_B*mu_B/(K_S + S_S)\n", - "[parameters] y_B: y_B\n", - " mu_B: mu_B\n", - " K_S: K_S\n", - "[dynamic parameters] \n" - ] - } - ], + "outputs": [], "source": [ "# At this point, the initiation of process 3 should be quite straightforward.\n", "# Here shows an alternative way to input stoichiometry\n", @@ -2137,20 +1114,12 @@ }, { "cell_type": "code", - "execution_count": 54, + "execution_count": 47, "id": "18d7995b", "metadata": { "scrolled": true }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "CompiledProcesses([hydrolysis, growth, decay])\n" - ] - } - ], + "outputs": [], "source": [ "# Now the final step is to compile the individual processes into a biokinetic model\n", "bkm = qs.Processes([pc1, pc2, pc3])\n", @@ -2160,21 +1129,10 @@ }, { "cell_type": "code", - "execution_count": 55, + "execution_count": 48, "id": "0a4159a3", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'k_hyd': k_hyd, 'y_B': y_B, 'mu_B': mu_B, 'K_S': K_S, 'b': b}" - ] - }, - "execution_count": 55, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# Parameters in the stoichiometry and rate equations across all processes are compiled into\n", "# a shared dictionary.\n", @@ -2185,83 +1143,10 @@ }, { "cell_type": "code", - "execution_count": 56, + "execution_count": 49, "id": "6bec6d90", "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
X_SS_SO2CO2X_BH2O
hydrolysis-110000
growth0-1.25-0.250.03410
decay1000-10
\n", - "
" - ], - "text/plain": [ - " X_S S_S O2 CO2 X_B H2O\n", - "hydrolysis -1 1 0 0 0 0\n", - "growth 0 -1.25 -0.25 0.034 1 0\n", - "decay 1 0 0 0 -1 0" - ] - }, - "execution_count": 56, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# After setting parameter values, the model will be ready\n", "bkm.set_parameters(k_hyd=3.0, y_B=0.8, mu_B=4.0, K_S=9.0, b=0.4)\n", @@ -2270,78 +1155,10 @@ }, { "cell_type": "code", - "execution_count": 57, + "execution_count": 50, "id": "f13307e3", "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
rate_of_production
X_S0.4*X_B - 3.0*X_S
S_S-5.0*S_S*X_B/(S_S + 9.0) + 3.0*X_S
O2-1.0*S_S*X_B/(S_S + 9.0)
CO20.136*S_S*X_B/(S_S + 9.0)
X_B4.0*S_S*X_B/(S_S + 9.0) - 0.4*X_B
H2O0
\n", - "
" - ], - "text/plain": [ - " rate_of_production\n", - "X_S 0.4*X_B - 3.0*X_S\n", - "S_S -5.0*S_S*X_B/(S_S + 9.0) + 3.0*X_S\n", - "O2 -1.0*S_S*X_B/(S_S + 9.0)\n", - "CO2 0.136*S_S*X_B/(S_S + 9.0)\n", - "X_B 4.0*S_S*X_B/(S_S + 9.0) - 0.4*X_B\n", - "H2O 0" - ] - }, - "execution_count": 57, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "bkm.production_rates" ] @@ -2365,91 +1182,14 @@ }, { "cell_type": "code", - "execution_count": 58, + "execution_count": 51, "id": "1b8ebfca", "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
X_SS_SO2CO2X_BH2OUnnamed: 7
hydrolysis-110000k_hyd*X_S
growth0(-1)/y_B??10mu_B*S_S/(K_S + S_S)*X_B
decay1000-10b*X_B
\n", - "
" - ], - "text/plain": [ - " X_S S_S O2 CO2 X_B H2O Unnamed: 7\n", - "hydrolysis -1 1 0 0 0 0 k_hyd*X_S\n", - "growth 0 (-1)/y_B ? ? 1 0 mu_B*S_S/(K_S + S_S)*X_B\n", - "decay 1 0 0 0 -1 0 b*X_B" - ] - }, - "execution_count": 58, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# Stoichiometry and rate equations are usually described in a table format\n", "from qsdsan.utils import load_data\n", - "df_bkm = load_data('_bkm.tsv', index_col=0)\n", + "df_bkm = load_data('assets/_bkm.tsv', index_col=0)\n", "df_bkm" ] }, @@ -2463,22 +1203,14 @@ }, { "cell_type": "code", - "execution_count": 59, + "execution_count": 52, "id": "8703f7bf", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "CompiledProcesses([hydrolysis, growth, decay])\n" - ] - } - ], + "outputs": [], "source": [ "# The same amount of information is still required to create the model.\n", "bkm_batch = qs.Processes.load_from_file(\n", - " path='_bkm.tsv', \n", + " path='assets/_bkm.tsv', \n", " conserved_for=('COD', 'C'),\n", " parameters=('k_hyd', 'y_B', 'mu_B', 'K_S', 'b'),\n", " compile=True\n", @@ -2488,83 +1220,10 @@ }, { "cell_type": "code", - "execution_count": 60, + "execution_count": 53, "id": "4105ea73", "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
X_SS_SO2CO2X_BH2O
hydrolysis-110000
growth0-1.0/y_B1.0*(y_B - 1.0)/y_B0.002*(160.0 - 183.0*y_B)/y_B1.000000000000000
decay1000-10
\n", - "
" - ], - "text/plain": [ - " X_S S_S O2 CO2 X_B H2O\n", - "hydrolysis -1 1 0 0 0 0\n", - "growth 0 -1.0/y_B 1.0*(y_B - 1.0)/y_B 0.002*(160.0 - 183.0*y_B)/y_B 1.00000000000000 0\n", - "decay 1 0 0 0 -1 0" - ] - }, - "execution_count": 60, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# You can see `bkm_batch` is equivalent to the `bkm` we created above\n", "bkm_batch.stoichiometry\n", @@ -2574,21 +1233,10 @@ }, { "cell_type": "code", - "execution_count": 61, + "execution_count": 54, "id": "ba36c5ed", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'X_S'" - ] - }, - "execution_count": 61, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# The reference component of each process is inferred from its stoichiometry\n", "bkm_batch.decay.ref_component" @@ -2596,26 +1244,14 @@ }, { "cell_type": "code", - "execution_count": 62, + "execution_count": 55, "id": "57c80cc6", "metadata": {}, - "outputs": [ - { - "ename": "RuntimeError", - "evalue": "The following materials are unconserved by the stoichiometric coefficients. A positive value means the material is created, a negative value means the material is destroyed:\n C: -0.05", - "output_type": "error", - "traceback": [ - "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[1;31mRuntimeError\u001b[0m Traceback (most recent call last)", - "\u001b[1;32m~\\AppData\\Local\\Temp\\ipykernel_8464\\355048950.py\u001b[0m in \u001b[0;36m\u001b[1;34m\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[1;31m# `conserved_for` now applies to all processes\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 2\u001b[1;33m \u001b[0mbkm_batch\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mdecay\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mcheck_conservation\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", - "\u001b[1;32m~\\anaconda3\\envs\\tut\\lib\\site-packages\\qsdsan\\_process.py\u001b[0m in \u001b[0;36mcheck_conservation\u001b[1;34m(self, rtol, atol)\u001b[0m\n\u001b[0;32m 484\u001b[0m \u001b[0mmaterials\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_conserved_for\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 485\u001b[0m \u001b[0munconserved\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;33m[\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mmaterials\u001b[0m\u001b[1;33m[\u001b[0m\u001b[0mi\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mic_dot_v\u001b[0m\u001b[1;33m[\u001b[0m\u001b[0mi\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m)\u001b[0m \u001b[1;32mfor\u001b[0m \u001b[0mi\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mconserved\u001b[0m \u001b[1;32min\u001b[0m \u001b[0menumerate\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mconserved_arr\u001b[0m\u001b[1;33m)\u001b[0m \u001b[1;32mif\u001b[0m \u001b[1;32mnot\u001b[0m \u001b[0mconserved\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 486\u001b[1;33m raise RuntimeError(\"The following materials are unconserved by the \"\n\u001b[0m\u001b[0;32m 487\u001b[0m \u001b[1;34m\"stoichiometric coefficients. A positive value \"\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 488\u001b[0m \u001b[1;34m\"means the material is created, a negative value \"\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[1;31mRuntimeError\u001b[0m: The following materials are unconserved by the stoichiometric coefficients. A positive value means the material is created, a negative value means the material is destroyed:\n C: -0.05" - ] - } - ], + "outputs": [], "source": [ - "# `conserved_for` now applies to all processes\n", - "bkm_batch.decay.check_conservation()" + "# `conserved_for` now applies to all processes,\n", + "# the following will trigger an error\n", + "# bkm_batch.decay.check_conservation()" ] }, { @@ -2628,21 +1264,10 @@ }, { "cell_type": "code", - "execution_count": 63, + "execution_count": 56, "id": "cf5c08b4", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'X_S': 0.32, 'S_S': 0.32, 'O2': 0.0, 'CO2': 1.0, 'X_B': 0.366, 'H2O': 0.0}" - ] - }, - "execution_count": 63, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# `i_C` values for each component\n", "dict(zip(cmps_bkm.IDs, cmps_bkm.i_C))" @@ -2675,7 +1300,7 @@ }, { "cell_type": "code", - "execution_count": 64, + "execution_count": 57, "id": "29c15cb9", "metadata": {}, "outputs": [], @@ -2700,21 +1325,10 @@ }, { "cell_type": "code", - "execution_count": 65, + "execution_count": 58, "id": "7fe90104", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'k_hyd': 3.0, 'y_B': 0.8, 'mu_B': 4.0, 'K_S': 9.0, 'b': 0.4}" - ] - }, - "execution_count": 65, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# At this point, the value for `y_B` is not updated yet, since we haven't evalutated it\n", "# with input of component concentrations\n", @@ -2723,30 +1337,10 @@ }, { "cell_type": "code", - "execution_count": 66, + "execution_count": 59, "id": "9121e9cc", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Process: growth\n", - "[stoichiometry] S_S: -1/y_B\n", - " O2: (y_B - 1.0)/y_B\n", - " CO2: 0.002*(160.0 - 183.0*y_B)/y_B\n", - " X_B: 1.00\n", - "[reference] X_B\n", - "[rate equation] S_S*X_B*mu_B/(K_S + S_S)\n", - "[parameters] k_hyd: 3\n", - " y_B: 0.8\n", - " mu_B: 4\n", - " K_S: 9\n", - " b: 0.4\n", - "[dynamic parameters] \n" - ] - } - ], + "outputs": [], "source": [ "# But the list of dynamic parameters have been updated\n", "pc2.show()" @@ -2754,21 +1348,10 @@ }, { "cell_type": "code", - "execution_count": 67, + "execution_count": 60, "id": "248e0922", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "qsdsan._process.DynamicParameter" - ] - }, - "execution_count": 67, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# `y_B` is now a `DynamicParameter` object stored in the `_dyn_params` attribute of the process\n", "type(pc2._dyn_params['y_B'])" @@ -2776,23 +1359,12 @@ }, { "cell_type": "code", - "execution_count": 68, + "execution_count": 61, "id": "9dc27821", "metadata": { "scrolled": false }, - "outputs": [ - { - "data": { - "text/plain": [ - "{'k_hyd': 3.0, 'y_B': 0.5656854249492381, 'mu_B': 4.0, 'K_S': 9.0, 'b': 0.4}" - ] - }, - "execution_count": 68, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# Assuming component concentrations are all 1.\n", "state_bkm = np.ones(6)\n", @@ -2804,83 +1376,10 @@ }, { "cell_type": "code", - "execution_count": 69, + "execution_count": 62, "id": "d765098e", "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
X_SS_SO2CO2X_BH2O
hydrolysis-110000
growth0-1.77-0.7680.210
decay1000-10
\n", - "
" - ], - "text/plain": [ - " X_S S_S O2 CO2 X_B H2O\n", - "hydrolysis -1 1 0 0 0 0\n", - "growth 0 -1.77 -0.768 0.2 1 0\n", - "decay 1 0 0 0 -1 0" - ] - }, - "execution_count": 69, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# Then the stoichiometry is updated accordingly\n", "bkm.stoichiometry" @@ -2909,21 +1408,10 @@ }, { "cell_type": "code", - "execution_count": 70, + "execution_count": 63, "id": "cb7c404e", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 70, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# The use of `Kinetics` to define process rate is also similar\n", "# For example, for the decay process\n", @@ -2955,21 +1443,10 @@ }, { "cell_type": "code", - "execution_count": 71, + "execution_count": 64, "id": "5fdddd88", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "array([1.732, 2.309, 0.369])" - ] - }, - "execution_count": 71, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# Define the function\n", "def rhos_eval(state_arr, params):\n", @@ -3001,9 +1478,9 @@ ], "metadata": { "kernelspec": { - "display_name": "Python [conda env:tut]", + "display_name": "Python 3 (ipykernel)", "language": "python", - "name": "conda-env-tut-py" + "name": "python3" }, "language_info": { "codemirror_mode": { @@ -3015,7 +1492,36 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.0" + "version": "3.9.13" + }, + "varInspector": { + "cols": { + "lenName": 16, + "lenType": 16, + "lenVar": 40 + }, + "kernels_config": { + "python": { + "delete_cmd_postfix": "", + "delete_cmd_prefix": "del ", + "library": "var_list.py", + "varRefreshCmd": "print(var_dic_list())" + }, + "r": { + "delete_cmd_postfix": ") ", + "delete_cmd_prefix": "rm(", + "library": "var_list.r", + "varRefreshCmd": "cat(var_dic_list()) " + } + }, + "types_to_exclude": [ + "module", + "function", + "builtin_function_or_method", + "instance", + "_Feature" + ], + "window_display": false } }, "nbformat": 4, diff --git a/docs/source/tutorials/11_Dynamic_Simulation.ipynb b/docs/source/tutorials/11_Dynamic_Simulation.ipynb index aed3ea92..57072991 100644 --- a/docs/source/tutorials/11_Dynamic_Simulation.ipynb +++ b/docs/source/tutorials/11_Dynamic_Simulation.ipynb @@ -3,13 +3,17 @@ { "cell_type": "markdown", "id": "28c4658c", - "metadata": {}, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, "source": [ "# Dynamic Simulation \n", "\n", "- **Prepared by:**\n", " \n", - " - [Joy Zhang](https://qsdsan.readthedocs.io/en/latest/authors/Joy_Zhang.html)\n", + " - [Joy Zhang](https://qsdsan.readthedocs.io/en/latest/CONTRIBUTING.html)\n", " \n", "- **Covered topics:**\n", "\n", @@ -19,17 +23,21 @@ " \n", "- **Video demo:**\n", "\n", - " - To be posted\n", + " - [Yalin Li](https://qsdsan.readthedocs.io/en/latest/CONTRIBUTING.html)\n", " \n", - "To run tutorials in your browser, go to this [Binder page](https://mybinder.org/v2/gh/QSD-Group/QSDsan/main?filepath=%2Fdocs%2Fsource%2Ftutorials).\n", + "To run tutorials in your browser, go to this [Binder page](https://mybinder.org/v2/gh/QSD-Group/QSDsan-env/main?urlpath=git-pull%3Frepo%3Dhttps%253A%252F%252Fgithub.com%252FQSD-group%252FQSDsan%26urlpath%3Dtree%252FQSDsan%252Fdocs%252Fsource%252Ftutorials%26branch%3Dmain).\n", " \n", - "You can also watch a video demo on YouTube (link to be posted) (subscriptions & likes appreciated!)." + "You can also watch a video demo on [YouTube](https://youtu.be/1Rr1QxUiE5k) (subscriptions & likes appreciated!)." ] }, { "cell_type": "markdown", "id": "2bc790e7", - "metadata": {}, + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, "source": [ "---\n", "From previous tutorials, we've covered how to use QSDsan's [SanUnit](https://qsdsan.readthedocs.io/en/latest/tutorials/5_SanUnit_advanced.html) and [WasteStream](https://qsdsan.readthedocs.io/en/latest/tutorials/3_WasteStream.html) classes to model the mass/energy flows throughout a system. You may have noticed, the simulation results generated by `SanUnit._run` are **static**, i.e., they don't carry time-related information. \n", @@ -41,13 +49,17 @@ "cell_type": "code", "execution_count": 1, "id": "3dc1138e", - "metadata": {}, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "This tutorial was made with qsdsan v1.2.5 and exposan v1.2.5\n" + "This tutorial was made with qsdsan v1.3.1 and exposan v1.3.1\n" ] } ], @@ -59,15 +71,29 @@ { "cell_type": "markdown", "id": "b7f9ccfc", - "metadata": {}, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, "source": [ "## 1. Understanding dynamic simulation with QSDsan \n", "\n", "### 1.1. An example system\n", "Let's use [Benchmark Simulation Model no.1 (BSM1)](http://iwa-mia.org/benchmarking/#BSM1) as an example. BSM1 describes an activated sludge treatment process that can be commonly found in conventional wastewater treatment facilities. The full system has been implemented in [EXPOsan](https://github.com/QSD-Group/EXPOsan/tree/main/exposan/bsm1).\n", "\n", - "The activated sludge process is often characterized as a series of biokinetic reactions in parallel (recap on `Process` [here](https://qsdsan.readthedocs.io/en/latest/tutorials/10_Process.html)). The mathematical models of this kind cannot output mass flows or concentrations directly as a function of input. But rather, they describe the rates of change in state variables at any time as a function of the state variables (often concentrations). As a result, simulation of such systems involves solving a series of ordinary differential equations (ODEs). We have developed features in QSDsan for this specific purpose.\n", - "\n", + "The activated sludge process is often characterized as a series of biokinetic reactions in parallel (recap on `Process` [here](https://qsdsan.readthedocs.io/en/latest/tutorials/10_Process.html)). The mathematical models of this kind cannot output mass flows or concentrations directly as a function of input. But rather, they describe the rates of change in state variables at any time as a function of the state variables (often concentrations). As a result, simulation of such systems involves solving a series of ordinary differential equations (ODEs). We have developed features in QSDsan for this specific purpose." + ] + }, + { + "cell_type": "markdown", + "id": "a8a07c91", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ "#### 1.1.1. Running dynamic simulation" ] }, @@ -75,7 +101,11 @@ "cell_type": "code", "execution_count": 2, "id": "a1c82016", - "metadata": {}, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, "outputs": [ { "name": "stdout", @@ -83,7 +113,7 @@ "text": [ "System: bsm1_sys\n", "ins...\n", - "[0] wastewater\n", + "[0] wastewater \n", " phase: 'l', T: 293.15 K, P: 101325 Pa\n", " flow (kmol/hr): S_I 23.1\n", " S_S 53.4\n", @@ -94,10 +124,10 @@ " S_ND 0.381\n", " ... 4.26e+04\n", "outs...\n", - "[0] effluent\n", + "[0] effluent \n", " phase: 'l', T: 293.15 K, P: 101325 Pa\n", " flow: 0\n", - "[1] WAS\n", + "[1] WAS \n", " phase: 'l', T: 293.15 K, P: 101325 Pa\n", " flow: 0\n" ] @@ -115,178 +145,236 @@ "cell_type": "code", "execution_count": 3, "id": "61ef9ac7", - "metadata": {}, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, "outputs": [ { "data": { "image/svg+xml": [ - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "A1CSTR:c->A2CSTR:c\n", - "\n", - "\n", + "A1\n", + "CSTR:c->A2\n", + "CSTR:c\n", + "\n", + "\n", "\n", - " ws1\n", + " ws1\n", "\n", "\n", "\n", - "\n", + "\n", "\n", - "A2CSTR:c->O1CSTR:c\n", - "\n", - "\n", + "A2\n", + "CSTR:c->O1\n", + "CSTR:c\n", + "\n", + "\n", "\n", - " ws3\n", + " ws3\n", "\n", "\n", "\n", - "\n", + "\n", "\n", - "O1CSTR:c->O2CSTR:c\n", - "\n", - "\n", + "O1\n", + "CSTR:c->O2\n", + "CSTR:c\n", + "\n", + "\n", "\n", - " ws5\n", + " ws5\n", "\n", "\n", "\n", - "\n", + "\n", "\n", - "O2CSTR:c->O3CSTR:c\n", - "\n", - "\n", + "O2\n", + "CSTR:c->O3\n", + "CSTR:c\n", + "\n", + "\n", "\n", - " ws7\n", + " ws7\n", "\n", "\n", "\n", - "\n", + "\n", "\n", - "O3CSTR:c->A1CSTR:c\n", - "\n", - "\n", + "O3\n", + "CSTR:c->A1\n", + "CSTR:c\n", + "\n", + "\n", "\n", - " RWW\n", + " RWW\n", "\n", "\n", "\n", - "\n", + "\n", "\n", - "O3CSTR:c->C1Flat bottom circular clarifier:c\n", - "\n", - "\n", + "O3\n", + "CSTR:c->C1\n", + "Flat bottom circular clarifier:c\n", + "\n", + "\n", "\n", - " treated\n", + " treated\n", "\n", "\n", "\n", - "\n", + "\n", "\n", - "C1Flat bottom circular clarifier:c->A1CSTR:c\n", - "\n", - "\n", + "C1\n", + "Flat bottom circular clarifier:c->A1\n", + "CSTR:c\n", + "\n", + "\n", "\n", - " RAS\n", + " RAS\n", "\n", "\n", "\n", - "\n", + "\n", "\n", - "C1Flat bottom circular clarifier:c-> effluent:w\n", - "\n", + "C1\n", + "Flat bottom circular clarifier:c->121356496865:w\n", + "\n", "\n", - " effluent\n", + " effluent\n", "\n", "\n", "\n", - "\n", + "\n", "\n", - "C1Flat bottom circular clarifier:c-> WAS:w\n", - "\n", + "C1\n", + "Flat bottom circular clarifier:c->121356496705:w\n", + "\n", "\n", - " WAS\n", + " WAS\n", "\n", "\n", "\n", - "\n", + "\n", "\n", - " wastewater:e->A1CSTR:c\n", - "\n", - "\n", + "121356497265:e->A1\n", + "CSTR:c\n", + "\n", + "\n", " wastewater\n", "\n", "\n", "\n", - "\n", + "\n", "\n", - "A1CSTR\n", + "A1\n", + "CSTR\n", "\n", - "\n", - "A1CSTR\n", + "\n", + "A1\n", + "CSTR\n", "\n", "\n", "\n", - "\n", + "\n", "\n", - "A2CSTR\n", + "A2\n", + "CSTR\n", "\n", - "\n", - "A2CSTR\n", + "\n", + "A2\n", + "CSTR\n", "\n", "\n", "\n", - "\n", + "\n", "\n", - "O1CSTR\n", + "O1\n", + "CSTR\n", "\n", - "\n", - "O1CSTR\n", + "\n", + "O1\n", + "CSTR\n", "\n", "\n", "\n", - "\n", + "\n", "\n", - "O2CSTR\n", + "O2\n", + "CSTR\n", "\n", - "\n", - "O2CSTR\n", + "\n", + "O2\n", + "CSTR\n", "\n", "\n", "\n", - "\n", + "\n", "\n", - "O3CSTR\n", + "O3\n", + "CSTR\n", "\n", - "\n", - "O3CSTR\n", + "\n", + "O3\n", + "CSTR\n", "\n", "\n", "\n", - "\n", + "\n", "\n", - "C1Flat bottom circular clarifier\n", + "C1\n", + "Flat bottom circular clarifier\n", "\n", - "\n", - "C1Flat bottom circular clarifier\n", + "\n", + "C1\n", + "Flat bottom circular clarifier\n", "\n", "\n", "\n", - "\n", + "\n", "\n", - " wastewater\n", + "121356497265\n", "\n", "\n", - "\n", + "\n", "\n", - " effluent\n", - "\n", + "121356496865\n", + "\n", "\n", - "\n", + "\n", "\n", - " WAS\n", - "\n", + "121356496705\n", + "\n", "\n", "\n", "" @@ -302,15 +390,19 @@ "source": [ "# The BSM1 system is composed of 5 CSTRs in series, \n", "# followed by a flat-bottom circular clarifier.\n", - "sys.diagram()\n", - "# sys.units" + "# sys.units\n", + "sys.diagram()" ] }, { "cell_type": "code", "execution_count": 4, "id": "03c7b593", - "metadata": {}, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, "outputs": [], "source": [ "# If we try to simulate it like we'd do for a \"static\" system\n", @@ -320,16 +412,24 @@ { "cell_type": "markdown", "id": "07f91f64", - "metadata": {}, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, "source": [ "We run into this error because QSDsan (essentially biosteam in the background) considers this system dynamic, and additional arguments are required for `simulate` to work." ] }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 5, "id": "b349b9a3", - "metadata": {}, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, "outputs": [ { "data": { @@ -337,7 +437,7 @@ "True" ] }, - "execution_count": 4, + "execution_count": 5, "metadata": {}, "output_type": "execute_result" } @@ -349,52 +449,64 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 6, "id": "43772dae", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{: True,\n", - " : True,\n", - " : True,\n", - " : True,\n", - " : True,\n", - " : True}" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" + "metadata": { + "slideshow": { + "slide_type": "slide" } - ], + }, + "outputs": [], "source": [ "# This is because the system contains at least one dynamic SanUnit\n", - "{u: u.isdynamic for u in sys.units}\n", + "# {u: u.isdynamic for u in sys.units}\n", "\n", "# If we disable dynamic simulation, then `simulate` would work as usual\n", - "# sys.isdynamic = False\n", - "# sys.simulate()" + "sys.isdynamic = False\n", + "sys.simulate()" ] }, { "cell_type": "markdown", "id": "cc28e85f", - "metadata": {}, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, "source": [ "To perform a dynamic simulation of the system, we need to provide at least one additional keyword argument, i.e., `t_span`, as suggested in the error message. `t_span` is a 2-tuple indicating the simulation period.\n", "\n", - ">**Note**: Whether `t_span = (0,10)` means 0-10 days or 0-10 hours/minutes/months depends entirely on units of the parameters in the system's ODEs. For BSM1, it'd mean 0-10 days because all parameters in the ODEs express time in the unit of \"day\".\n", - "\n", + ">**Note**: Whether `t_span = (0,10)` means 0-10 days or 0-10 hours/minutes/months depends entirely on units of the parameters in the system's ODEs. For BSM1, it'd mean 0-10 days because all parameters in the ODEs express time in the unit of \"day\"." + ] + }, + { + "cell_type": "markdown", + "id": "0c111c81", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ "Other often-used keyword arguments include:\n", "\n", "- `t_eval`: a 1d array to specify the output time points\n", "- `method`: a string specifying the ODE solver\n", "- `state_reset_hook`: specifies how to reset the simulation\n", "\n", - "`t_span`, `t_eval`, and `method` are essentially passed to [scipy.integrate.solve_ivp](https://docs.scipy.org/doc/scipy/reference/generated/scipy.integrate.solve_ivp.html) function as keyword arguments. See [documentation](https://biosteam.readthedocs.io/en/latest/API/System.html#biosteam.System.dynamic_run) for a complete list of keyword arguments. You may notice that `scipy.integrate.solve_ivp` also requires input of `fun` (i.e., the ODEs) and `y0` (i.e., the initial condition). We'll learn later how `System.simulate` automates the compilation of these inputs.\n", - "\n", + "`t_span`, `t_eval`, and `method` are essentially passed to [scipy.integrate.solve_ivp](https://docs.scipy.org/doc/scipy/reference/generated/scipy.integrate.solve_ivp.html) function as keyword arguments. See [documentation](https://biosteam.readthedocs.io/en/latest/API/System.html#biosteam.System.dynamic_run) for a complete list of keyword arguments. You may notice that `scipy.integrate.solve_ivp` also requires input of `fun` (i.e., the ODEs) and `y0` (i.e., the initial condition). We'll learn later how `System.simulate` automates the compilation of these inputs." + ] + }, + { + "cell_type": "markdown", + "id": "9c8b4556", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ "---\n", "### Tip\n", "For systems that are expected to converge to some sort of \"steady state\", it is usually faster to simulate with implicit ODE solvers (e.g., `method = BDF` or `method = LSODA`) than with explicit ones. In case of one solver fails to complete integration through the entire specified simulation period, always try with alternative ones.\n", @@ -404,9 +516,14 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 7, "id": "45ef4032", - "metadata": {}, + "metadata": { + "scrolled": true, + "slideshow": { + "slide_type": "slide" + } + }, "outputs": [ { "name": "stdout", @@ -415,10 +532,10 @@ "System: bsm1_sys\n", "Highest convergence error among components in recycle\n", "streams {C1-1, O3-0} after 5 loops:\n", - "- flow rate 7.28e-12 kmol/hr (7.6e-14%)\n", + "- flow rate 1.46e-11 kmol/hr (4.2e-14%)\n", "- temperature 0.00e+00 K (0%)\n", "ins...\n", - "[0] wastewater\n", + "[0] wastewater \n", " phase: 'l', T: 293.15 K, P: 101325 Pa\n", " flow (kmol/hr): S_I 23.1\n", " S_S 53.4\n", @@ -429,7 +546,7 @@ " S_ND 0.381\n", " ... 4.26e+04\n", "outs...\n", - "[0] effluent\n", + "[0] effluent \n", " phase: 'l', T: 293.15 K, P: 101325 Pa\n", " flow (kmol/hr): S_I 22.6\n", " S_S 0.67\n", @@ -439,7 +556,7 @@ " X_BA 0.43\n", " X_P 1.3\n", " ... 4.17e+04\n", - "[1] WAS\n", + "[1] WAS \n", " phase: 'l', T: 293.15 K, P: 101325 Pa\n", " flow (kmol/hr): S_I 0.481\n", " S_S 0.0143\n", @@ -448,12 +565,14 @@ " X_BH 80.3\n", " X_BA 4.69\n", " X_P 14.1\n", - " ... 885\n" + " ... 884\n" ] } ], "source": [ "# Let's try simulating the BSM1 system from day 0 to day 50\n", + "# user shorter time or try changing method to 'RK23' (explicit solver) if it takes a long time\n", + "sys.isdynamic = True\n", "sys.simulate(t_span=(0, 50), method='BDF', state_reset_hook='reset_cache')\n", "sys.show()" ] @@ -461,19 +580,25 @@ { "cell_type": "markdown", "id": "972442da", - "metadata": {}, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, "source": [ - "[Back to top](#top)\n", - "\n", "#### 1.1.2. Retrieve dynamic simulation data\n", "The `show` method only displays the system's state at the end of the simulation period. How do we retrieve information on system dynamics? QSDsan uses [Scope](https://qsdsan.readthedocs.io/en/latest/api/utils/scope.html) objects to keep track of values of state variables during simulation." ] }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 8, "id": "3d7a8b0d", - "metadata": {}, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, "outputs": [ { "data": { @@ -481,7 +606,7 @@ "(, )" ] }, - "execution_count": 7, + "execution_count": 8, "metadata": {}, "output_type": "execute_result" } @@ -494,9 +619,13 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 9, "id": "5fedeb57", - "metadata": {}, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, "outputs": [ { "data": { @@ -504,7 +633,7 @@ "" ] }, - "execution_count": 8, + "execution_count": 9, "metadata": {}, "output_type": "execute_result" } @@ -521,13 +650,17 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 10, "id": "a7c7fa4d", - "metadata": {}, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, "outputs": [ { "data": { - "image/png": "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8m+//Xb9n3/+qVq1apXlk08+scyZM0fXkgBMRNSaGFyJiIKkd+/e9qeffrq2Z8+e4tSpU2v1ej06dOgg5ebmWnv27ClOmzatpqSkRNi5c6caAGbNmqXr27ev+NJLL9VkZmaK559/vvjee+9VrVu3Tr1//37Z/57v379f9e2332reeeedqsGDB9uzs7PFefPmVVdVVQXuN0tE5Af8aIiIKEh69+4tOn+t0WgQHx8vZWVl1T+WnJwsAUBxcbEAALt371avX79ebTAYmhXM/v7770KvXr1kvW5eXp5Ko9FgwIAB9a+VmZkpmkymlv9miIhaAYMrEVGQaLXaRhc+CYIArVZbf1+lciyiiqIjX1ZUVAiXXnqp7aWXXqpueq6WXHxFRBRuGFyJiMLEeeedZ//88881GRkZUsOA66tzzjlHtNls2LZtm+qCCy4QAWDfvn2q0tJSf02ViCggWONKRBQmHnjggdrTp08L119/feSWLVtUv/32m7BixQr1bbfdprfZbLLPc84554gjR460T5gwIXLTpk3qrVu3qu6++259ZCTbtRJRaGNwJSIKE2eddZa0ceNGi91ux+jRo6P79u1rmDhxot5kMknOsgK53n///aqUlBRx+PDhUddcc03UPffcY+3YsSPLDYgopAmSJJUFexJERBQYc+fO1T7yyCP60tLS8pYc//3336tHjBgRVVJSUh4XF+fv6RER+YQrrkREbZzZbIbBYDD+85//jPDluHPOOSf68ssvjwrUvIiIfMUVVyKiNqysrAyFhYUCAMTFxcGXcoD8/HzBarUCALp16yap1WovRxARBRaDKxERERGFBZYKEBEREVFYYHAlIiIiorDA4EpEREREYYHBlYiIiIjCAoMrEREREYUFBlciIiIiCgsMrkREREQUFhhciYiIiCgs/D9iX3QJzFy/3AAAAABJRU5ErkJggg==\n", + "image/png": "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ERGRP2QFUJ1Oh27duok2GzfHas615Wu1LlFSe4vEdkRERETkJHvk9IknnqibNm2a/vTp08HoT9iTurRCVb0deUu2Y9WeoqD2h4iIiChSyF7H9fXXX9cdPHhQ1bVrV1NaWpojOjq6yfM7d+6sDljvwsiqPUV45pMC3G3bKumn2hnOWthnPinAyKwUlgwQERER+SA7uI4bN87qu1XH4tryVYADf9B/I+mYEpghAigqr8WWwlIM6cHSASIiIqLWyA6uzz77bH0wOhKuGm/5OlhVgFjB97qsrjVcXbiWKxEREZFvXB1AocZbvl6m+lHSMVsdveBo9KPnWq5EREREvskecbXZbJg7d65u+fLl2qNHjwpWq7VJcWZpaanvbaMiSOPR0ssEacG1C065v+darkRERETSyB5x/dvf/qZ/9dVXdTfccIO1oqJCeOihh+quueYaqyAI4vTp0zvUdq9A09HSGKFG0jGN243rl8qJWUREREQSyA6uy5Yt0/773/+unTp1ar1Go8Ett9xiXbRoUe1f//rX+u+//14djE6GMteWrwBwXOwk6ZjG7fJ3FcHukLqIFhEREVHHJTu4njhxQujbt68dAGJiYsSysjIBcK42sGrVKtmlB+FOrRIwrl8qAGC943xJxzRu51pVgIiIiIhaJzu4du3a1XH8+HEVAHTv3t3xxRdfaABgy5Ytap1O1+GGDu0OEfm7nJsInBSl1ao2b8dVBYiIiIh8kx1cx40bZ1u9erUaAP785z/XP/PMM/qePXvG3HnnnVF33HFHh1vjtfGqAglClaRjmrfjqgJEREREvkn+aP+VV17R3nHHHda5c+e6J2DdfPPNtrS0NMvGjRvVvXr1clx77bW24HQzdDUeLb1E2CPpmNNirPt7c7SWqwoQERERSSB5xHXGjBmGrl27mm666aaor776yj0J67LLLrM//vjj9R0xtAJnR0s1sOH36p2SjjmBs0G1zGLFVwXFwegaERERUUSRHFyLiooq//Wvf9UWFRUJubm50RkZGcYZM2boDh8+3KHXcsrJTIA5Wov71SsgSPhJVIhRTXbNEgA880kBVxYgIiIi8kFycI2Ojsadd95p/fbbby0///xz1c0332x9++23dT169DCOHDkyetmyZZr6+o67G+yD6nxJ7XRifZNds0RwZQEiIiIiKfza8rVnz57i7Nmz6woLC6s+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kSNH2nbt2lW9fft2923AgAGOm266ybp9+/bqnJwcvodhprKyEoWFharU1FQxVP8OslQggCZPnlx/1113RV188cX2QYMG2V9++WWdxWIR7r77bmt7941aqqysxP79+92/vB08eFD1ww8/qBITE8WMjAxx0qRJ9c8//7y+V69eju7duzumT5+uT01NFcePH88VBUJAXl6e4T//+Y/2f//7n8VkMonHjx8XAMBsNovR0dEwm82YOHGi9dFHHzUkJCTUxMbGig899JBh0KBBds5mDg2PPfaY/qqrrrKlp6c7KisrhSVLlmjXrVunXrlyZR3fv9AXGxsLV025S0xMjJiYmCi6Hud7GNomT56sHzt2rC0jI8Nx7Ngx1YwZM/QqlUq85ZZbrKH6d5DLYQXYK6+8ov373/+uP3HihNC3b1/7q6++WsffLEPT119/rR4xYkR088dvvfVW67vvvlvrcDgwffp0/YIFC7Tl5eXCkCFD7PPmzavt06cPSwVCgCAIJk+Pv/XWW7X33HOPFXAunj158mTDf//7X21dXR1GjBhhmzdvXm2XLl34MWUImDhxomHt2rWa4uJiITY2VszOznZMnTq1Ljc31w7w/QtHQ4cOjXYthwXwPQx1N9xwQ9SGDRvUpaWlQqdOncRLLrnEPnv27Npzzz1XBELz/WNwJSIiIqKwwBpXIiIiIgoLDK5EREREFBYYXImIiIgoLDC4EhEREVFYYHAlIiIiorDA4EpEREREYYHBlYiIiIjCAoMrEREREYUFBlciojZw2223GcaOHRvV1tedP3++VhAEkyAIpkmTJulba5uenm6cO3eurvF917FnzpwJfmeJiHzQtHcHiIjCnbftZ13++te/1r/22mu1otg+uyTGxsZi7969VUajUVYHtmzZUr1u3Tr1H/7whzYP3EREnjC4EhEpdOzYsSrX9++//7525syZ+r1797ofM5lMosnUarYNKkEQ4M/e4snJyWJCQgL3lCeikMFSASIihbp06SK6bnFxcaIrKLpuJpOpRanA0KFDox988EHDpEmT9PHx8aakpCTjvHnztFVVVbj99tsNJpPJ1KNHD+Onn36qbnyt3bt3q6688spoo9FoSkpKMt58882GkydPCnL7XFxcLFx11VVRUVFRpoyMDOM777zDgQwiCnkMrkRE7WTJkiXaxMREcfPmzdUPPvhg/aRJkwwTJkyIGjJkiH3btm3VI0aMsN1xxx1R1dXVAIAzZ87g97//fXT//v3tW7ZsqV65cqXlxIkTqhtuuEH2R/l33HGH4bffflOtXr3a8t///tcyb948nT8BmIioLTG4EhG1kwsuuMD+9NNP1/fu3dsxffr0eoPBgE6dOol5eXnW3r17O2bMmFFXWloq7Ny5Uw0Ar776qq5fv36OF198sS4rK8tx8cUXOxYtWlTz7bffqvft2yf53/N9+/apvvzyS82bb75Zc+mll9pzcnIcCxYsqK2pqQneiyUiCgB+NERE1E4uuOACh+t7jUaDhIQEMTs72/1YSkqKCAAlJSUCAOzevVu9bt06tdFobFEwe+DAAaFPnz6SrltQUKDSaDQYOHCg+1pZWVkOs9ns/4shImoDDK5ERO1Eq9U2mfgkCAK0Wq37vkrlHER1OJz5sqqqSrjqqqtsL774Ym3zc/kz+YqIKNwwuBIRhYkLL7zQ/tFHH2kyMzPFxgFXrvPOO89hs9mwdetW1eDBgx0AsHfvXlVZWVmgukpEFBSscSUiChOTJk2qP3PmjHDjjTdGbd68WfXLL78IK1euVN9+++0Gm80m+TznnXeeY+TIkfYHHnggauPGjeotW7ao7rnnHkNUFJdrJaLQxuBKRBQmzjnnHHHDhg0Wu92O0aNHx/Tr1884efJkg9lsFl1lBVK9/fbbNampqY7hw4dHX3/99dH33nuvtXPnziw3IKKQJoiiWNHenSAiouCYP3++9tFHHzWUlZVV+nP8119/rR4xYkR0aWlpZXx8fKC7R0QkC0dciYgiXHl5OYxGo+kvf/mLXs5x5513XsyYMWOig9UvIiK5OOJKRBTBKioqUFxcLABAfHw85JQDFBYWClarFQDQo0cPUa1W+ziCiCi4GFyJiIiIKCywVICIiIiIwgKDKxERERGFBQZXIiIiIgoLDK5EREREFBYYXImIiIgoLDC4EhEREVFYYHAlIiIiorDA4EpEREREYeH/AcWk35QFvrqKAAAAAElFTkSuQmCC\n", "text/plain": [ "
" ] @@ -543,91 +676,97 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 11, "id": "51cc75b4", - "metadata": {}, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, "outputs": [ { "data": { "text/plain": [ - "array([[3.000e+01, 5.000e+00, 1.000e+03, ..., 4.152e-11, 9.379e-05,\n", - " 9.223e+04],\n", - " [3.000e+01, 5.000e+00, 1.000e+03, ..., 4.194e-09, 9.473e-03,\n", - " 9.223e+04],\n", - " [3.000e+01, 5.000e+00, 1.000e+03, ..., 8.346e-09, 1.885e-02,\n", - " 9.223e+04],\n", - " ...,\n", - " [3.000e+01, 2.811e+00, 1.146e+03, ..., 2.500e+01, 9.986e+05,\n", - " 9.223e+04],\n", - " [3.000e+01, 2.810e+00, 1.147e+03, ..., 2.501e+01, 9.986e+05,\n", - " 9.223e+04],\n", - " [3.000e+01, 2.810e+00, 1.148e+03, ..., 2.501e+01, 9.986e+05,\n", - " 9.223e+04]])" + "array([], shape=(0, 1), dtype=float64)" ] }, - "execution_count": 10, + "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Raw time-series data are stored in\n", - "A1.scope.record" + "# A1.scope.record\n", + "A2 = sys.flowsheet.unit.A2\n", + "A2.scope\n", + "A2.scope.record" ] }, { "cell_type": "markdown", "id": "8b8727df", - "metadata": {}, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, "source": [ "Each row in the `record` attribute is values of `A1`'s state variables at a certain time point." ] }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 12, "id": "cab34aab", "metadata": { - "scrolled": true + "scrolled": true, + "slideshow": { + "slide_type": "slide" + } }, "outputs": [ { "data": { "text/plain": [ - "array([0.000e+00, 5.092e-10, 1.018e-09, 6.110e-09, 1.120e-08, 6.212e-08,\n", - " 1.130e-07, 3.163e-07, 5.195e-07, 7.227e-07, 1.402e-06, 2.081e-06,\n", - " 2.761e-06, 8.668e-06, 1.458e-05, 2.048e-05, 3.167e-05, 4.285e-05,\n", - " 5.404e-05, 6.522e-05, 1.049e-04, 1.445e-04, 1.842e-04, 2.238e-04,\n", - " 3.089e-04, 3.940e-04, 4.791e-04, 5.642e-04, 6.493e-04, 8.356e-04,\n", - " 1.022e-03, 1.208e-03, 1.394e-03, 1.581e-03, 1.767e-03, 2.184e-03,\n", - " 2.602e-03, 2.895e-03, 3.188e-03, 3.398e-03, 3.567e-03, 3.736e-03,\n", - " 3.905e-03, 4.038e-03, 4.171e-03, 4.304e-03, 4.437e-03, 4.571e-03,\n", - " 4.704e-03, 4.848e-03, 4.993e-03, 5.138e-03, 5.282e-03, 5.427e-03,\n", - " 5.571e-03, 5.832e-03, 6.092e-03, 6.352e-03, 6.612e-03, 6.872e-03,\n", - " 7.331e-03, 7.790e-03, 8.248e-03, 8.707e-03, 9.408e-03, 1.011e-02,\n", - " 1.081e-02, 1.151e-02, 1.274e-02, 1.396e-02, 1.519e-02, 1.642e-02,\n", - " 1.849e-02, 2.056e-02, 2.196e-02, 2.336e-02, 2.476e-02, 2.616e-02,\n", - " 2.858e-02, 3.100e-02, 3.221e-02, 3.342e-02, 3.463e-02, 3.583e-02,\n", - " 3.704e-02, 3.955e-02, 4.122e-02, 4.290e-02, 4.457e-02, 4.625e-02,\n", - " 5.036e-02, 5.447e-02, 5.858e-02, 6.270e-02, 6.959e-02, 7.649e-02,\n", - " 7.735e-02, 7.821e-02, 7.907e-02, 7.994e-02, 8.083e-02, 8.173e-02,\n", - " 8.262e-02, 8.299e-02, 8.335e-02, 8.367e-02, 8.399e-02, 8.436e-02,\n", - " 8.473e-02, 8.827e-02, 8.872e-02, 8.916e-02, 9.138e-02, 9.359e-02,\n", - " 1.004e-01, 1.072e-01, 1.228e-01, 1.248e-01, 1.267e-01, 1.287e-01,\n", - " 1.291e-01, 1.296e-01, 1.300e-01, 1.344e-01, 1.388e-01, 1.432e-01,\n", - " 1.597e-01, 1.762e-01, 1.927e-01, 1.936e-01, 1.945e-01, 1.954e-01,\n", - " 2.041e-01, 2.128e-01, 2.413e-01, 2.698e-01, 2.983e-01, 2.990e-01,\n", - " 2.997e-01, 3.005e-01, 3.012e-01, 3.083e-01, 3.154e-01, 3.225e-01,\n", - " 3.622e-01, 4.019e-01, 4.416e-01, 5.179e-01, 5.941e-01, 6.703e-01,\n", - " 7.466e-01, 8.335e-01, 9.205e-01, 1.007e+00, 1.094e+00, 1.280e+00,\n", - " 1.466e+00, 1.652e+00, 1.838e+00, 2.148e+00, 2.457e+00, 2.767e+00,\n", - " 3.076e+00, 3.591e+00, 4.106e+00, 4.621e+00, 5.136e+00, 5.874e+00,\n", - " 6.613e+00, 7.351e+00, 8.090e+00, 9.540e+00, 1.099e+01, 1.244e+01,\n", - " 1.389e+01, 1.586e+01, 1.782e+01, 1.979e+01, 2.176e+01, 2.373e+01,\n", - " 2.790e+01, 3.207e+01, 3.624e+01, 4.041e+01, 4.689e+01, 5.000e+01])" + "array([0.000e+00, 5.096e-10, 1.019e-09, 6.115e-09, 1.121e-08, 6.217e-08,\n", + " 1.131e-07, 3.165e-07, 5.198e-07, 7.231e-07, 1.403e-06, 2.082e-06,\n", + " 2.762e-06, 8.671e-06, 1.458e-05, 2.049e-05, 3.168e-05, 4.286e-05,\n", + " 5.405e-05, 6.524e-05, 1.049e-04, 1.446e-04, 1.842e-04, 2.239e-04,\n", + " 3.090e-04, 3.941e-04, 4.793e-04, 5.644e-04, 6.495e-04, 8.358e-04,\n", + " 1.022e-03, 1.208e-03, 1.395e-03, 1.581e-03, 1.767e-03, 2.185e-03,\n", + " 2.602e-03, 2.895e-03, 3.189e-03, 3.398e-03, 3.567e-03, 3.736e-03,\n", + " 3.905e-03, 4.038e-03, 4.171e-03, 4.304e-03, 4.438e-03, 4.571e-03,\n", + " 4.704e-03, 4.849e-03, 4.993e-03, 5.138e-03, 5.283e-03, 5.427e-03,\n", + " 5.572e-03, 5.832e-03, 6.093e-03, 6.353e-03, 6.613e-03, 6.874e-03,\n", + " 7.332e-03, 7.790e-03, 8.248e-03, 8.706e-03, 9.407e-03, 1.011e-02,\n", + " 1.081e-02, 1.151e-02, 1.273e-02, 1.396e-02, 1.519e-02, 1.641e-02,\n", + " 1.848e-02, 2.055e-02, 2.195e-02, 2.335e-02, 2.476e-02, 2.616e-02,\n", + " 2.857e-02, 3.097e-02, 3.218e-02, 3.338e-02, 3.458e-02, 3.578e-02,\n", + " 3.699e-02, 3.949e-02, 4.116e-02, 4.283e-02, 4.450e-02, 4.616e-02,\n", + " 5.025e-02, 5.433e-02, 5.842e-02, 6.250e-02, 6.937e-02, 7.624e-02,\n", + " 7.709e-02, 7.795e-02, 7.881e-02, 7.967e-02, 8.006e-02, 8.045e-02,\n", + " 8.084e-02, 8.182e-02, 8.280e-02, 8.378e-02, 8.420e-02, 8.462e-02,\n", + " 8.503e-02, 8.548e-02, 8.593e-02, 8.620e-02, 8.646e-02, 8.712e-02,\n", + " 8.778e-02, 9.300e-02, 9.822e-02, 1.096e-01, 1.110e-01, 1.124e-01,\n", + " 1.138e-01, 1.144e-01, 1.150e-01, 1.156e-01, 1.171e-01, 1.185e-01,\n", + " 1.200e-01, 1.208e-01, 1.216e-01, 1.224e-01, 1.229e-01, 1.233e-01,\n", + " 1.236e-01, 1.239e-01, 1.255e-01, 1.262e-01, 1.270e-01, 1.328e-01,\n", + " 1.386e-01, 1.519e-01, 1.652e-01, 1.668e-01, 1.685e-01, 1.702e-01,\n", + " 1.717e-01, 1.733e-01, 1.842e-01, 1.856e-01, 1.870e-01, 1.878e-01,\n", + " 1.886e-01, 1.891e-01, 1.896e-01, 1.899e-01, 1.902e-01, 1.933e-01,\n", + " 1.965e-01, 2.170e-01, 2.375e-01, 2.581e-01, 2.592e-01, 2.603e-01,\n", + " 2.614e-01, 2.723e-01, 2.832e-01, 3.164e-01, 3.496e-01, 3.828e-01,\n", + " 4.503e-01, 5.178e-01, 5.852e-01, 6.527e-01, 7.282e-01, 8.037e-01,\n", + " 8.791e-01, 9.546e-01, 1.105e+00, 1.256e+00, 1.406e+00, 1.557e+00,\n", + " 1.810e+00, 2.063e+00, 2.317e+00, 2.570e+00, 3.003e+00, 3.436e+00,\n", + " 3.869e+00, 4.302e+00, 4.915e+00, 5.528e+00, 6.142e+00, 6.755e+00,\n", + " 7.995e+00, 9.236e+00, 1.048e+01, 1.172e+01, 1.341e+01, 1.511e+01,\n", + " 1.680e+01, 1.850e+01, 2.118e+01, 2.386e+01, 2.654e+01, 2.923e+01,\n", + " 3.427e+01, 3.932e+01, 4.437e+01, 4.942e+01, 5.000e+01])" ] }, - "execution_count": 11, + "execution_count": 12, "metadata": {}, "output_type": "execute_result" } @@ -640,7 +779,11 @@ { "cell_type": "markdown", "id": "c14d81b0", - "metadata": {}, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, "source": [ "The tracked time-series data can be exported to a file in two ways." ] @@ -649,7 +792,11 @@ "cell_type": "code", "execution_count": 13, "id": "c126483f", - "metadata": {}, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, "outputs": [], "source": [ "# sys.scope.export('bsm1_time_series.xlsx')\n", @@ -667,16 +814,24 @@ { "cell_type": "markdown", "id": "5b93411d", - "metadata": {}, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, "source": [ "We can also (re-)define which unit or stream to track during dynamic simulation." ] }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 14, "id": "b818bfbf", - "metadata": {}, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, "outputs": [ { "data": { @@ -684,7 +839,7 @@ "(, )" ] }, - "execution_count": 12, + "execution_count": 14, "metadata": {}, "output_type": "execute_result" } @@ -699,15 +854,18 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 15, "id": "f6b35327", "metadata": { - "scrolled": false + "scrolled": false, + "slideshow": { + "slide_type": "slide" + } }, "outputs": [ { "data": { - "image/png": "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JjExUW+pR0JCgqB79+7UAYIQQghpgTr4+SNkxvxKM8FmrVojZMZ8dPDzb5a8oqKiMGfOHJw8eRJpaWn45ZdfMH78eKhUKgQGBgIAhg0bhi+++AI///wz7t69i5MnT+K9996Dm5sb3N3dAQAffPABzp8/j08++QS///47du7cif/+97947733muR9TJ06FVu2bMHXX3+N27dvY9myZfjtt990M8VqtRqHDh2qcv3vgwcPcOXKFb3bs5OZxqDOa4ALCgrw3nvvmXz77bdCALhx48aT9u3bsxERESb29vbaBQsWlHIda/bs2eJBgwapnZyctPn5+cy3334rPH36NP/HH38ssbCwQFhYmGrWrFkmVlZWRebm5uy0adNM/Pz8NAEBARoACA4O1ri7u2vHjBkjWblyZXFmZiazePFi8bvvvltq8nRHZ0REROnGjRtFM2bMEIeHh6sSEhL4u3fvFsTHx1fZAYIQQgghxq+Dnz9cff3w4Po1PMnNgczCEm08vJp15rd3795Yv349xo4di6ysLFhaWqJr1644evQoOnbsCAAICgrCrl27sHz5ciiVSigUCvTr1w9RUVEQCMrKMl9fX+zZswfz5s3Dxx9/DBcXF8TExGD06NFN8j5Gjx6NP/74A7NmzUJxcTFCQ0MRFhaGCxcuAABOnToFmUyGl156qdJzP/vsM3z22Wd613bs2KE7LMNosCybV5fbe++9V9K1a1fNyZMnC0xNTdnbt2/nsyybt3v37kIfHx9NXcZ65513Sh0dHbUikYht3bq1tk+fPupDhw4VlD9eWFiY9+6775ZaWlqyUqmUDQkJUT148CC/4hh//PFH/oABA1QSiYRt1aqVNjIysqS0tFTvdRISEgp8fHw0IpGIdXFx0X711VdFdckzNzc3DwCrVCpZQgghhDRMUVERm5qayhYVFTV3Ks+NxYsXs507d2608V999VV2zJgxLMuy7NSpU9kpU6bUa5yG5lnTz45SqWQBsLm5ubXWdnWeAY6Pjxfu2rWrKCAgQFNx4bi3t7cmLS2tTksqtm/fXlzT4xKJBBs3bizeuHFjtXEuLi7skSNHimoap3///ppff/21+sU3hBBCCCEt3NWrVyGTybBy5cpKXRzqorCwEBs3bkRQUBD4fD527dqFhIQEHDt2DADg7e2Nnj171mnMe/fuwdPTE6WlpZU2+DWHOhfA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euP8pT89zyF/BEdEREREGYV7gImIiIgoo7AAJiIiIqKMwgKYiIiIiDIKC2AiIiIiyigsgHVo+fLlpuHDhzusVquztLTUvn37ds6TTm3atMlQXl5uKy4udgghnK+99lrExWUURcG9995rKSoqcthsNuf5559v//zzzzmfOvGrX/3KfM4552Q5nU5nQUGBY968ebbq6uqI+Wlvb8fixYuteXl5DofD4bz88sttx48fF6nKmSKtWLHCNHHixCyXy+V0uVzOGTNm2N98801DaDnnL7089NBDZiGEs7KyMnzaAM6h/lVVVVmEEM6u/8aMGZMVWq7HOeQbsc68+OKLxrvvvttaVVXl3b17t3vy5MlKeXl5Vk1NDZ/sOuR2uzF58mRlxYoVnmjLH330UfPKlSvNK1eu9Gzfvt2dlZWlXnrppfb29vZkp0pRbN682VhRUeHbtm2b+5133mnz+/245JJL7K2treE+t912m/Wtt94yrl27tn3jxo3u48ePi/nz59tSmDZ1MXToUPXRRx/17tq1y71z5073eeedJ8+fP9/+6aefSgDnL53s2LFDWr16tXnixIlK13bOYXoYP368cvTo0dbQvw8//LAttEyPc8jToOlMaWmpfdq0acqqVas8ACDLMoYOHeq45ZZbfFVVVb5U50e9E0I4X3311fYFCxYEgODe35KSEsftt9/uu+eee3wA0NjYiKKiIueaNWvar7nmmkBqM6buamtrRVFRkWPjxo1t559/vtzY2IjBgwc7n3vuufaFCxcGAGD//v3SmWeembV169a22bNny6nOmXrKy8tzPvroo56FCxf6OX/poaWlBVOnTs164oknPA8//LBlypQp8ooVK7x8DqaHqqoqyxtvvGHct2+fu/syvc4h9wDriNfrxd69ew1z5swJF0YGgwEXXHBBYMeOHYZYY0l/vv76a1FbWysuuuii8Hzm5OSgtLRU3r59O+dTh5qamgAA+fn5KgDs2rXL4Pf7cfHFF4fncMKECcrQoUPVbdu2cQ51JhAI4IUXXjC63W7Mnj1b5vylj4qKCuvcuXMDl1xySUQxxDlMH1999ZVUXFzsGDlypOOqq66yffvttwLQ7xwatbtQstTV1QlZllFYWKh2bR88eLDK40bTz/HjxyUAKCoq6jGfNTU1nE+dkWUZt912m7WsrEyePHmyAgA1NTXCbDYjNzc3om/HHPKwJJ345JNPpNmzZ2d5PB44HA68+uqr7RMnTlT27t1r5Pzp3wsvvGDcu3evYffu3T32HvI5mB5mzpwZWLNmjTxu3Djl2LFj4sEHH7Sce+65WZ999lmrXueQBTAREYJ7oPbv32/YsmVLjzdh0rdx48Ype/bsaW1sbBSvvPKK6ac//an1/fffb9MeSal26NAhsWTJEuv69evbbDYe1puuLrvssvCe+7POOgtlZWVtI0aMcL700ksmu92uxhqbKtwLpSMFBQWqwWBAbW1txCeiEydOiO57hUn/iouLw3sRu7afOHFCFBUVKdFHUSpUVFRY161bZ9y4caN72LBh4edaUVGR6vP50NDQENG/Yw75nNQJi8WCMWPGqNOnT1d+85vfeCdNmqQsW7bMzPnTv927dxvq6urEtGnTsoxGo9NoNDq3bNliePLJJ81Go9FZWFjIOUxDubm5GD16tHLw4EFJr89DFsA6YrFYcPbZZ8sbNmwI75mXZRmbNm0yzpw5kwf6p5nTTz9dLSwsVN97773wfDY1NWHXrl2GsrIyzqcOKIqCiooK61//+lfjhg0b2kaNGhXxYlxaWiqbTCa8++674Tmsrq6WvvvuOzFr1izOoU6pqgqv18v5SwMXXXRR4JNPPnHv2bMn/G/q1KnKVVdd5d+zZ497+vTpnMM01NLSgm+++UYqLi5W9fo85CEQOrNkyRLfokWLbNOmTZNnzJghL1261NzW1iZ+9rOf+VOdG/XU0tKCL774IvxB8uuvv5Y++ugjKT8/Xx0xYoRaWVnp+/Wvf20ZM2aMcvrppytVVVWW4uJidf78+TwDhA5UVFRY165da3r99dfbnE6neuzYMQEAOTk5qt1uR05ODm688Ub/XXfdZc3Ly2t3uVzqrbfeap0xY4bMX5/rw89//nNLeXl5YPjw4UpLS4t4/vnnTZs3bzasW7fOy/nTP5fLhdAx9yFZWVlqfn6+GmrnHOrfkiVLLPPmzQuMGDFCOXr0qPTAAw9YJElSr7nmGr9en4c8DZoOLVu2zPT4449bamtrxeTJk+Xly5d7+UlXnzZs2GCYM2eOvXv7tdde63/uuec8iqKgqqrKsmbNGlNTU5MoKyuTV61a5Rk3bhwPgdABIYQzWvsf//hHz0033eQHgidwX7JkifXll182eb1ezJkzJ7Bq1SpPSUkJv37VgRtvvNG6adMmY01NjXC5XOrEiROVX/ziF95LL71UBjh/6ejcc8+1h06DBnAO08EVV1xh27p1q6G+vl4MGjRInTVrlvzII494zjjjDBXQ5xyyACYiIiKijMJjgImIiIgoo7AAJiIiIqKMwgKYiIiIiDIKC2AiIiIiyigsgImIiIgoo7AAJiIiIqKMwgKYiIiIiDIKC2AiIiIiyigsgImI0sR1111nnTdvni3Z6129erVJCOEUQjgrKystsfoOHz7c8dvf/tbc9e/Q2IaGhsQnS0QUB2OqEyAiot4vyxxy3333+VasWOFR1dRcOdTlcqG6urrV4XD0KYGdO3e6N2/ebLjyyiuTXrgTEfWGBTARkQ4cPXq0NfT/l156yfTggw9aqqurw21Op1N1OmPWyAklhEBJSUmfq+/CwkI1Ly8vNVU7EVEveAgEEZEOlJSUqKF/2dnZaqjgDP1zOp09DoE499xz7bfccou1srLSkpub6xw8eLBj1apVptbWVlx//fVWp9PpHDVqlOPNN980dF3Xvn37pIsvvtjucDicgwcPdlx99dXWuro60deca2pqRHl5uc1mszlHjBjhePbZZ7lThYjSAgtgIqI09vzzz5vy8/PVHTt2uG+55RZfZWWldcGCBbaysjJ59+7d7jlz5gRuuOEGm9vtBgA0NDTgwgsvtJ911lnyzp073evWrWurra2Vrrjiij4fonDDDTdYjxw5Ir333nttL7/8ctuqVavM/SmkiYiSjQUwEVEamzRpkvzLX/7SN3bsWKWqqspntVoxaNAgtaKiwj927FjlgQce8NbX14uPP/7YAADLly83T5kyRXnssce8EyZMUKZNm6b86U9/av/ggw8MBw4ciPs94cCBA9L69euNTz31VPvs2bPl6dOnK2vWrPG0t7cn7s4SEQ0Qfl1FRJTGJk2apIT+bzQakZeXp06cODHcVlRUpALAiRMnBADs27fPsHnzZoPD4ehxQPHBgwfFuHHj4lrv/v37JaPRiNLS0vC6JkyYoOTk5PT/zhARJQkLYCKiNGYymSJ+YCaEgMlkCv8tScGduooSrFNbW1tFeXl54LHHHvN0j9WfH7kREaUjFsBERBnk7LPPlv/85z8bR44cqXYtlPtq/PjxSiAQwK5du6SZM2cqAFBdXS01NjYOVKpERAnDY4CJiDJIZWWlr6GhQSxcuNC2Y8cO6csvvxTr1q0zXH/99dZAIBB3nPHjxysXXXSRvHjxYtu2bdsMO3fulG666SarzcbT/RKR/rEAJiLKIKeddpq6devWNlmWMXfu3KwpU6Y4lixZYs3JyVFDh0vE6+mnn24vLi5WLrjgAvuPf/xj+8033+wvKCjgYRREpHtCVdXmVCdBRET6tXr1atNdd91lbWxsbOnP+A0bNhjmzJljr6+vb8nNzR3o9IiI+ox7gImISFNTUxMcDofzzjvvtPRl3Pjx47Muu+wye6LyIiLqD+4BJiKimJqbm1FTUyMAIDc3F305zOGbb74Rfr8fADBq1CjVYDBojCAiSjwWwERERESUUXgIBBERERFlFBbARERERJRRWAATERERUUZhAUxEREREGYUFMBERERFlFBbARERERJRRWAATERERUUZhAUxEREREGeX/AzDBTK6xE8BAAAAAAElFTkSuQmCC\n", + "image/png": "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4Ofnp7569SqvIukFgMOHDwssLCzg5eWlBQBfX19NUlKS3lKPxMREQc+ePakCBCGEENICdfL1Q/CM+ZVmgs1btUbwjPno5OvXLOOKiorCnDlzcOLECWRkZOCXX37BhAkToFKpEBAQAAB4++238dVXX+H8+fO4c+cOTpw4gQ8++ACurq5wcys/Cfajjz7CuXPn8Nlnn+GPP/7Azp078Z///AcffPBBk9xHREQEtmzZgm+//Ra///47li1bht9++003U6xWq3Hw4MEq1//ev38fqampeo9nJzONQZ3XABcWFuKDDz4w/f7774UAcP369YKOHTuy4eHhpm3atNEuWLCgjGtfs2fPFg0ZMkTt6Oioffz4MfP9998LT506xf/5559LLS0tERoaqpo1a5aptbV1sYWFBTtt2jRTX19fjb+/vwYAgoKCNG5ubtqxY8eKV65cWZKdnc0sXrxY9P7775eZPtnRGR4eXrZp0yaTGTNmiMLCwlSJiYn83bt3CxISEqqsAEEIIYQQ49fJ1w8uPr64f+0qCvLzILW0Qlt3z2ad+e3bty82bNiAcePGIScnB1ZWVujevTuOHDmCzp07AwACAwOxa9cuLF++HAqFAnK5HAMGDEBUVBQEgvK0zMfHB3v27MG8efPw6aefwtnZGatXr8aYMWOa5D7GjBmD27dvY9asWSgpKUFISAhCQ0Nx4cIFAMDJkychlUrx0ksvVXrvF198gS+++EKvbceOHbrDMowGy7LKujw++OCD0u7du2tOnDhRaGZmxv7++++PWZZV7t69u8jb21tTl77ee++9MgcHB62JiQnbunVrbb9+/dQHDx4srHi9qKhI+f7775dZWVmxEomEDQ4OVt2/f//x033cvn378aBBg1RisZht1aqVNjIysrSsrEzvOomJiYXe3t4aExMT1tnZWfvNN98U12Wc+fn5SgCsQqFgCSGEENIwxcXFbHp6OltcXNzcQ3luLF68mO3atWuj9f/qq6+yY8eOZVmWZSMiItipU6fWq5+GjrOmfzsKhYIFwObn59ea29V5BjghIUG4a9euYn9/f83TC8e9vLw0GRkZdVpSsX379pKaXheLxdi0aVPJpk2bqo1zdnZmDx8+XFxTPwMHDtT8+uuv1S++IYQQQghp4a5cuQKpVIqVK1dWquJQF0VFRdi0aRMCAwPB5/Oxa9cuJCYm4ujR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EaWwiRMnqh1/GwwG5ObmahMmTAguczqdGgDU1tYKAPj888/lLVu2yHa7vccBxQcPHhRjx46NKO/+/fslg8GAkpKSYK7x48er2dnZ/R8MEVGcsAAmIkphRqOxyxfMhBAwGo3B25IU2KmrqoE6taWlRZSVlfmXLl3q7h6rP19yIyJKRSyAiYjSyHnnnaf84x//MIwYMUILLZSjNW7cONXv92P37t3S9OnTVQCoqqqSGhoaYtVVIqIBw2OAiYjSSEVFhbe+vl5cd9111p07d0rffPONWL9+vXzLLbdY/H5/xHHGjRunzp07V7nzzjut27dvl3ft2iXdfvvtFquVp/slouTHApiIKI0MHTpU27ZtW5uiKLjiiitskydPti9evNiSnZ2tdRwuEalnn33WVVBQoF5yySUZP/3pTzPuuOMOX15eHg+jIKKkJzRNa0p0J4iIKHmtWbPGeP/991saGhqa+9N+48aN8pw5czJOnTrVnJOTE+vuERFFjXuAiYhIV2NjI+x2u+O+++4zR9Nu3LhxtiuvvDJjoPpFRNQf3ANMRER9ampqQnV1tQCAnJwcRHOYw5EjR4TP5wMAjBo1SpNlWacFEdHAYwFMRERERGmFh0AQERERUVphAUxEREREaYUFMBERERGlFRbARERERJRWWAATERERUVphAUxEREREaYUFMBERERGlFRbARERERJRW/j82g11cyBFxEgAAAABJRU5ErkJggg==\n", "text/plain": [ "
" ] @@ -718,21 +876,25 @@ ], "source": [ "# Need to rerun the simulation before retrieving results\n", + "# user shorter time or try changing method to 'RK23' (explicit solver) if it takes a long time\n", "sys.simulate(t_span=(0, 50), method='BDF', state_reset_hook='reset_cache')\n", "fig, ax = C1.scope.plot_time_series([f'TSS{i}' for i in range(1,11)])" ] }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 16, "id": "68dcbad5", "metadata": { - "scrolled": false + "scrolled": false, + "slideshow": { + "slide_type": "slide" + } }, "outputs": [ { "data": { - "image/png": "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R48e0rRp03TLli2z5uTkCAsWLNA8+OCDNq3WGZBmz55tW7VqVcjcuXM1M2fOtG/dulX5ySefqNavX+9RBQhqXoM7RcCoVcFirXuyixjO9EZERERXwedVIOqzfPly6/jx4x233367fsSIEaHR0dHyp59+WuZar1KpsGHDhlKlUilff/31odOnT9dNmzbNvmTJkoqL6rp06SKvX7++dNu2bar+/fuHvvrqq5pVq1ZZWQPYP/2UU4RSW+2nhjO9ERERUWPweR3gQMA6wM2j1ObAxNd248T5EiS0MeJCiQ05lS6IizVpsWBiPGd6IyIiohq8qQPsFzPBEQHAoi8ycOJ8CaIMGvzfvYNh1odwpjciIiJqdAzA5BOVpzmOMmiRZ7Hig/TTEATg1an9EBmmAQAM7RLp45YSERFRS8MATM2utmmOXf26s2/sgmFdW/mmYURERBQUGICpWbmmOa4+j5vrcc9YjrEmIiKipuXXVSCoZXE3zTEALPkyk9McExERUZNiAKZm426aY4DTHBMREVHTYwCmZsNpjomIiMgfMABTs+E0x0REROQPGICp2QzuFIFYU93hVoBzsgtOc0xERERNiQGYmo1SISB1Qs9a13GaYyIiImouLINGTa7ypBfpJ50XuAlAlWoQMZzmmIiIiJoJAzA1qdomvQCAOwe3x8S+bTnNMRERETU7jwLwZ5995nVQHjt2rEOv13vfImox6pr0AgDeTzuN4d1b45Z+bZu9XURERBTcPAq2t912m86bgwqCgJ9++qm4a9eunNEgSHky6cVzX2RgdHwMe36JiIioWXl8Edy5c+eKJUkq8uTGnl9yN+mFDE56QURERL7hUQCeNm2aXa/Xe9ybe+edd9pNJlPDW0UBj5NeEBERkb/yaAjEO++841VKefPNN5lqghwnvSAiIiJ/1Wh1gDMyMhTdunULbazjUWDjpBdERETkrxotAFutVvz222+cWIMAOCe9uH1g+1rXcdILIiIi8iUGVmoSoiRj8w85AAB9iLLKuhiTFiunDeCkF0REROQTnAiDmsRH+0/jx5wimHRqbH/sD/g5t5iTXhAREZFfYACmRuOa8vhUfgle3JgJAHhkZDdEhmkwNEzj49YREREROXkcgMPDww2CUHevncPhaJQGUWCqbcpjpUJAlJHBl4iIiPyLxwH473//O0ubUa3qmvJYlGSkvHcIKoXA8b5ERETkNzwOwMOHD3dwamOqjlMeExERUaDxuApEv379wuLj40P/+te/avbu3cvqEQSAUx4TERFR4PE4yJ4/f77ohRdeKD9//rxw66236mNiYsLuvfde7f/+9z9VWVlZU7aR/BinPCYiIqJA43EA1ul0uPXWWx1vv/22NTs7u/i///1vWUREhPzkk09qWrdubZg4caLuzTffVOfm5vLv3EGEUx4TERFRoGnQUAaFQoEbbrhBfPnll8t//PHHkgMHDpRcf/314jvvvKOOi4sLW758ubqxG0r+iVMeExERUaBplLG811xzjfTEE0/Ydu/eXXr27NnipKQksTGOS/5PqRBw93Udal3HKY+JiIjIH3k9EcZnn31W6z6CIECr1crdu3eXrrnmGunqm0aBIu2k8wI3nVqJMvuV7z4xJi0WTIxnCTQiIiLyK14H4Ntuu00nCAJkuWrhK9cyQRAwbNgw8fPPPy+NiOCfvVu6g6cu4ZufzkOpEPDlw9cj11LOKY+JiIjIr3k9BGLTpk2l1157rbhp06bSgoKCooKCgqJNmzaVDho0SPz888/LduzYUXrx4kVh7ty5vOqphRIlGXtPXMTnh89i4fofAACT+7dF59ZhGNolErf0a4uhXSIZfomIiMgved0D/Oijj2r/9a9/WW+44YaKv3WPGTNG1Gq15Q8++KA2MzOz5JVXXrHOnDlT17hNJX9Q25THANCnndk3DSIiIiLyktcBOCsrS2EymWpM/GUymeSTJ08qAKB79+7SxYsX2f3XwtQ15TEAPPv5cbQ2hHC8LxEREfk9r4dA9O/fX5w3b562cr3f3Nxc4fHHH9dee+21IgD8/PPPinbt2vFCuBbE0ymPRYmzZRMREZF/8zoAr1mzxnry5EkhLi4urEuXLmFdunQJi4uLC/v999+F1atXWwGguLhYePrpp22N31zyFU55TERERC2F10MgevbsKWVmZpZs2rRJ+dNPPykBoEePHuLYsWNFpVIJALjtttscjdxO8jFOeUxEREQthdcBGACUSiUmTJggTpgwgRNeBAlOeUxEREQtRYMC8L59+xTbt29XnT9/XpCkqkN9ly9fXt4oLSO/4pryuK5hEAKcE19wymMiIiLyd14H4EWLFoUsXLhQ061bNyk6OloWhCvFHirfp5ZFqRCwYGI8Zr17sMY6TnlMREREgcTrALxi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Onr0aInFYhFSUlJ4FVYDiXLDvzew/BkRERGR57zuAd6yZYtq8+bNJb169aoY8pCQkCCtWLGibNy4caGN27zgcb6ovMH7svwZERERkee87gGWJAnVS58BgFqthiRxGHBDSXLD3zuWPyMiIiLynNcB+A9/+IPj0Ucf1Z45c6bib+6nT58W5s6dq73xxhsbPpA1yOVZGlZcQyEA13YIb+TWEBEREbVcXgfg119/3WqxWITOnTuHuW5dunQJs1gswooVK6xN0chgEG3SNGg/SQYO/H6pkVtDRERE1HJ5PQa4Q4cO8qFDh0q+/vprZWZmpgIA4uPjpbFjx4qN37zgEW1oWAAGgLwifu8gIiIi8lSD6gArFAokJSWJSUlJDL2NxKgNafC+HANMRERE5DmPh0Bs2bJF2aNHj9DCwsIa6woKCtCzZ8/Qb775RtmorQsi2Q2cCtmsV7MGMBEREZEXPA7Ar776ash9991nN5lMNdaZzWbcf//99n/84x8N78YMcueLG1YGbcbQjqwBTEREROQFjwPwsWPHlOPHj6+zykNSUpLj0KFD7AFuIKutYaNJBnVk7y8RERGRNzwOwHl5eYJara5zujKVSiVfuHCBXZENZLU3LABfKGn4BBpEREREwcjjANymTRv52LFjdfbwHjlyRBkTE9Pw+XyDXEGpvUH78QI4IiIiIu94HICTkpIczz77rKasrObFWqWlpVi4cKFm/PjxDUtxhNIGDIEQAF4AR0REROQlj8ugPfPMM+X/+9//Qrt37x6WnJxs69GjhwQAmZmZilWrVoWIoojU1NSGTWdGKLF5P4meSiHwAjgiIiIiL3kcgGNjY+XvvvuuZNasWdpnnnlGI8vO0Q6CIGDUqFGON954wxobG8shEA1kl7zfp3WYuvEbQkRERNTCeTURRqdOneTNmzeX5efn4+eff1bIsoxrrrlGiojgn+F94S83dfV1E4iIiIgCToNmgouIiMB1113XgD5LakydWxt93QQiIiKigOPxRXDUtG7p3car7c06zgBHRERE1BAMwH6ibaTOq+17tTHyAjgiIiKiBmAA9hMmnXcXtJ28UNJELSEiIiJq2RiA/URBmXcV5IrLvS+bRkREREQMwH4ju8Dq1fZtzZwBjoiIiKghGID9RKyXgfbx0T2aqCVERERELRsDsJ8w60K82n54z6gmagkRERFRy8YA7CcsVrvH26oEsAIEERERUQMxAPsJ2YtJpEVOOE1ERETUYAzAfkJQeD6xHvMvERERUcMxAPuJ//vulK+bQERERBQUGID9hNUu+roJREREREGBAdhPmL2YCe7vt/ZqwpYQERERtWwMwH5iQ8pwj7e9dXCHJmwJERERUcvGAOwnYsxa6NTuT8eqaQNYAo2IiIjoKjAA+5HM58fVG4JXTRuApITYZmwRERERUcuj8nUDqKrM58chp8CKCf/8FoVWB3RqJZZO7ouk3jHs+SUiIiJqBAzAfijGrMWBZ8f6uhlERERELRKHQBARERFRUGEAJiIiIqKgwgBMREREREGFAZiIiIiIggoDMBEREREFFQZgIiIiIgoqDMBEREREFFQYgImIiIgoqPg0AO/YsUM5fvx4XWxsbJggCIZPPvmkysQckiTh6aef1sTExITpdDrDiBEj9D/99FOVNl+8eBF33HGHzmg0Gsxms2HGjBnaoqKiKs9z+PBhRWJiol6r1RratWsX9sILL4Q0w8sjIiIiIj/k0wBcUlKCPn36SK+99pq1tvUvvvhiyBtvvBHyxhtvWPfu3VsSGhoqJyUl6cvKyiq2ufPOO/UZGRmKTZs2lX7++eelu3fvVs6cOVPnWl9YWIixY8fq4+LipPT09JKXXnrJunjxYs0bb7yhboaXSERERER+RpBl2eLrRgCAIAiGjz/+uOy2225zAM7e3zZt2oQ9+uijtieffNIGAAUFBYiJiTGsWbOm7K677nL88MMPioSEhNB9+/aVDBkyRAKAL7/8Ujlx4kT9qVOnitu1aye/9tpr6gULFmizs7OLNBoNAGDevHma9evXq37++ecST9pWWFgIs9lsKCwshNFobKJ3gIiIiIgaymKxwGQyoaCgoMhkMtW7rd+OAf7tt9+E3NxcYfTo0Q7XMrPZjEGDBol79+5VAsB3332nNJvNcIVfABgzZoyoUCiwb98+JQDs27dPmZiY6HCFXwBISkpy/PLLL4r8/Pxan9tqtaKwsLDiZrH4xXcEIiIiImoEfhuAs7OzFQAQExMjV14eFRUl5+TkKAAgJydHaN26tVR5vVqtRnh4uJydnS1c3kYRHR1d5RiuY7qeo7rFixdrzGazwXWLi4szNN4rIyIiIiJf8tsA7EupqanlBQUFRa7bqVOnitzvRURERESBQOV+E9+IjY2VAGcvb9u2bSt6cPPy8oS+ffuKgLMn9/z581VCvN1ux6VLl4TY2Fj58jZSbm6uUHmbnJwcofJzVKfVaqHVahv3BRERERGRX/DbHuDOnTvL0dHR8tatWytCemFhIdLT05VDhw4VASAxMVEsKChAWlpaxevYunWrUpIkXHfddSIAXHfddeJ3332nstlsFcf++uuvVd26dZMiIiKa8RURERERkT/waQAuKirCgQMHFAcOHFAAwG+//aY4cOCA4uTJk4JCoUBKSopt6dKlms8++0x15MgRxbRp03SxsbHy5MmTHQDQq1cvafTo0eIDDzyg27t3r2Lnzp3Khx9+WDtlyhRHu3btZAC4++677Wq1Wr7nnnu0x44dU7z33nuq119/PeTRRx+11dc2IiIiImqZfFoGbdu2bcpRo0bpqy+fNm2afe3atVZJkpCamqpZs2aNurCwUBg6dKi4cuVKa48ePSqGLly8eBGzZ8/Wbdy4UaVQKHDrrbfaV6xYYTUYrly3dvjwYcVf/vIX7YEDB5SRkZHy7NmzbfPnz/c4ALMMGhEREZF/86YMmt/UAfZnDMBERERE/q1F1AEmIiIiImoKDMBEREREFFQYgImIiIgoqDAAExEREVFQYQAmIiIioqDCAExEREREQYUBmIiIiIiCCgMwEREREQUVBmAiIiIiCioMwEREREQUVBiAiYiIiCioMAATERERUVBhACYiIiKioMIATERERERBhQGYiIiIiIIKAzARERERBRUGYCIiIiIKKgzARERERBRUGICJiIiIKKgwABMRERFRUGEAJiIiIqKgwgBMREREREGFAZiIiIiIggoDMBEREREFFQZgIiIiIgoqDMBEREREFFQYgImIiIgoqKh83QCqRf5Z4M1hQHkxoAkDHtgDRLT1dauIiIiIWgQGYH+zqDUg2a48thYA/4wHFCHAs+d91iwiIiKiloJDIPxJ9fBbmWRzriciIiKiq8IA7C/yz9Ydfl0km3M7IiIiImowBmB/8fqgxt2OiIiIiGrFAOwvxJLG3Y6IiIiIasUATERERERBhQGYiIiIiIIKy6ARETU1SQR+3wMU5wJh0UCHYYBC6b/HbQ5su2+w7S2bP71H/tSWWjAAEwW7QAxngdTmjPXApicAy7kry4xtgKSXgPhJ/nfc5sC2+wbb3rL503vkT22pgyDLssXXjfB3hYWFMJvNhsLCQhiN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\n", + "image/png": "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NlLl9Wq1GrGxsXJeXp5Qvo0iISGhyjF8x/Sd40Jz5szRms1mo+8jOTnZGLwrIyIiIqJQCtsAHEozZ850WiyWYt/HsWPHiuvfi4iIiIiaA1X9m4RGUlKSBHh7edu1a1fRg1tYWCj07NlTBLw9uadPn64S4t1uN86dOyckJSXJ5dtIBQUFQuVt8vPzhcrnuJBOp4NOpwvuBRERERFRWAjbHuBLLrlETkhIkDds2FAR0q1WK7Kzs5X9+/cXAWDgwIGixWJBVlZWxXVs2LBBKUkSrrnmGhEArrnmGnH79u0ql8tVceyvv/5addlll0lxcXFNeEVEREREFA5CGoCLi4vx/fffK77//nsFAPz222+K77//XnH06FFBoVAgMzPTNW/ePO0XX3yh2r9/v2LMmDH6pKQkefTo0R4A6N69uzRkyBDxz3/+s37nzp2KLVu2KB977DHdnXfe6Wnfvr0MAGPHjnWr1Wr5gQce0B08eFDxwQcfqN566y3NlClTXHW1jYiIiIhappCWQdu4caNy8ODBhguXjxkzxr18+XKHJEmYOXOmdunSpWqr1Sr0799fXLx4saNr164VQxfOnj2LSZMm6desWaNSKBS4/fbb3W+++abDaDz/3Nq+ffsUf/nLX3Tff/+9slWrVvKkSZNcM2bM8DsAswwaERERUXgLpAxa2NQBDmcMwEREREThrUXUASYiIiIiagwMwEREREQUURiAiYiIiCiiMAATERERUURhACYiIiKiiMIATEREREQRhQGYiIiIiCIKAzARERERRRQGYCIiIiKKKAzARERERBRRGICJiIiIKKIwABMRERFRRGEAJiIiIqKIwgBMRERERBGFAZiIiIiIIgoDMBERERFFFAZgIiIiIoooDMBEREREFFEYgImIiIgoojAAExEREVFEYQAmIiIioojCAExEREREEYUBmIiIiIgiCgMwEREREUUUBmAiIiIiiigMwEREREQUURiAiYiIiCiiqELdAKpB0Ung7QGAswTQRgN/3gHEtQt1q4iIiIhaBAbgcPNCG0Bynf/aYQH+kQooNMBzp0PWLCIiIqKWgkMgwsmF4bcyyeVdT0REREQXhQE4XBSdrD38+kgu73ZERERE1GAMwOHirT7B3Y6IiIiIasQAHC7E0uBuR0REREQ1YgAmIiIioojCAExEREREEYVl0IiImjtJBH7fAZQU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\n", "text/plain": [ "
" ] @@ -748,27 +910,55 @@ { "cell_type": "markdown", "id": "0eb92bf1", - "metadata": {}, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, "source": [ "So far we've learned how to simulate any dynamic system developed with QSDsan. \n", - "A complete list of existing unit operations within QSDsan is available [here](https://qsdsan.readthedocs.io/en/latest/api/sanunits/_index.html). The column \"Dynamic\" indicates whether the unit is enabled for dynamic simulations. Any system composed of the enabled units can be simulated dynamically as we learned above.\n", - "\n", + "A complete list of existing unit operations within QSDsan is available [here](https://qsdsan.readthedocs.io/en/latest/api/sanunits/_index.html). The column \"Dynamic\" indicates whether the unit is enabled for dynamic simulations. Any system composed of the enabled units can be simulated dynamically as we learned above." + ] + }, + { + "cell_type": "markdown", + "id": "497d72b8", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ "[Back to top](#top)" ] }, { "cell_type": "markdown", "id": "3d13e036", - "metadata": {}, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "### 1.2. When is a system \"dynamic\"?\n", + "It's ultimately the user's decision whether a system should be run dynamically. This section will cover the essentials to switch to the dynamic mode for system simulation." + ] + }, + { + "cell_type": "markdown", + "id": "94eab6a5", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, "source": [ - "### 1.2. What makes a system \"dynamic\"?\n", - "It's ultimately the user's decision whether a system should be run dynamically. This section will cover the essentials to switch to the dynamic mode for system simulation.\n", - "\n", "#### `System.isdynamic` vs. `SanUnit.isdynamic` vs. `SanUnit.hasode` \n", "\n", "- Simply speaking, when the `.isdynamic == True`, the program will attempt dynamic simulation. Users can directly enable/disable the dynamic mode by setting the `isdynamic` property of a `System` object.\n", "\n", - "- The program will deduct the value of `.isdynamic` when it's not specified by users. `.isdynamic` is considered `True` in all cases except when `.isdynamic == False` for all units.\n", + "- The program will set the value of `.isdynamic` when it's not specified by users. `.isdynamic` is considered `True` in all cases except when `.isdynamic == False` for all units.\n", "\n", "- Setting `.isdynamic = True` does not gaurantee the unit can be simulated dynamically. Just like how the `_run` method must be defined for static simulation, a series of additional methods must be defined to enable dynamic simulation.\n", "\n", @@ -777,9 +967,13 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 17, "id": "c130f36f", - "metadata": {}, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, "outputs": [ { "data": { @@ -792,7 +986,7 @@ " : True}" ] }, - "execution_count": 15, + "execution_count": 17, "metadata": {}, "output_type": "execute_result" } @@ -802,407 +996,14 @@ "{u: u.hasode for u in sys.units}" ] }, - { - "cell_type": "code", - "execution_count": 16, - "id": "b6a0612a", - "metadata": {}, - "outputs": [ - { - "data": { - "image/svg+xml": [ - "\n", - "\n", - "\n", - "\n", - "M1Mixer:e->A1CSTR:c\n", - "\n", - "\n", - "\n", - " ws26\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "A1CSTR:c->A2CSTR:c\n", - "\n", - "\n", - "\n", - " ws11\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "A2CSTR:c->O1CSTR:c\n", - "\n", - "\n", - "\n", - " ws13\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "O1CSTR:c->O2CSTR:c\n", - "\n", - "\n", - "\n", - " ws15\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "O2CSTR:c->O3CSTR:c\n", - "\n", - "\n", - "\n", - " ws17\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "O3CSTR:c->M1Mixer:c\n", - "\n", - "\n", - "\n", - " RWW\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "O3CSTR:c->C1Flat bottom circular clarifier:c\n", - "\n", - "\n", - "\n", - " treated\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "C1Flat bottom circular clarifier:c->M1Mixer:c\n", - "\n", - "\n", - "\n", - " RAS\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "C1Flat bottom circular clarifier:c->J1ASMto ADM:c\n", - "\n", - "\n", - "\n", - " WAS\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "C1Flat bottom circular clarifier:c-> effluent:w\n", - "\n", - "\n", - " effluent\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "J1ASMto ADM:c->AD1Anaerobic CSTR:c\n", - "\n", - "\n", - "\n", - " ws21\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "AD1Anaerobic CSTR:c->J2ADMto ASM:c\n", - "\n", - "\n", - "\n", - " ad eff\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "AD1Anaerobic CSTR:c-> biogas:w\n", - "\n", - "\n", - " biogas\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "J2ADMto ASM:c->M1Mixer:c\n", - "\n", - "\n", - "\n", - " ws25\n", - "\n", - "\n", - "\n", - "\n", - "\n", - " wastewater:e->M1Mixer:c\n", - "\n", - "\n", - " wastewater\n", - "\n", - "\n", - "\n", - "\n", - "\n", - " filler0:e->A1CSTR:c\n", - "\n", - "\n", - " filler0\n", - "\n", - "\n", - "\n", - "\n", - "\n", - " filler1:e->A1CSTR:c\n", - "\n", - "\n", - " filler1\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "M1Mixer\n", - "\n", - "\n", - "M1Mixer\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "A1CSTR\n", - "\n", - "\n", - "A1CSTR\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "A2CSTR\n", - "\n", - "\n", - "A2CSTR\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "O1CSTR\n", - "\n", - "\n", - "O1CSTR\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "O2CSTR\n", - "\n", - "\n", - "O2CSTR\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "O3CSTR\n", - "\n", - "\n", - "O3CSTR\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "C1Flat bottom circular clarifier\n", - "\n", - "\n", - "C1Flat bottom circular clarifier\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "J1ASMto ADM\n", - "\n", - "\n", - "J1ASMto ADM\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "AD1Anaerobic CSTR\n", - "\n", - "\n", - "AD1Anaerobic CSTR\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "J2ADMto ASM\n", - "\n", - "\n", - "J2ADMto ASM\n", - "\n", - "\n", - "\n", - "\n", - "\n", - " wastewater\n", - "\n", - "\n", - "\n", - "\n", - " effluent\n", - "\n", - "\n", - "\n", - "\n", - " biogas\n", - "\n", - "\n", - "\n", - "\n", - " filler0\n", - "\n", - "\n", - "\n", - "\n", - " filler1\n", - "\n", - "\n", - "\n", - "" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# Units without ODEs can also be simulated dynamically as long as \n", - "# the fundamental methods are defined. Here is an example.\n", - "from exposan import interface as inter\n", - "inter.load()\n", - "inter.sys.diagram()" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "id": "f2a81479", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{: False,\n", - " : True,\n", - " : True,\n", - " : True,\n", - " : True,\n", - " : True,\n", - " : True,\n", - " : False,\n", - " : True,\n", - " : False}" - ] - }, - "execution_count": 17, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "{u: u.hasode for u in inter.sys.units}\n", - "# inter.sys.isdynamic" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "id": "be199e8d", - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "C:\\Users\\joy_c\\Dropbox\\PhD\\Research\\QSD\\codes_developing\\QSDsan\\qsdsan\\sanunits\\_junction.py:573: UserWarning: Ignored dissolved H2 or CH4.\n", - " warn('Ignored dissolved H2 or CH4.')\n" - ] - }, - { - "data": { - "text/plain": [ - "(,\n", - " ,\n", - " ,\n", - " ,\n", - " )" - ] - }, - "execution_count": 18, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "uf = inter.sys.flowsheet.unit\n", - "inter.sys.simulate(t_span=(0,3), method='BDF', state_reset_hook='reset_cache')\n", - "inter.sys.scope.subjects" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "id": "ff2ea29e", - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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0tsMBoOtsnqqqDrn/cYbb7QtWbLE/MUXX5j++9//FtpsNtx8880hZ8+e5Xn3EqPRKERHR5f5HsXExEi5ubkwmdhB7C2NGzeW3n33XfPnn39u+vzzzwvj4uJsvXr10u3du5d/N6pJFEVMnDhR0717dzExMdFWWR1f3z1L7nnn67tnHD58WKHX60M1Gk3o448/rl2zZk1h+/btnZ53f/pZ5yB9IjfcfPPN4s033yyW+rqwTZs2+oULFwbPmjXL4su2EXlSmzZtbG3atCn5Q3XLLbeIp06dUsybNy941apV/Ni+GsaNG6c5duyYctu2bQWuq8lT5J53vr57xo033mg7cOBAfnZ2tvD5558HPfzww5qtW7eaKgvF/oLv9P1AVFSUpFQqUf4dUWZmplDZBSwGg0HKyMgo8/0zGo0KXvAiX3XOe3nBwcHo0KGD+Mcff/B3yUsMBoOUmZlZ5nuUkZEhhIWFQafT+apZAalLly78Wa+mcePGaTZs2KDavHlzQdOmTat8feHru+e4c97L4+t79ajVarRu3Vrq2rWrbe7cuZaEhATb22+/Heys1p9+1vlN9gNqtRo33XSTmJ6eXtJjL4oitmzZourWrZvTcTfJycni5s2by4yxSU9PV1ZWTxVV57yXZ7Va8csvvyhiY2P9+p1vXXb1Z73Mp1lpaWmqrl278me9lh0+fFgZExPDUOYGm82GcePGab766itVenq6qWXLli7PH1/fa6465708vr57hiRJsFicd7D70886h0z4iaeeeqpo1KhR2i5duojJycnivHnzgk0mkzB69OhiABg+fLimcePG0ty5cy0AMHHixKKUlBTdnDlzggcOHGhdtWpV0IEDB5SLFy/mR5lucPe8T506Nbh79+5iq1atbFeuXBHmzp2rPnfunGLMmDEVrp4l5/Ly8nDy5MmSN+OnTp1S7N+/XxEZGSk1a9ZMmjRpkvr8+fPCypUrzQAwfvz4okWLFgU//fTT6kceeaQ4LS1NuXbtWtW6des4gNgN7p73N954I7hFixa29u3bi2azWViyZEnQDz/8oNy4cSPPuxvGjRun+fTTT4P+97//mUJDQyXHPM4RERGS4xMOvr57XnXOO1/fa27SpEnqAQMGWOPj4215eXnCihUrgn788Uflhg0bLIB//6wzEPuJBx54wJqZmWmePn262mg0ComJieL69etNsbGxEgCcO3dOoVAoyoznW758eeHUqVPVU6dOVbds2dK2du3awqouGKCK3D3v2dnZwqOPPqo1Go1CRESEdNNNN4nbtm0r8PexUf5kz549yt69e5eMdZg8ebIagHrEiBHFy5cvN2dkZAjnzp0rCW4tW7aU1q1bZ3r66ac1//nPf4KbNGkiLVq0yDxgwAD2lrnB3fNeVFSESZMmac6fPy9otVopISHBtmnTJlPv3r153t2wePHiIADo1atXmfE9S5YsMT/yyCPFAF/fvaE6552v7zWXlZUljBw5Unt1WJvUvn1724YNG0z9+/cXAf/+Wec8xEREREQU0DiGmIiIiIgCGgMxEREREQU0BmIiIiIiCmgMxEREREQU0BiIiYiIiCigMRATERERUUBjICYiIiKigMZATEREREQBjYGYiKgO+/e//60ZOHCgtraPu3Tp0iBBEEIFQQidMGGCuqra+Ph4/RtvvBFc+mvHY69cueL9xhIRucClm4mI/JQgCKFV3f/iiy8WLViwwCxJUm01qYywsDAcP348X6/Xu9WAPXv2FPz444/Ke++9t9aDPBGRMwzERER+6u+//853/PuTTz4JeuWVV9THjx8v2RYaGiqFhlaZmb1KEAQ0btzY7TRuMBikhg0b+ibFExE5wSETRER+qnHjxpLjFh4eLjkCqOMWGhpaYcjErbfeqhs/frxmwoQJ6gYNGoRGR0frFy5cGJSfn48HH3xQExoaGtqyZUv9N998oyx9rCNHjij69u2r0+v1odHR0foHHnhAk5WVJbjb5oyMDGHAgAFarVYb2qxZM/1///tfdrwQkd9jICYiqmdWrFgRFBkZKe3atatg/PjxRRMmTNAMGTJE2717d3Hfvn0FvXv3tj700EPagoICAMCVK1fQq1cvXceOHcU9e/YUbNiwwWQ0GhXDhg1ze0jDQw89pPnrr78UaWlpps8++8y0cOHC4OoEayKi2sRATERUzyQkJIjTp08vuuGGG2ypqalFGo0GjRo1ksaNG1d8ww032KZNm2a5fPmycOjQISUAzJ8/P7hDhw62119/3dK2bVtbly5dbB9++GHhDz/8oPz1119l/5349ddfFd99951q8eLFhT179hS7du1q++CDD8yFhYXee7JERB7Aj7KIiOqZhIQEm+PfKpUKDRs2lNq3b1+yLSYmRgKAzMxMAQCOHDmi/PHHH5V6vb7CgOTff/9duPHGG2Ud99ixYwqVSoWkpKSSY7Vt29YWERFR/SdDRFQLGIiJiOqZoKCgMhesCYKAoKCgkq8VCnunr81mz635+fnCgAEDrK+//rq5/L6qc9EcEVFdw0BMRBTgbrrpJvGLL75QNW/eXCodnN3Vpk0bm9Vqxd69exXdunWzAcDx48cV2dnZnmoqEZFXcAwxEVGAmzBhQtGVK1eEf/3rX9pdu3YpfvvtN2HDhg3KBx98UGO1WmXvp02bNrY+ffqIjz32mHbHjh3KPXv2KB555BGNVsvphonIvzEQExEFuOuuu07avn27SRRF3HnnnSEdOnTQP/XUU5qIiAjJMbxCro8++qgwNjbWlpKSohs6dKhuzJgxxVFRURx2QUR+TZAkKdfXjSAiorpl6dKlQc8++6wmOzs7rzqPT09PV/bu3Vt3+fLlvAYNGni6eUREbmEPMRERVUtOTg70en3oM888o3bncW3atAn5xz/+ofNWu4iI3MUeYiIicltubi4yMjIEAGjQoAHcGRZx+vRpobi4GADQsmVLSalUungEEZF3MRATERERUUDjkAkiIiIiCmgMxEREREQU0BiIiYiIiCigMRATERERUUBjICYiIiKigMZATEREREQBjYGYiIiIiAIaAzERERERBbT/B5G9HGMhttiTAAAAAElFTkSuQmCC\n", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "fig, ax = uf.AD1.scope.plot_time_series(('S_ch4_gas', 'S_h2_gas', 'S_IC_gas'))" - ] - }, { "cell_type": "markdown", "id": "7839f0e2", - "metadata": {}, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, "source": [ "[Back to top](#top)" ] @@ -1210,7 +1011,11 @@ { "cell_type": "markdown", "id": "33a3d638", - "metadata": {}, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, "source": [ "## 2. Writing a dynamic `SanUnit` \n", "\n", @@ -1220,7 +1025,11 @@ { "cell_type": "markdown", "id": "220c984a", - "metadata": {}, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, "source": [ "### 2.1. Basic structure \n", "\n", @@ -1228,39 +1037,57 @@ "\n", "In comparison, during dynamic simulations, all information are stored as `_state` and `_dstate` attributes of the relevant `SanUnit` obejcts as well as `state` and `dstate` properties of `WasteStream` objects. These information won't be translated to mass or energy flows until dynamic simulation is completed.\n", "\n", - "- `WasteStream.state` is a 1d `numpy.array` of length $n+1$, $n$ is the length of the components associated with the `thermo`. Each element of the array represents value of one state variable.\n", + "- `WasteStream.state` is a 1d `numpy.array` of length $n+1$, $n$ is the length of the components associated with the `thermo`. Each element of the array represents value of one state variable." + ] + }, + { + "cell_type": "markdown", + "id": "b1529db3", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "---\n", + "#### Tip\n", "\n", - " ---\n", - " #### Tip\n", - " \n", - " Typically for a liquid `WasteStream`, the first $n$ element represents the component concentrations \\[mg/L\\], while the last element represents the total volumetric flow \\[m3/d\\]. For a gaseous `WasteStream`, the first $n$ state variables can simply be the mass flows \\[g/d\\] of the components if the last element is fixed at 1. This is because after completing dynamic simulations, the `WasteStream`'s mass flow is defined as the first $n$ element of this array multiplied by the last element.\n", + "Typically for a liquid `WasteStream`, the first $n$ element represents the component concentrations \\[mg/L\\], while the last element represents the total volumetric flow \\[m3/d\\]. For a gaseous `WasteStream`, the first $n$ state variables can simply be the mass flows \\[g/d\\] of the components if the last element is fixed at 1. This is because after completing dynamic simulations, the `WasteStream`'s mass flow is defined as the first $n$ element of this array multiplied by the last element.\n", "\n", - " ---" + "---" ] }, { "cell_type": "markdown", "id": "d997b05d", - "metadata": {}, + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, "source": [ "- `WasteStrem.dstate` is an array of the exact same shape as `WasteStream.state`, storing values of the time derivatives (i.e., the rates of change) of the state variables." ] }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 18, "id": "a8ae235a", - "metadata": {}, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, "outputs": [ { "data": { "text/plain": [ "array([3.000e+01, 8.899e-01, 4.389e+00, 1.886e-01, 9.784e+00, 5.720e-01,\n", - " 1.722e+00, 4.898e-01, 1.038e+01, 1.747e+00, 6.884e-01, 1.349e-02,\n", - " 4.954e+01, 2.751e+01, 9.986e+05, 1.806e+04])" + " 1.722e+00, 4.897e-01, 1.038e+01, 1.747e+00, 6.884e-01, 1.349e-02,\n", + " 4.954e+01, 2.751e+01, 9.978e+05, 1.806e+04])" ] }, - "execution_count": 20, + "execution_count": 18, "metadata": {}, "output_type": "execute_result" } @@ -1272,19 +1099,23 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 19, "id": "ab1496fd", - "metadata": {}, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, "outputs": [ { "data": { "text/plain": [ "sparse([3.000e+01, 8.899e-01, 4.389e+00, 1.886e-01, 9.784e+00, 5.720e-01,\n", - " 1.722e+00, 4.898e-01, 1.038e+01, 1.747e+00, 6.884e-01, 1.349e-02,\n", - " 4.954e+01, 2.751e+01, 9.988e+05])" + " 1.722e+00, 4.897e-01, 1.038e+01, 1.747e+00, 6.884e-01, 1.349e-02,\n", + " 4.954e+01, 2.751e+01, 9.981e+05])" ] }, - "execution_count": 21, + "execution_count": 19, "metadata": {}, "output_type": "execute_result" } @@ -1296,9 +1127,13 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 20, "id": "825050c1", - "metadata": {}, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, "outputs": [ { "data": { @@ -1306,7 +1141,7 @@ "True" ] }, - "execution_count": 22, + "execution_count": 20, "metadata": {}, "output_type": "execute_result" } @@ -1318,8 +1153,13 @@ { "cell_type": "markdown", "id": "eb706d47", - "metadata": {}, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, "source": [ + "\n", "- `SanUnit._state` is also a 1d `numpy.array`, but the length of the array is not assumed, because the state variables relevant for a `SanUnit` is entirely dependent on the unit operation itself. Therefore, there is no predefined units of measure or order for state variables of a unit operation.\n", "\n", "- `SanUnit._dstate`, similarly, must have the exact same shape as the `_state` array, as each element corresponds to the time derivative of a state variable." @@ -1327,9 +1167,13 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 21, "id": "956dbc0f", - "metadata": {}, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, "outputs": [ { "data": { @@ -1337,43 +1181,48 @@ "False" ] }, - "execution_count": 23, + "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "C1._state.shape == A1._state.shape" + "C1._state.shape == A1._state.shape\n", + "# C1._state.shape == C1._dstate.shape" ] }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 22, "id": "561a5589", - "metadata": {}, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, "outputs": [ { "data": { "text/plain": [ "{'S_I': 30.0,\n", - " 'S_S': 2.8098364831332874,\n", - " 'X_I': 1147.9022739122495,\n", - " 'X_S': 82.1499821192233,\n", - " 'X_BH': 2551.1711914474663,\n", - " 'X_BA': 148.18618671733967,\n", - " 'X_P': 447.1254556866469,\n", - " 'S_O': 0.004289034729931556,\n", - " 'S_NO': 5.338805118093289,\n", - " 'S_NH': 7.929128379209027,\n", - " 'S_ND': 1.2166810265512678,\n", - " 'X_ND': 5.285761329880132,\n", - " 'S_ALK': 59.15859597894533,\n", - " 'S_N2': 25.007887255081272,\n", - " 'H2O': 998557.4809730583,\n", + " 'S_S': 2.8098296544615704,\n", + " 'X_I': 1147.8970757884535,\n", + " 'X_S': 82.14996504835973,\n", + " 'X_BH': 2551.1712941951987,\n", + " 'X_BA': 148.18576250649838,\n", + " 'X_P': 447.1086242830684,\n", + " 'S_O': 0.004288622012845044,\n", + " 'S_NO': 5.33892893863284,\n", + " 'S_NH': 7.928812844268634,\n", + " 'S_ND': 1.216680910568711,\n", + " 'X_ND': 5.285760801254182,\n", + " 'S_ALK': 59.158219028756534,\n", + " 'S_N2': 25.008073542375985,\n", + " 'H2O': 997794.331078558,\n", " 'Q': 92229.99999999996}" ] }, - "execution_count": 24, + "execution_count": 22, "metadata": {}, "output_type": "execute_result" } @@ -1387,7 +1236,11 @@ { "cell_type": "markdown", "id": "68f067f1", - "metadata": {}, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, "source": [ "[Back to top](#top)" ] @@ -1395,11 +1248,25 @@ { "cell_type": "markdown", "id": "b6a928f2", - "metadata": {}, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, "source": [ "### 2.2. Fundamental methods\n", - "In addition to proper `__init__` and `_run` methods ([recap](https://qsdsan.readthedocs.io/en/latest/tutorials/5_SanUnit_advanced.html#2.1.-Fundamental-methods)), a few more methods are required in a `SanUnit` subclass for dynamic simulation. Users typically won't interact with these methods but they will be called by `System.simulate` to manipulate the values of the arrays mentioned [above](#s2.1) (i.e., `._state`, `._dstate`, `.state`, and `.dstate`).\n", - "\n", + "In addition to proper `__init__` and `_run` methods ([recap](https://qsdsan.readthedocs.io/en/latest/tutorials/5_SanUnit_advanced.html#2.1.-Fundamental-methods)), a few more methods are required in a `SanUnit` subclass for dynamic simulation. Users typically won't interact with these methods but they will be called by `System.simulate` to manipulate the values of the arrays mentioned [above](#s2.1) (i.e., `._state`, `._dstate`, `.state`, and `.dstate`)." + ] + }, + { + "cell_type": "markdown", + "id": "976dabeb", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ "- `_init_state`, called after `_run` to generate an initial condition for the unit, i.e., defining shape and values of the `_state` and `_dstate` arrays. For example:\n", "```python\n", "import numpy as np\n", @@ -1408,9 +1275,18 @@ " self._state = np.ones(len(inf.components)+1)\n", " self._dstate = self._state * 0.\n", "```\n", - "This method (not saying it makes sense) assumes $n+1$ state variables and gives an initial value of 1 to all of them. Then it also sets the initial time derivatives to be 0. \n", - "\n", - "\n", + "This method (not saying it makes sense) assumes $n+1$ state variables and gives an initial value of 1 to all of them. Then it also sets the initial time derivatives to be 0. " + ] + }, + { + "cell_type": "markdown", + "id": "3a3de71d", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ "- `_update_state`, to update effluent streams' state arrays based on current state (and maybe dstate) of the SanUnit. For example:\n", "```python\n", "def _update_state(self):\n", @@ -1418,8 +1294,18 @@ " eff, = self.outs # assuming this SanUnit has one outlet only\n", " eff.state[:] = arr # assume arr has the same shape as WasteStream.state\n", "```\n", - "The goal of this method is to update the values in `.state` for each `WasteStream` in `.outs`.\n", - "\n", + "The goal of this method is to update the values in `.state` for each `WasteStream` in `.outs`." + ] + }, + { + "cell_type": "markdown", + "id": "8deec2a2", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ "- `_update_dstate`, to update effluent streams' `dstate` arrays based on current `_state` and `_dstate` of the SanUnit. The signiture and often the algorithm are similar to `_update_state`.\n", "\n", "\n", @@ -1430,7 +1316,18 @@ " if self._ODE is None:\n", " self._compile_ODE()\n", " return self._ODE \n", - "```\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "a431142f", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ "```python\n", "def _compile_ODE(self):\n", " _dstate = self._dstate\n", @@ -1439,14 +1336,36 @@ " _dstate[:] = some_algorithm(t, y_ins, y, dy_ins)\n", " _update_dstate()\n", " self._ODE = dy_dt\n", - "```\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "83c50a89", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ "```python\n", "@property\n", "def AE(self):\n", " if self._AE is None:\n", " self._compile_AE()\n", " return self._AE\n", - "```\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "dd66c263", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ "```python\n", "def _compile_AE(self):\n", " _state = self._state\n", @@ -1459,8 +1378,18 @@ " _update_state()\n", " _update_dstate()\n", " self._AE = y_t\n", - "```\n", - "\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "a144502d", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ "> **Note**: Within the `dy_dt` or `y_t` functions, `._state[:] = ` rather than `._state = ` because it's generally faster to update values in an existing array than overwriting this array with a newly created array.\n", "\n", "We'll learn more about these two methods in the next subsections." @@ -1469,7 +1398,11 @@ { "cell_type": "markdown", "id": "7cb3c766", - "metadata": {}, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, "source": [ "[Back to top](#top)" ] @@ -1477,7 +1410,11 @@ { "cell_type": "markdown", "id": "afd475f2", - "metadata": {}, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, "source": [ "### 2.3. Making a simple MixerSplitter (`_compile_AE`)\n", "\n", @@ -1486,9 +1423,13 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 23, "id": "c38b235a", - "metadata": {}, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, "outputs": [], "source": [ "# Typically if implemented as a static SanUnit, it'd be pretty simple\n", @@ -1520,9 +1461,13 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 24, "id": "9b5ce52d", - "metadata": {}, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, "outputs": [ { "name": "stdout", @@ -1540,34 +1485,37 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 25, "id": "12aa03d9", "metadata": { - "scrolled": false + "scrolled": false, + "slideshow": { + "slide_type": "slide" + } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "WasteStream: ws28\n", - " phase: 'l', T: 298.15 K, P: 101325 Pa\n", - " flow (g/hr): S_S 3e+03\n", - " S_NH 2.1e+03\n", - " H2O 8e+05\n", + "WasteStream: ws12\n", + "phase: 'l', T: 298.15 K, P: 101325 Pa\n", + "flow (g/hr): S_S 3e+03\n", + " S_NH 2.1e+03\n", + " H2O 8e+05\n", " WasteStream-specific properties:\n", " pH : 7.0\n", " Alkalinity : 2.5 mg/L\n", - " COD : 3711.6 mg/L\n", - " BOD : 2661.2 mg/L\n", - " TC : 1187.7 mg/L\n", - " TOC : 1187.7 mg/L\n", - " TN : 2598.1 mg/L\n", + " COD : 3711.8 mg/L\n", + " BOD : 2661.3 mg/L\n", + " TC : 1187.8 mg/L\n", + " TOC : 1187.8 mg/L\n", + " TN : 2598.2 mg/L\n", " TP : 37.1 mg/L\n", " Component concentrations (mg/L):\n", - " S_S 3711.6\n", - " S_NH 2598.1\n", - " H2O 989764.3\n" + " S_S 3711.8\n", + " S_NH 2598.2\n", + " H2O 989803.5\n" ] } ], @@ -1580,79 +1528,82 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 26, "id": "4ff4c667", "metadata": { - "scrolled": true + "scrolled": true, + "slideshow": { + "slide_type": "slide" + } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "MixerSplitter1: M2\n", + "MixerSplitter1: M1\n", "ins...\n", - "[0] ws27\n", - " phase: 'l', T: 298.15 K, P: 101325 Pa\n", - " flow (g/hr): S_O 5e+03\n", - " H2O 1e+06\n", + "[0] ws11\n", + "phase: 'l', T: 298.15 K, P: 101325 Pa\n", + "flow (g/hr): S_O 5e+03\n", + " H2O 1e+06\n", " WasteStream-specific properties:\n", " pH : 7.0\n", - "[1] ws28\n", - " phase: 'l', T: 298.15 K, P: 101325 Pa\n", - " flow (g/hr): S_S 3e+03\n", - " S_NH 2.1e+03\n", - " H2O 8e+05\n", + "[1] ws12\n", + "phase: 'l', T: 298.15 K, P: 101325 Pa\n", + "flow (g/hr): S_S 3e+03\n", + " S_NH 2.1e+03\n", + " H2O 8e+05\n", " WasteStream-specific properties:\n", " pH : 7.0\n", - " COD : 3711.6 mg/L\n", - " BOD : 2661.2 mg/L\n", - " TC : 1187.7 mg/L\n", - " TOC : 1187.7 mg/L\n", - " TN : 2598.1 mg/L\n", + " COD : 3711.8 mg/L\n", + " BOD : 2661.3 mg/L\n", + " TC : 1187.8 mg/L\n", + " TOC : 1187.8 mg/L\n", + " TN : 2598.2 mg/L\n", " TP : 37.1 mg/L\n", "outs...\n", - "[0] ws29\n", - " phase: 'l', T: 298.15 K, P: 101325 Pa\n", - " flow (g/hr): S_S 1e+03\n", - " S_O 1.67e+03\n", - " S_NH 700\n", - " H2O 6e+05\n", + "[0] ws13\n", + "phase: 'l', T: 298.15 K, P: 101325 Pa\n", + "flow (g/hr): S_S 1e+03\n", + " S_O 1.67e+03\n", + " S_NH 700\n", + " H2O 6e+05\n", " WasteStream-specific properties:\n", " pH : 7.0\n", - " COD : 1645.9 mg/L\n", - " BOD : 1180.1 mg/L\n", - " TC : 526.7 mg/L\n", - " TOC : 526.7 mg/L\n", - " TN : 1152.1 mg/L\n", + " COD : 1650.8 mg/L\n", + " BOD : 1183.6 mg/L\n", + " TC : 528.3 mg/L\n", + " TOC : 528.3 mg/L\n", + " TN : 1155.6 mg/L\n", " TP : 16.5 mg/L\n", - "[1] ws30\n", - " phase: 'l', T: 298.15 K, P: 101325 Pa\n", - " flow (g/hr): S_S 1e+03\n", - " S_O 1.67e+03\n", - " S_NH 700\n", - " H2O 6e+05\n", + "[1] ws14\n", + "phase: 'l', T: 298.15 K, P: 101325 Pa\n", + "flow (g/hr): S_S 1e+03\n", + " S_O 1.67e+03\n", + " S_NH 700\n", + " H2O 6e+05\n", " WasteStream-specific properties:\n", " pH : 7.0\n", - " COD : 1645.9 mg/L\n", - " BOD : 1180.1 mg/L\n", - " TC : 526.7 mg/L\n", - " TOC : 526.7 mg/L\n", - " TN : 1152.1 mg/L\n", + " COD : 1650.8 mg/L\n", + " BOD : 1183.6 mg/L\n", + " TC : 528.3 mg/L\n", + " TOC : 528.3 mg/L\n", + " TN : 1155.6 mg/L\n", " TP : 16.5 mg/L\n", - "[2] ws31\n", - " phase: 'l', T: 298.15 K, P: 101325 Pa\n", - " flow (g/hr): S_S 1e+03\n", - " S_O 1.67e+03\n", - " S_NH 700\n", - " H2O 6e+05\n", + "[2] ws15\n", + "phase: 'l', T: 298.15 K, P: 101325 Pa\n", + "flow (g/hr): S_S 1e+03\n", + " S_O 1.67e+03\n", + " S_NH 700\n", + " H2O 6e+05\n", " WasteStream-specific properties:\n", " pH : 7.0\n", - " COD : 1645.9 mg/L\n", - " BOD : 1180.1 mg/L\n", - " TC : 526.7 mg/L\n", - " TOC : 526.7 mg/L\n", - " TN : 1152.1 mg/L\n", + " COD : 1650.8 mg/L\n", + " BOD : 1183.6 mg/L\n", + " TC : 528.3 mg/L\n", + " TOC : 528.3 mg/L\n", + " TN : 1155.6 mg/L\n", " TP : 16.5 mg/L\n" ] } @@ -1665,9 +1616,14 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": 27, "id": "72151a1e", - "metadata": {}, + "metadata": { + "scrolled": true, + "slideshow": { + "slide_type": "slide" + } + }, "outputs": [], "source": [ "# Obviously, it's not ready for dynamic simulation\n", @@ -1679,30 +1635,59 @@ { "cell_type": "markdown", "id": "0c4eb0cd", - "metadata": {}, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, "source": [ "Since the mixer-splitter mixes and splits instantly, we can express this process with a set of algebraic equations (AEs). Assume its array of state variables follow the \"concentration-volumetric flow\" convention. In mathematical forms, state variables of the mixer-splitter ($C_m$, component concentrations; $Q_m$, total volumetric flow) follow:\n", "$$Q_m = \\sum_{i \\in ins} Q_i \\tag{1}$$\n", "$$Q_mC_m = \\sum_{i \\in ins} Q_iC_i$$\n", - "$$\\therefore C_m = \\frac{\\sum_{i \\in ins} Q_iC_i}{Q_m} \\tag{2}$$\n", + "$$\\therefore C_m = \\frac{\\sum_{i \\in ins} Q_iC_i}{Q_m} \\tag{2}$$" + ] + }, + { + "cell_type": "markdown", + "id": "a37f98d9", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ "Therefore, the time derivatives $\\dot{Q_m}$ follow:\n", "$$\\dot{Q_m} = \\sum_{i \\in ins} \\dot{Q_i} \\tag{3}$$\n", "$$Q_m\\dot{C_m} + C_m\\dot{Q_m} = \\sum_{i \\in ins} (Q_i\\dot{C_i} + C_i\\dot{Q_i})$$\n", - "$$\\therefore \\dot{C_m} = \\frac{1}{Q_m}\\cdot(\\sum_{i \\in ins}Q_i\\dot{C_i} + \\sum_{i \\in ins}C_i\\dot{Q_i} - C_m\\dot{Q_m}) \\tag{4}$$\n", + "$$\\therefore \\dot{C_m} = \\frac{1}{Q_m}\\cdot(\\sum_{i \\in ins}Q_i\\dot{C_i} + \\sum_{i \\in ins}C_i\\dot{Q_i} - C_m\\dot{Q_m}) \\tag{4}$$" + ] + }, + { + "cell_type": "markdown", + "id": "7578a12e", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ "For any effluent `WasteStream` $j$:\n", "$$Q_j = \\frac{Q_m}{n_{outs}} \\tag{5}$$\n", "$$C_j = C_m \\tag{6}$$\n", "$$\\therefore \\dot{Q_j} = \\frac{\\dot{Q_m}}{n_{outs}} \\tag{7}$$\n", "$$\\dot{C_j} = \\dot{C_m} \\tag{8}$$\n", - "Now, let's try to implement this algorithm in methods for dynamic simulation.\n", - "\n" + "Now, let's try to implement this algorithm in methods for dynamic simulation." ] }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 28, "id": "38abf7cb", - "metadata": {}, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, "outputs": [], "source": [ "import numpy as np\n", @@ -1758,7 +1743,11 @@ { "cell_type": "markdown", "id": "da258438", - "metadata": {}, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, "source": [ ">**Note**: \n", ">1. All `SanUnit._AE` must take exactly these three postional arguments (`t`, `y_ins`, `dy_ins`). `t` is time as a `float`. Both `y_ins` and `dy_ins` are **2d** `numpy.array` of the same shape `(m, n+1)`, where $m$ is the number of inlets, $n+1$ is the length of the `state` or `dstate` array of a `WasteStream`.\n", @@ -1768,9 +1757,13 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 29, "id": "ba8c9001", - "metadata": {}, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, "outputs": [], "source": [ "# Now let's see if this works\n", @@ -1782,13 +1775,17 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": 30, "id": "a4f65bf6", - "metadata": {}, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, "outputs": [ { "data": { - "image/png": "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qoceekgNACEhIWJhYaFwbX/2mV/7TPDN2vj5+UGtVldbk1KphL+/f+WN636JiIiI7gwuDcA2mw2JiYnKjRs3ytLT041RUVFVlim8+eabpkOHDpUeOHCg8gYAH3zwQcXKlSvLAKB79+6Wo0ePSuyhFwC2bNki8/PzQ/v27W0AEBsba83IyKgy252WliaLiYmxgoiIiIi8ikuXQCQmJiq/+eYb+b///W+jVqsVc3NzBQDQ6XSiWq1G48aNxeo++Na8eXObPSwnJCRYW7dubRs+fLhqwYIF5fn5+UJycrLipZdeMimVSgDAhAkTTMuXL/eZOnWqYsyYMea0tDTphg0bZCkpKQ7tAEFEREREdw6XzgB/+umncoPBgN69e6ubNGmisd/Wrl3r8CEVMpkMmzZtMkqlUvGBBx7wff7551XDhw83z507t3L9blRUlJiSkmJMT0+X3Xfffb6LFi1SLF++vHzAgAGcASYiIiLyMm61D7A74z7ARERERO7NI/cBJiIiIiKqbwzARERERORVGICJiIiIyKswABMRERGRV2EAJiIiIiKvwgBMRERERF6FAZiIiIiIvAoDMBERERF5FQZgIiIiIvIqDMBERERE5FUYgImIiIjIqzAAExEREZFXYQAmIiIiIq/CAExEREREXoUBmIiIiIi8CgMwEREREXkVBmAiIiIi8ioMwERERETkVRiAiYiIiMirMAATERERkVdhACYiIiIir8IATERERERehQGYiIiIiLwKAzAREREReRUGYCIiIiLyKgzARERERORVGICJiIiIyKswABMRERGRV2EAJiIiIiKv4tIAPHv2bJ/777/fV6vVaoOCgjQDBw5UHTt2rLKmS5cuYcKECcp77rnHV6VSaZs1a6Z5+eWXFXq9vko/Z86cERISElRqtVobFBSkmTJlisJsNldpk56eLu3YsaOvQqHQRkVFaVasWCFvkIskIiIiIrfi0gC8Y8cOWWJiomn37t2lW7ZsMZrNZsTHx6tLSkoAAH/++ackNzdXWLBgQcXhw4dLPv/887KffvpJNmrUKJW9D4vFgkceeURtMpmEzMzM0pUrV5atXr1aPnPmTIW9zcmTJ4VBgwape/ToYTlw4EDpxIkTK8aPH69MTU2VuuCyiYiIiMiFBFEUi1xdhF1BQYEQGhqqycjIMPbs2dNaXZuvv/5a9sILL6hKSkqK5XI5Nm3aJB08eLD6woULJWFhYSIALFmyRD5z5kxlYWFhsUKhwKuvvqr48ccfZTk5OaX2foYNG6bS6/XCzz//bHSkNoPBAJ1OpzUYDPDz86ubCyYiIiKiOlNUVAR/f3/o9fpif3//m7ZzqzXABoMBABAYGCjerI1erxe0Wq0ol19dwbB7925Zu3btbPbwCwD9+/e3FBUV4ciRIxIA2Lt3r7R3796Wa/vp16+fJTs7+6YzwOXl5TAYDJW3oiK3+T2BiIiIiG6D2wRgq9WKyZMnK7t162aNjo62Vdfm4sWLwrx58xSjR4+uXOBbUFAgBAcHVwnMoaGhIgDk5+cLt2pTVFQEo7H6CeA5c+YodDqd1n4LDw/X3uYlEhEREZEbcJsAnJiYqMzJyZF+/fXXZdU9bjAYMGDAAFXr1q1ts2fPrqjvepKSkir0en2x/Xbu3Lni+n5NIiIiIqp/stt5cllZGVQqVc0Na5CYmKhMTU2Vbd++vTQ8PPyG5Q9FRUWIj49XazQabNy40ejj41P5WEhIiLhv3z7h2vb2mV/7THBISIhYWFh4Qxs/Pz+o1epqa1IqlVAqlbd9bURERETkXpyeAbZarUhOTvZp3LixRqvVav/44w8BAN58803FP/7xD6e2FrPZbEhMTFRu3LhRlp6eboyKiroh/BoMBvTt21ft4+ODTZs2Ga8P3N27d7ccPXpUYg+9ALBlyxaZn58f2rdvbwOA2NhYa0ZGRpWwn5aWJouJian2g3ZEREREdOdyOgC/8847PqtXr/Z5//33y6+die3QoYP1888/dyoAJyYmKtetWyf/6quvyrRarZibmyvk5uYK9nW59vBbWloq/POf/ywzGAyCvY3FcvUzbQkJCdbWrVvbhg8frjpw4IAkNTVVmpycrHjppZdM9hncCRMmmM6cOSOZOnWqIicnR/L3v/9dvmHDBtmUKVPqfSkFEREREbkXp7dBi4qK0ixbtqysX79+Vq1Wqz148GDJ3XffLebk5Eji4uJ8r1y54vBaWUEQqv1g2WeffVY+ZswYc3p6urRPnz7VrlE4efJkSYsWLUQAOH36tDB+/HhlZmamTK1WiyNGjDAvWLCgwr5TBHD1IIypU6cqf/vtN0mTJk3EGTNmVIwZM8ZcXd/V4TZoRERERO7N0W3QnF4DnJubK7Rs2fKGXRpsNhuuP32tJqIo3jIs9+7d21pTGwCIjIwUt2zZUu2H567t69dffy29VRsiIiIiuvM5vQSidevWth07dtwQnL/99lvZvffeyzW1REREROTWnJ4BfuuttypefPFF1YULFwSbzYb169fLjx8/LlmzZo1848aNDp2qRkRERETkKk7PAA8ZMsTy/fffG9PT02VqtVp89913Fb/99pvk+++/NyYkJHAGmIiIiIjcWq32Ae7Ro4e1R48enO0lIiIiIo/jNifBERERERE1BIdmgBs1aqQVBKHmhgAuX77MI4OJiIiIyG05FID/9re/ldd3IUREREREDcGhAPziiy86t8EvEREREZGbqtWH4CwWCzZs2CDLycmRAEC7du1sjz/+uOXak9eIiIiIbofVanX6kC26s8nlckil0tvux+mjkI8cOSIZPHiwuqCgoPJEuD/++ENy1113iSkpKcbo6OgbTom7E/AoZCIiooYhiiLy8/Oh1+tdXQq5IZ1Oh9DQUFT3+bR6Owp5zJgxyjZt2lh/+eWXsoCAAADA5cuX8fzzz6vGjRunzMrK4vZoREREVGv28BscHAy1Wl1t0CHvI4oijEYjCgsLAQBhYWG17svpAHz48GFpdnZ2qT38AkBAQADmzZtXERsb61vrSoiIiMjrWa3WyvAbGBjo6nLIzahUKgBAYWEhgoODa70cwul9gO+++25bfn7+Db+KFRQUCC1atLgjlz8QERFRw7Cv+VWr1S6uhNyV/WfjdtaHOzQDbDAYKv993rx5FZMnT1YmJydXdOvWzQoAe/bskc6ePVvx/vvvV9S6EiIiIqL/4bIHupm6+Nmo1UEYoijimWeeUdnvE0URAPDYY4+prFYrD8IgIiIiIrflUABOS0vjB9uIiIjIY1htIrJPX0ZhcTmCtUrERAZAKrmzZpUjIiJw9uxZAMCVK1eg0+lcW9B1Zs2ahXfeeQcAsHDhQrzyyiuuLegaDgXgXr16Weu7ECIiIqK6sPn/8vDOf3KQZ/jrINswfyWSB7ZFQvva7xxwKxcvXsTbb7+NH374AQUFBWjUqBHuvfdevP3224iLi6vx+b/++iveeustZGVloaioCKGhoYiNjcXixYsRHBx80+e9++67GDt2LG615Vddi4yMxGeffQaZTIaePXveNHxPmzYN48ePR5cuXRqsNkfV6iAMACgtLcXZs2clJpOpyv0dO3bkB+GIiIjIJTb/Xx4S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GeCgoCBRJpOhTZs21mvvb926te38+fMCcHUm12Qy4cqVK1WeW1hYKNhneUNCQsTCwsIqM732mV97GyIiIiLyHm4bgBUKBe6//37r8ePHq9R44sQJSXh4uAgAXbp0scrlcvz888+VM9nHjh2TnD9/XujevbsVALp37245evSo5NrlDlu2bJH5+fmhffv2toa6HiIiIiJyDy5dAlFcXIxrA+6pU6ck+/fvlwQGBooRERHitGnTTM8++6zqwQcftPbu3duSmpoqS01NlaWnpxsBQKfTYeTIkeZp06YpAwICyvz8/MRJkyYpY2NjrXFxcVYASEhIsLZu3do2fPhw1YIFC8rz8/OF5ORkxUsvvWTiEgciIiIi7+PSbdDS09Olffr0UV9///Dhw82rV68uB4BPP/1UvmDBAp8///xT0rJlS9usWbMqhgwZUrnrwzUHYcgrKipgPwijcePGlcsbTp8+LYwfP16ZmZkpU6vV4ogRI3gQBhEREdEdxiNOgvMkDMBERERE7s3j9wEmIiIiIqoPDMBERERE5FUYgImIiIjIqzAAExEREZFXYQAmIiIiIq/CAExEREREXoUBmIiIiIi8CgMwEREREXkVBmAiIiIi8ioMwERERETkVRiAiYiIiMirMAATERERkVdhACYiIiIir8IATERERERehQGYiIiIiLwKAzAREREReRUGYCIiIiLyKgzARERERORVGICJiIiIyKswABMRERGRV2EAJiIiIiKvwgBMRERERF6FAZiIiIiIvAoDMBERERF5FQZgIiIiIvIqDMBERERE5FUYgImIiIjIqzAAExEREZFXcWkA3rp1q3TAgAGqsLAwjSAI2g0bNshu1nbs2LFKQRC0H374oc+191+6dAlPP/20ys/PT6vT6bQjR45UFhcXV3nuoUOHJHFxcWqlUqlt2rSpZt68eVX6ICIiIiLv4dIAXFpaiujoaNvixYvLb9Vu/fr1sr1790rDwsLE6x975pln1Dk5OZLNmzcbN27caNy5c6d0zJgxKvvjBoMB8fHx6vDwcNu+fftK58+fXz5nzhzF0qVL5fVxTURERETk3m4649oQHn30Ueujjz5qvVWb8+fPC5MnT1Zu3rzZ+Oijj6qvfezo0aOSn3/+WZqVlVUaGxtrA4CPP/64fODAgeq//e1vQtOmTcUvv/xSbjabhVWrVpUrFAp06NDBdvDgQdOiRYt8JkyYYK7P6yMiIiIi9+PWa4CtVitGjBihmjp1qqlDhw626x/ftWuXVKfTwR5+AaBfv35WiUSCrKwsKQBkZWVJ4+LiLAqFovJ5CQkJlhMnTkguX75809cuLy+HwWCovBUVFdXtxRERERGRS7h1AJ43b56PVCrFlClTTNU9np+fLwQFBVUJxnK5HI0aNRLz8vKE/7WRhISEVFk6ERoaKgJAXl7eTa9/zpw5Cp1Op7XfwsPDtbd/RURERETkam4bgLOzsyVLlizx+eKLL8okkoYvMykpqUKv1xfbb+fOnSuu+VlERERE5O7cNgDv2LFDdvHiRSEiIkIjk8m0MplMe+7cOWH69OmK5s2ba4CrM7kXL16scg1msxlXrlwR7B+YCw0NtRUUFAjXtsnPzxcAICws7IZlFXZKpRL+/v6VNz8/v7q/SCIiIiJqcG4bgEeOHGk+dOhQ6YEDBypvYWFh4pQpU0ybN282AkBcXJxVr9cjOzu78jrS0tKkNpsNXbt2tQJA165drbt27ZKZTH+tovjpp59kLVu2tAUEBDT4dRERERGRa7l0F4ji4mIcP368MryeOnVKsn//fklgYKAYEREh3nXXXVXW7srlcoSGhopt2rSxAUC7du1sffv2tY4bN061bNmyMrPZLEyaNEk5bNgwS9OmTUUAGDFihHnOnDmKUaNGKd944w3TkSNHJJ988onPBx98cMut14iIiIjozuTSAJydnS3t06dP5dZmr7/+ugKAYvjw4ebVq1c7FFDXrVtnnDBhgqpfv36+EokEjz32mHnJkiWVz9XpdNiyZYvx5ZdfVnbp0sU3MDBQnDFjRgW3QCMiIiLyToIoitzfywEGgwE6nU5rMBi4HpiIiIjIDRUVFcHf3x96vb7Y39//pu3cdg0wEREREVF9YAAmIiIiIq/CAExEREREXoUBmIiIiIi8CgMwEREREXkVBmAiIiIi8ioMwERERETkVRiAiYiIiMirMAATERERkVdhACYiIiIir8IATERERERehQGYiIiIiLwKAzAREREReRUGYCIiIiLyKgzARERERORVGICJiIiIyKswABMRERGRV2EAJiIiIiKvwgBMRERERF6FAZiIiIiIvAoDMBERERF5FQZgIiIiIvIqDMBERERE5FUYgImIiIjIqzAAExEREZFXYQAmIiIiIq/CAExERERE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\n", + "image/png": "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aQHg8ccfVz300ENqAAgPDxdzc3OFsv05Zn4dM8GVtQkICIBara7t0yMiIiIiD+LWAGy32xEfH6/csmWLLDU11dS0adNyyxReffVV87FjxwqPHDlS+gCA9957r2T16tVFANC9e3frqVOnJI7QCwDbt2+XBQQEoE2bNnYAiImJsaWlpZWb7U5JSZF16dLFBiIiIiLyKW5dAhEfH6/cuHGj/OuvvzZptVrxypUrAgDodDpRrVajfv36YkUXvjVu3NjuCMtxcXG2li1b2kePHq1atGhRcXZ2tpCQkKCYNGmS2bGEYcqUKeaVK1f6zZw5UzF+/HhLSkqKdPPmzbKkpKSKFwATERER0V3LrTPAH3/8sdxoNKJPnz7qBg0aaByP9evXO32TCplMhq1bt5qkUqn4wAMP+D/zzDOq0aNHW956663SPcyaNm0qJiUlmVJTU2X333+//5IlSxQrV64sHjRoEGeAiYiIiHyMR+0D7MmMRiN0Oh33ASYiIiLyUF65DzARERERUW1jACYiIiIin8IATEREREQ+hQGYiIiIiHwKAzARERER+RQGYCIiIiLyKQzARERERORTGICJiIiIyKcwABMRERGRT2EAJiIiIiKfwgBMRERERD6FAZiIiIiIfAoDMBERERH5FAZgIiIiIvIpDMBERERE5FMYgImIiIjIpzAAExEREZFPYQAmIiIiIp/CAExEREREPoUBmIiIiIh8CgMwEREREfkUBmAiIiIi8ikMwERERETkUxiAiYiIiMinMAATERERkU9hACYiIiIin8IATEREREQ+hQGYiIiIiHyKWwPwggUL/Dp27Oiv1Wq1oaGhmiFDhqhOnz5dWtO1a9cwZcoUZfPmzf1VKpW2UaNGmhdeeEFhMBjK9XPx4kUhLi5OpVartaGhoZoZM2YoLBZLuTapqanS9u3b+ysUCm3Tpk01q1atktfJSRIRERGRR3FrAN69e7csPj7evH///sLt27ebLBYLBgwYoC4oKAAA/PHHH5IrV64IixYtKjlx4kTBp59+WvTDDz/InnvuOZWjD6vVisGDB6vNZrOwZ8+ewtWrVxetXbtWPnfuXIWjzblz54ShQ4eqe/bsaT1y5Ejh1KlTSyZPnqxMTk6WuuG0iYiIiMiNBFEU89xdhENOTo6g1+s1aWlppl69etkqarNhwwbZs88+qyooKMiXy+XYunWrdNiwYerLly8XREREiACwbNky+dy5c5W5ubn5CoUCL730kuL777+XZWRkFDr6GTlypMpgMAg//vijyZnajEYjdDqd1mg0IiAgoGZOmIiIiIhqTF5eHgIDA2EwGPIDAwMrbedRa4CNRiMAICQkRKysjcFgELRarSiX31zBsH//flnr1q3tjvALAAMHDrTm5eXh5MmTEgD4+eefpX369LGW7ad///7WAwcOVDoDXFxcDKPRWPrIy/OY3xOIiIiI6A54TAC22WyYPn26slu3brZ27drZK2pz9epVYeHChYpx48aVLvDNyckRwsLCygVmvV4vAkB2drZwuzZ5eXkwmSqeAE5MTFTodDqt4xEZGam9w1MkIiIiIg9wRwG4qKiopupAfHy8MiMjQ7phw4YKOzUajRg0aJCqZcuW9gULFpTU2BtXYt68eSUGgyHf8cjMzMyv7fckIiIiotrncgC22WxISEjwq1+/vkar1Wp/++03AQBeffVVxT//+c9q7awQHx+vTE5OlqWlpRVGRkbesvwhLy8PAwYMUGs0GmzZssXk5+dX+lp4eLiYm5srlG3vmPl1zARX1iYgIABqtbrCmpRKJQIDA0sfXPdLREREdHdwOQD/7W9/81u7dq3fO++8U1w2iLZt29b26aefuhSA7XY74uPjlVu2bJGlpqaamjZtekv4NRqN6Nevn9rPzw9bt241qVSqcq93797deurUKYkj9ALA9u3bZQEBAWjTpo0dAGJiYmxpaWmysselpKTIunTpUuGFdkRERER093I5AK9bt85v5cqVRc8884xVKv3zGrL27dvbz5w549K2YvHx8covv/xS/sUXXxRptVrxypUrwpUrVwTHulxH+C0sLBT+9a9/FRmNRsHRxmq9eU1bXFycrWXLlvbRo0erjhw5IklOTpYmJCQoJk2aZFYqlQCAKVOmmC9evCiZOXOmIiMjQ/KPf/xDvnnzZtmMGTNqfSkFEREREXkWWdVNyrty5YrQrFmzWy5Ss9vt+OvNJ6ry8ccfywGgT58+5dYhfPLJJ8Xjx4+3HDp0SHrw4EEpADRv3lxTts25c+cKmjRpIspkMmzdutU0efJk5QMPPOCvVqvFMWPGWN56663ScNu0aVMxKSnJNHPmTOVHH33k16BBA3HlypXFgwYN4gwwERERkY9xOQC3bNnSvnv3bll0dHS5tPvVV1/J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v34d169fBwCEhobeUUAvq85mgB3MZjPUarX2yJEjhe3ateO6XyIiIqIqDB06FDExMRW+die3FL6TcPpXAwYMwIABVe+17Irg4GAEB1e937I7uPSrgp+fHxo1aiTW1MLmBQsW+HXs2NFfq9VqQ0NDNUOGDFGdPn26XE1FRUWYPHmyMjg4WKPRaLSPPPKIKisrq9xai4sXLwpxcXEqtVqtDQ0N1cyYMUPh+I3IITU1Vdq+fXt/hUKhbdq0qWbVqlXefRNrIiIi8gparRb33HNPhY+aDLHkPJfnyufMmVPy2muvKa5du3bHb757925ZfHy8ef/+/YXbt283WSwWDBgwQF1QUFDaZvr06crvvvtOtnHjxqK0tLTCrKwsYfjw4SrH61arFYMHD1abzWZhz549hatXry5au3atfO7cuaUX6507d04YOnSoumfPntYjR44UTp06tWTy5MnK5ORk3rmOiIiIyMe4vAb4vvvu8z9//rzEYrEgMjLSrlary71+7Nixau/YnJOTI+j1ek1aWpqpV69eNoPBgLCwMO3atWuLnnjiCSsAZGRkSFq3bu2/d+9eU2xsrG3r1q3SYcOGqS9fvlwQEREhAsCyZcvkc+fOVebm5uYrFAq89NJLiu+//16WkZFRWtvIkSNVBoNB+PHHHytcmV1cXIySkj83tMjLy0NkZCTXABMREdWiu3ENMNWsOl8DDABDhw61VN2qeoxGIwAgJCREBICDBw9KLRYL+vfvX7rmolWrVvZGjRqJ+/fvl8bGxtr2798va926td0RfgFg4MCB1qlTp+LkyZOSTp062X/++Wdpnz59yq3b6N+/v3XWrFmVfrISExMVb731ll+NnyQRERERuZXLAXjBggXm2ijEZrNh+vTpym7dutkcF9hlZ2cLfn5+CAoKKtc2LCxMzM7OFoCbs8ZhYWHl7kCn1+tFx/G3a5OXlweTyYS/zmIDwLx580pefvnl0ilgxwxwzZwtEREREbnLHd8JrqbEx8crMzIypHv27Kn9m147QalU8k8vRERERHchly+Cs1qteOedd/w6derkHx4ergkODtaWfVSniPj4eGVycrIsLS2tMDIysnSmVq/Xi2azGTdu3CjXPjc3V3DM8oaHh4u5ubnldoVwzPxW1SYgIKDC2V8iIiIib9GzZ08IggBBEHDs2DF3l3OLNWvWlNZXG3etqw6X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9e7du6g231+pVKLsWmMiIiIiujt47AxwaGioKJPJcO+999rKPt+yZUv777//LgA3Z3LNZjNu3LhR7tjc3FzBMcsbHh4u5ubmlpvpdcz8OtoQERERke/w2ACsUCjQsWNH25kzZ8rVePbsWUlkZKQIAJ07d7bJ5XL8+OOPpTPZp0+flvz+++9C9+7dbQDQvXt366lTpyRllzts375dFhAQgDZt2tjr6nyIiIiIyDO4dQlEfn4+ygbc8+fPSw4fPiwJCQkRo6KixFmzZpmfeuop1YMPPmjr06ePNTk5WZacnCxLTU01AYBOp8PYsWMts2bNUgYHBxcFBASI06ZNU8bExNhiY2NtABAXF2dr2bKlffTo0apFixYVZ2dnCwkJCYpJkyaZucSBiIiIyPe4dRu01NRUad++fdV/fX706NGWtWvXFgPAxx9/LF+0aJHfH3/8IWnWrJl9/vz5JcOHDy/d9aGoqAgzZsxQfvXVV/KSkhL07dvXumLFiuL69euXLm+4cOGCMHnyZOWePXtkarVaHDNmjGXRokUlcrnc6Vp5JzgiIiIiz8ZbIdcwBmAiIiIiz+b1+wATEREREdUGBmAiIiIi8ikMwERERETkUxiAiYiIiMinMAATERERkU9hACYiIiIin8IATEREREQ+hQGYiIiIiHwKAzARERER+RQGYCIiIiLyKQzARERERORTGICJiIiIyKcwABMRERGRT2EAJiIiIiKfwgBMRERERD6FAZiIiIiIfAoDMBERERH5FAZgIiIiIvIpDMBERERE5FMYgImIiIjIpzAAExEREZFPYQAmIiIiIp/CAExEREREPoUBmIiIiIh8CgMwEREREfkUBmAiIiIi8ikMwERERETkUxiAiYiIiMinuDUA79ixQzpo0CBVRESERhAE7ebNm2WVtZ0wYYJSEATt4sWL/co+f+3aNYwaNUoVEBCg1el02rFjxyrz8/PLHXvs2DFJbGysWqlUahs2bKhZuHBhuT6IiIiIyHe4NQAXFhaiXbt29qVLlxbfrt2mTZtkP//8szQiIkL862tPPvmkOiMjQ7Jt2zbTli1bTHv37pWOHz9e5XjdaDRiwIAB6sjISPvBgwcL33333eLExETF8uXL5bVxTkRERETk2Sqdca0LDz/8sO3hhx+23a7N77//LkyfPl25bds208MPP6wu+9qpU6ckP/74ozQ9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LytEdcxms2H69OnKbt262dq1a8c/wXqR48ePS2JjY/2Li4uh0WiwadOmIv4Z3XusW7dOdvToUemhQ4c8dgaRKte1a1frv/71L1vLli3tV65cEd58803FQw895P9///d/BQEBAe4uDwADMBFRpeLj45UZGRnSPXv28H/CXqZly5b2I0eOFBgMBuHf//63/LnnnlPu3LnTxBDs+S5duiTMmDFD+cMPP5hUKpW7y6FqePjhh0tn7du3b49u3bqZoqKitF9++aXcU5YhMQB7kNDQUFEqlSInJ6fcnwlyc3OFv84KE1Htio+PVyYnJ8t27dpVGBkZyc+fl1EoFGjevLkIQOzSpUvJoUOHpEuWLPFbtWoVL071cIcOHZJevXpV6NSpU+muATabDXv37pWuWLHCr7i4OF8mY3zxJkFBQbjnnnvsv/32m8f8NdtjCqGb/8G+//77bampqaWfbJvNhh07dsi6du3KNVBEdcButyM+Pl65ZcsWWWpqqqlp06YMv3cBURRRUsLrp7xBv379rMePHy88cuRI6aNDhw72UaNGWY4cOVLI8Ot98vPzceHCBUlERITH/PeUP0UeZsaMGebnn39e1alTJ1tMTIztgw8+8DOZTMK4ceM84k8GVLX8/HycOXOm9JfL8+fPSw4fPiwJCQkRo6KiPObDTxWLj49Xbty4Uf7111+btFqteOXKFQEAdDqdqFar3V0eOeHll19WDBo0yNq4cWN7fn6+8MUXX8h3794tTU5OZgL2AgEBAfjrmnt/f38xJCRE5Fp87zBjxgzFkCFDrFFRUfY//vhDkpCQoJBIJOLTTz/tMVmGAdjDPPXUU9bc3Nzi+fPnK3JycoR27drZvvvuO5Mn/dZEt3fgwAFp3759S5PSK6+8ogCgGD16tGXt2rX886uH+/jjj+UA0KdPn3Jp95NPPikeP368x/zHmyp39epVYezYsars7GwhICBAbNOmjT05OdkUFxfHv6QR1YHLly9Lnn76adX169eFevXqid27d7f99NNPhZ60nJP7ABMRERGRT+EaYCIiIiLyKQzARERERORTGICJiIiIyKcwABMRERGRT2EAJiIiIiKfwgBMRERERD6FAZiIiIiIfAoDMBERERH5FAZgIiIvMWbMGOWQIUNUdf2+q1atkguCoBUEQTt16lTF7do2btxYs3jxYr+yXzuOvXHjRu0XS0TkBN4KmYjIAwiCoL3d63PnzjUvXbq0WBTdcyfRgIAAnD59ukCj0bhUwIEDBwp3794tffzxx+s8uBMRVYYBmIjIA/zxxx8Fjn//8ssv5W+++abi9OnTpc9ptVpRq71tRq5VgiCgfv36Lqfv8PBwMTg42D2pnYioElwCQUTkAerXry86HoGBgaIjcDoeWq32liUQDz30kHrKlCnKqVOnKoKCgrRhYWGaFStWyAsKCvDMM88otVqttmnTppqtW7dKy77XiRMnJP3791drNBptWFiY5qmnnlJevXpVcLXm7OxsYdCgQSqVSqWNiorSfP7555xUISKvwABMROTFvvjiC3lISIiYnp5eOGXKFPPUqVOVI0aMUHXr1s126NChwr59+1qfffZZVWFhIQDgxo0b6NOnj7p9+/a2AwcOFCYnJ5tycnIkI0eOdHmJwrPPPqu8fPmyJCUlxfTVV1+ZVqxY4VedIE1EVNcYgImIvFjbtm1t8+fPN7do0cI+b948s1KpRL169cT4+HhLixYt7AkJCSXXr18Xjh07JgWADz/80O++++6zL1q0qKRVq1b2Tp062VevXl20a9cu6S+//OL0/xN++eUXyQ8//CD7+OOPi2JjY21dunSx/+tf/youKiqqvZMlIqoh/HMVEZEXa9u2rd3x7zKZDMHBwWKbNm1Kn9Pr9SIA5ObmCgBw4sQJ6e7du6UajeaWBcW//fab0LJlS6feNyMjQyKTydC5c+fS92rVqpVdp9NV/2SIiOoIAzARkReTy+XlLjATBAFyubz0a4nk5qSu3X4zpxYUFAiDBg2yLlq0qPivfVXnIjciIm/EAExE5EPuv/9+2zfffCOLjo4WywZlV9177712q9WKgwcPSrp27WoHgNOnT0sMBkNNlUpEVGu4BpiIyIdMnTrVfOPGDeGJJ55QpaenS86ePSskJydLn3nmGaXVanW6n3vvvdfer18/2+TJk1X79++XHjhwQDJ+/HilSsXtfonI8zEAExH5kIYNG4p79+412Ww2DBw40P++++7TzJgxQ6nT6UTHcglnrVmzpigiIsLeu3dv9WOPPaaeMGGCJTQ0lMsoiMjjCaIo5rm7CCIi8lyrVq2Sz5o1S2kwGPKrc3xqaqq0b9++6uvXr+cHBQXVdHlERC7jDDAREVXJaDRCo9FoX3rpJYUrx917773+Dz/8sLq26iIiqg7OABMR0W3l5eUhOztbAICgoCC4sszhwoULgsViAQA0bdpUlEqlVRxBRFT7GICJiIiIyKdwCQQRERER+RQGYCIiIiLyKQzARERERORTGICJiIiIyKcwABMRERGRT2EAJiIiIiKfwgBMRERERD6FAZiIiIiIfMr/B7EmmpyMM1FGAAAAAElFTkSuQmCC\n", "text/plain": [ "
" ] @@ -1806,7 +1803,11 @@ { "cell_type": "markdown", "id": "22788b98", - "metadata": {}, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, "source": [ "Many commonly used unit operations, such as [Pump](https://qsdsan.readthedocs.io/en/latest/api/sanunits/pumping.html#qsdsan.sanunits.Pump), [Mixer](https://qsdsan.readthedocs.io/en/latest/api/sanunits/abstract.html#mixer), [Splitter](https://qsdsan.readthedocs.io/en/latest/api/sanunits/abstract.html#splitter), and [HydraulicDelay](https://qsdsan.readthedocs.io/en/latest/api/sanunits/pumping.html#hydraulicdelay), have implemented the fundamental methods to be used in a dynamic system. You can always refer to the source codes of these units to learn more about how they work." ] @@ -1814,7 +1815,11 @@ { "cell_type": "markdown", "id": "1f5d8d3f", - "metadata": {}, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, "source": [ "[Back to top](#top)" ] @@ -1822,7 +1827,11 @@ { "cell_type": "markdown", "id": "a04a8ab5", - "metadata": {}, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, "source": [ "### 2.4. Making an inactive CompleteMixTank (`_compile_ODE`)" ] @@ -1830,7 +1839,11 @@ { "cell_type": "markdown", "id": "21dca6ff", - "metadata": {}, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, "source": [ "As you can see above, it's not very impressive to dynamically simulate a system without any ODEs. So let's make a simple inactive complete mix tank. Assume the reactor has a fixed liquid volume $V$, and thus the effluent volumetric flow rate changes instantly with influents. The mass balance of this type of reactor can be described as:\n", "$$Q = \\sum_{i \\in ins} Q_i \\tag{9}$$\n", @@ -1842,9 +1855,13 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": 31, "id": "c4706ed2", - "metadata": {}, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, "outputs": [], "source": [ "class CompleteMixTank(qs.SanUnit):\n", @@ -1908,7 +1925,11 @@ { "cell_type": "markdown", "id": "472f1577", - "metadata": {}, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, "source": [ ">**Note**: \n", ">1. All `SanUnit._ODE` must take exactly these four postional arguments: `t`, `y_ins`, and `dy_ins` are the same as the ones in `SanUnit._AE`. `y` is a **1d** `numpy.array`, because it is equal to the `_state` array of the unit.\n", @@ -1918,9 +1939,13 @@ }, { "cell_type": "code", - "execution_count": 33, + "execution_count": 32, "id": "493239c1", - "metadata": {}, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, "outputs": [], "source": [ "# Let's see if it works\n", @@ -1937,24 +1962,28 @@ }, { "cell_type": "code", - "execution_count": 34, + "execution_count": 33, "id": "c3df8f02", - "metadata": {}, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, "outputs": [ { "data": { "text/plain": [ "(
,\n", - " )" + " )" ] }, - "execution_count": 34, + "execution_count": 33, "metadata": {}, "output_type": "execute_result" }, { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ "
" ] @@ -1970,7 +1999,11 @@ { "cell_type": "markdown", "id": "3970aeaa", - "metadata": {}, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, "source": [ "Many commonly used unit operations described by ODEs have been implemented in QSDsan, such as [CSTR](https://qsdsan.readthedocs.io/en/latest/api/sanunits/suspended_growth_bioreactors.html#cstr), [BatchExperiment](https://qsdsan.readthedocs.io/en/latest/api/sanunits/suspended_growth_bioreactors.html#batchexperiment), and [FlatBottomCircularClarifier](https://qsdsan.readthedocs.io/en/latest/api/sanunits/clarifiers.html)." ] @@ -1978,7 +2011,11 @@ { "cell_type": "markdown", "id": "9d9a485a", - "metadata": {}, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, "source": [ "[Back to top](#top)" ] @@ -1986,7 +2023,11 @@ { "cell_type": "markdown", "id": "b085b491", - "metadata": {}, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, "source": [ "## 3. Other convenient features \n", "### 3.1. `ExogenousDynamicVariable`\n", @@ -1995,42 +2036,75 @@ }, { "cell_type": "code", - "execution_count": 35, + "execution_count": 34, "id": "6e8b6a32", - "metadata": {}, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, "outputs": [], "source": [ "# Check out the documentation\n", "from qsdsan.utils import ExogenousDynamicVariable as EDV\n", - "EDV?" + "# EDV?" ] }, { "cell_type": "markdown", "id": "d0365c64", - "metadata": {}, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, "source": [ "There are generally two ways to create an `ExogenousDynamicVariable`.\n", "\n", "1. __Define the variable as a function of time.__ Let's say we want to create a variable to represent the changing reaction temperature. Assume the temperature value \\[K\\] can be expressed as $T = 298.15 + 5\\cdot \\sin(t)$, indicating that the temperatue fluctuacts around $25^{\\circ}C$ by $\\pm 5^{\\circ}C$. Then simply,\n", "```python\n", "T = EDV('T', function=lambda t: 298.15+5*np.sin(t))\n", - "```\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "e2885b32", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ "2. __Provide time-series data to describe the dynamics of the variable.__ For demonstration purpose, we'll just make up the data. In practice, this is convenient if you have real data.\n", "```python\n", "t_arr = np.linspace(0, 5)\n", "y_arr = 298.15+5*np.sin(t_arr)\n", "T = EDV('T', t=t_arr, y=y_arr)\n", - "```\n", - "\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "a89fa738", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ "For convenience, `ExogenousDynamicVariable` also has a `classmethod` that enables batch creation of multiple variables at once. We just need to provide a file of the time-series data, including a column `t` for time points and additional columns of the variable values. See the [documentation](https://qsdsan.readthedocs.io/en/latest/api/utils/dynamics.html#qsdsan.utils.ExogenousDynamicVariable.batch_init) of `ExogenousDynamicVariable.batch_init` for detailed usage." ] }, { "cell_type": "code", - "execution_count": 39, + "execution_count": 35, "id": "b6401d1c", - "metadata": {}, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, "outputs": [], "source": [ "# EDV.batch_init?" @@ -2039,7 +2113,11 @@ { "cell_type": "markdown", "id": "8a5bd5b7", - "metadata": {}, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, "source": [ "Once created, these `ExogenousDynamicVariable` objects can be incorporated into any `SanUnit` upon its initialization or through the `SanUnit.exo_dynamic_vars` property setter. " ] @@ -2048,12 +2126,24 @@ "cell_type": "code", "execution_count": 36, "id": "2639a8b7", - "metadata": {}, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "All impact indicators have been removed from the registry.\n", + "All impact items have been removed from the registry.\n" + ] + }, { "data": { "text/plain": [ - "(, )" + "(,)" ] }, "execution_count": 36, @@ -2063,22 +2153,26 @@ ], "source": [ "# Let's see an example\n", - "from exposan.metab_mock import create_systems\n", - "sys_mt, = create_systems(which='A')\n", + "from exposan.metab import create_system\n", + "sys_mt = create_system()\n", "uf_mt = sys_mt.flowsheet.unit\n", - "uf_mt.R1A.exo_dynamic_vars" + "uf_mt.R1.exo_dynamic_vars" ] }, { "cell_type": "code", "execution_count": 37, "id": "a7e11837", - "metadata": {}, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, "outputs": [ { "data": { "text/plain": [ - "[308.15, 5.8]" + "[295.15]" ] }, "execution_count": 37, @@ -2089,13 +2183,17 @@ "source": [ "# The evaluation of these variables during unit simulation is done through \n", "# the `eval_exo_dynamic_vars` method\n", - "uf_mt.R1A.eval_exo_dynamic_vars(t=0.1)" + "uf_mt.R1.eval_exo_dynamic_vars(t=0.1)" ] }, { "cell_type": "markdown", "id": "0d1b7290", - "metadata": {}, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, "source": [ "[Back to top](#top)" ] @@ -2103,7 +2201,11 @@ { "cell_type": "markdown", "id": "d8205f55", - "metadata": {}, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, "source": [ "### 3.2. `DynamicInfluent`\n", "The [DynamicInfluent](https://qsdsan.readthedocs.io/en/latest/api/sanunits/DynamicInfluent.html) is a `SanUnit` subclass for generating dynamic influent streams from user-defined time-series data. The use of this class is, to some extent, similar to an `ExogenousDynamicVariable`." @@ -2113,7 +2215,11 @@ "cell_type": "code", "execution_count": 38, "id": "3ea577e7", - "metadata": {}, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, "outputs": [], "source": [ "from qsdsan.sanunits import DynamicInfluent as DI\n", @@ -2124,13 +2230,18 @@ "cell_type": "code", "execution_count": 39, "id": "7c2c9521", - "metadata": {}, + "metadata": { + "scrolled": false, + "slideshow": { + "slide_type": "slide" + } + }, "outputs": [ { "data": { "text/plain": [ "(
,\n", - " )" + " )" ] }, "execution_count": 39, @@ -2139,7 +2250,7 @@ }, { "data": { - "image/png": "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vaB169blktGePXsalUoltm7dak2wT58+LUtJSRH69OnT+JbX85QJERERUaUkrbjOmDFDvX79euWPP/5YoNVqzVevXhUAwMvLy+zm5gYvLy9MmTJFP3v2bLW3t3ehh4eH+dlnn1X36tXLWNFEgUaBp0yIiIiIKiTpOCxBECpc8b948WLdtGnT9IC4AcHMmTPV3377rbKoqAiDBw82LFq0SBcUFGRTq0B2dja8vLwcfxwWERERURNl6zgsh5rjWh+YuBIRERE5Nqec40pEREREVBkmrkRERETkFJi4EhEREZFTYOJKRERERE6BiSsREREROQUmrkRERETkFJi4EhEREZFTkHTnrIZgNov7FOTkNOpxtUREREROy5KnWfK2yjT6xDU3NxcAEBwcLHEkRERERFSV3NxceHl5Vfr9Rr9zlsFgQGpqKjQaDWSy+u+MyMnJQUhIiDY5OTmXO3U5J/4MnR9/hs6NPz/nx5+h82von6HJZEJeXh4CAwOhUFReV230FVeFQiFJtdXDwwNVbVlGjo8/Q+fHn6Fz48/P+fFn6Pwa8mfYrFmzao/h4iwiIiIicgpMXImIiIjIKTBxrWMuLi547bXXil1cXKQOhWqIP0Pnx5+hc+PPz/nxZ+j8HPVn2OgXZxERERFR48CKKxERERE5BSauREREROQUmLgSERERkVNg4kpEREREToGJax1buHChMjQ0VKNWq7U9e/Z0i4uL4/+xE3j77bdV3bt3d9dqtVpfX1/NAw884Hr69Gn+7JzYO++8oxIEQfvMM8841pJYqlJKSorw6KOPqr29vTWurq7ajh07uh84cIB/i07CYDDg1VdfdQkLC9O4urpqW7VqpXnjjTdUJpNJ6tCoAjt37pTfe++9roGBgRpBELQ//PBDuY2pTCYT5syZ4xIQEKBxdXXVDhw40O3s2bOS/j3yyaAOff3114qXXnpJPXfu3KLDhw/nd+7c2XTvvfe6p6WlCVLHRlXbvXu3YsaMGcWxsbH5v//+e4Fer8fQoUPd8vLypA6NamD//v2yJUuWqKKiovhq6UQyMzPRr18/d6VSid9++63g5MmTeQsWLNB5e3ubpY6NbDNv3jzVV199pfzkk090p06dynvvvfd0H374ocvHH3+skjo2ul1+fj46d+5s+vTTT3UVff+9995Tff7556rPP/9cFxcXl+/u7m4eNmyY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\n", + "image/png": "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vaB169blktGePXsalUoltm7dak2wT58+LUtJSRH69OnT+JbX85QJERERUaUkrbjOmDFDvX79euWPP/5YoNVqzVevXhUAwMvLy+zm5gYvLy9MmTJFP3v2bLW3t3ehh4eH+dlnn1X36tXLWNFEgUaBp0yIiIiIKiTpOCxBECpc8b948WLdtGnT9IC4AcHMmTPV3377rbKoqAiDBw82LFq0SBcUFGRTq0B2dja8vLwcfxwWERERURNl6zgsh5rjWh+YuBIRERE5Nqec40pEREREVBkmrkRERETkFJi4EhEREZFTYOJKRERERE6BiSsREREROQUmrkRERETkFJi4EhEREZFTkHTnrIZgNov7FOTkNOpxtUREREROy5KnWfK2yjT6xDU3NxcAEBwcLHEkRERERFSV3NxceHl5Vfr9Rr9zlsFgQGpqKjQaDWSy+u+MyMnJQUhIiDY5OTmXO3U5J/4MnR9/hs6NPz/nx5+h82von6HJZEJeXh4CAwOhUFReV230FVeFQiFJtdXDwwNVbVlGjo8/Q+fHn6Fz48/P+fFn6Pwa8mfYrFmzao/h4iwiIiIicgpMXImIiIjIKTBxrWMuLi547bXXil1cXKQOhWqIP0Pnx5+hc+PPz/nxZ+j8HPVn2OgXZxERERFR48CKKxERERE5BSauREREROQUmLgSERERkVNg4kpEREREToGJax1buHChMjQ0VKNWq7U9e/Z0i4uL4/+xE3j77bdV3bt3d9dqtVpfX1/NAw884Hr69Gn+7JzYO++8oxIEQfvMM8841pJYqlJKSorw6KOPqr29vTWurq7ajh07uh84cIB/i07CYDDg1VdfdQkLC9O4urpqW7VqpXnjjTdUJpNJ6tCoAjt37pTfe++9roGBgRpBELQ//PBDuY2pTCYT5syZ4xIQEKBxdXXVDhw40O3s2bOS/j3yyaAOff3114qXXnpJPXfu3KLDhw/nd+7c2XTvvfe6p6WlCVLHRlXbvXu3YsaMGcWxsbH5v//+e4Fer8fQoUPd8vLypA6NamD//v2yJUuWqKKiovhq6UQyMzPRr18/d6VSid9++63g5MmTeQsWLNB5e3ubpY6NbDNv3jzVV199pfzkk090p06dynvvvfd0H374ocvHH3+skjo2ul1+fj46d+5s+vTTT3UVff+9995Tff7556rPP/9cFxcXl+/u7m4eNmyY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\n", "text/plain": [ "
" ] @@ -2160,17 +2271,22 @@ { "cell_type": "markdown", "id": "786d6034", - "metadata": {}, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, "source": [ "[Back to top](#top)" ] } ], "metadata": { + "celltoolbar": "Slideshow", "kernelspec": { - "display_name": "Python [conda env:tut]", + "display_name": "Python 3 (ipykernel)", "language": "python", - "name": "conda-env-tut-py" + "name": "python3" }, "language_info": { "codemirror_mode": { @@ -2182,7 +2298,36 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.0" + "version": "3.9.13" + }, + "varInspector": { + "cols": { + "lenName": 16, + "lenType": 16, + "lenVar": 40 + }, + "kernels_config": { + "python": { + "delete_cmd_postfix": "", + "delete_cmd_prefix": "del ", + "library": "var_list.py", + "varRefreshCmd": "print(var_dic_list())" + }, + "r": { + "delete_cmd_postfix": ") ", + "delete_cmd_prefix": "rm(", + "library": "var_list.r", + "varRefreshCmd": "cat(var_dic_list()) " + } + }, + "types_to_exclude": [ + "module", + "function", + "builtin_function_or_method", + "instance", + "_Feature" + ], + "window_display": false } }, "nbformat": 4, diff --git a/docs/source/tutorials/12_Anaerobic_Digestion_Model_No_1.ipynb b/docs/source/tutorials/12_Anaerobic_Digestion_Model_No_1.ipynb new file mode 100644 index 00000000..b986a473 --- /dev/null +++ b/docs/source/tutorials/12_Anaerobic_Digestion_Model_No_1.ipynb @@ -0,0 +1,1538 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "8d891055", + "metadata": {}, + "source": [ + "# Anaerobic Digestion Model No. 1 (ADM1) \n", + "\n", + "- **Prepared by:**\n", + " \n", + " - [Ga-Yeong Kim](https://qsdsan.readthedocs.io/en/latest/CONTRIBUTING.html)\n", + " \n", + "- **Covered topics:**\n", + "\n", + " - [1. Introduction](#s1)\n", + " - [2. System Setup](#s2)\n", + " - [3. System Simulation](#s3)\n", + " \n", + "To run tutorials in your browser, go to this [Binder page](https://mybinder.org/v2/gh/QSD-Group/QSDsan-env/main?urlpath=git-pull%3Frepo%3Dhttps%253A%252F%252Fgithub.com%252FQSD-group%252FQSDsan%26urlpath%3Dtree%252FQSDsan%252Fdocs%252Fsource%252Ftutorials%26branch%3Dmain)." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "9a2a96b7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "This tutorial was made with qsdsan v1.3.1 and exposan v1.3.1\n" + ] + } + ], + "source": [ + "import qsdsan as qs, exposan\n", + "print(f'This tutorial was made with qsdsan v{qs.__version__} and exposan v{exposan.__version__}')" + ] + }, + { + "cell_type": "markdown", + "id": "1bbdffaa", + "metadata": {}, + "source": [ + "## 1. Introduction " + ] + }, + { + "cell_type": "markdown", + "id": "cefa6e0a", + "metadata": {}, + "source": [ + "Anaerobic Digestion Model No.1 (ADM1) includes multiple steps describing **biochemical** as well as **physicochemical processes**. \n", + "\n", + "The **biochemical steps** include disintegration from homogeneous particulates to carbohydrates, proteins and lipids; extracellular hydrolysis of these particulate substrates to sugars, amino acids, and long chain fatty acids (LCFA), respectively; acidogenesis from sugars and amino acids to volatile fatty acids (VFAs) and hydrogen; acetogenesis of LCFA and VFAs to acetate; and separate methanogenesis steps from acetate and hydrogen/CO2. \n", + "\n", + "The **physico-chemical equations** describe ion association and dissociation, and gas-liquid transfer. \n", + "\n", + "Implemented as a differential and algebraic equation (DAE) set, there are 26 dynamic state concentration variables, and 8 implicit algebraic variables per reactor vessel or element. Implemented as differential equations (DE) only, there are 32 dynamic concentration state variables.\n", + "\n", + "*Water Science and Technology, Vol 45, No 10, pp 65–73*" + ] + }, + { + "attachments": { + "ADM1.JPG": { + "image/jpeg": "/9j/4AAQSkZJRgABAQEAYABgAAD/4RDyRXhpZgAATU0AKgAAAAgABAE7AAIAAAANAAAISodpAAQAAAABAAAIWJydAAEAAAAaAAAQ0OocAAcAAAgMAAAAPgAAAAAc6gAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEdhLVllb25nIEtpbQAAAAWQAwACAAAAFAAAEKaQBAACAAAAFAAAELqSkQACAAAAAzc4AACSkgACAAAAAzc4AADqHAAHAAAIDAAACJoAAAAAHOoAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAyMDIzOjEwOjE1IDIxOjM4OjU0ADIwMjM6MTA6MTUgMjE6Mzg6NTQAAABHAGEALQBZAGUAbwBuAGcAIABLAGkAbQAAAP/hCx9odHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvADw/eHBhY2tldCBiZWdpbj0n77u/JyBpZD0nVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkJz8+DQo8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIj48cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPjxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSJ1dWlkOmZhZjViZGQ1LWJhM2QtMTFkYS1hZDMxLWQzM2Q3NTE4MmYxYiIgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIi8+PHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9InV1aWQ6ZmFmNWJkZDUtYmEzZC0xMWRhLWFkMzEtZDMzZDc1MTgyZjFiIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iPjx4bXA6Q3JlYXRlRGF0ZT4yMDIzLTEwLTE1VDIxOjM4OjU0Ljc4MjwveG1wOkNyZWF0ZURhdGU+PC9yZGY6RGVzY3JpcHRpb24+PHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9InV1aWQ6ZmFmNWJkZDUtYmEzZC0xMWRhLWFkMzEtZDMzZDc1MTgyZjFiIiB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iPjxkYzpjcmVhdG9yPjxyZGY6U2VxIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+PHJkZjpsaT5HYS1ZZW9uZyBLaW08L3JkZjpsaT48L3JkZjpTZXE+DQoJCQk8L2RjOmNyZWF0b3I+PC9yZGY6RGVzY3JpcHRpb24+PC9yZGY6UkRGPjwveDp4bXBtZXRhPg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8P3hwYWNrZXQg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" + } + }, + "cell_type": "markdown", + "id": "180af880", + "metadata": {}, + "source": [ + "![assets/ADM1.JPG](attachment:ADM1.JPG)" + ] + }, + { + "cell_type": "markdown", + "id": "deab6410", + "metadata": {}, + "source": [ + "**Note:** You can find validation of the ADM1 system in [EXPOsan](https://github.com/QSD-Group/EXPOsan/tree/main/exposan/adm)." + ] + }, + { + "cell_type": "markdown", + "id": "47af6e27", + "metadata": {}, + "source": [ + "## 2. System Setup " + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "fb4e6486", + "metadata": {}, + "outputs": [], + "source": [ + "# Import packages\n", + "import numpy as np\n", + "from chemicals.elements import molecular_weight as get_mw\n", + "from qsdsan import sanunits as su, processes as pc, WasteStream, System\n", + "from qsdsan.utils import time_printer\n", + "\n", + "import warnings\n", + "warnings.simplefilter(action='ignore', category=FutureWarning) # to ignore Pandas future warning" + ] + }, + { + "cell_type": "markdown", + "id": "8c7244dc", + "metadata": {}, + "source": [ + "### 2.1. State variables of ADM1" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "5774fdae", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CompiledComponents([S_su, S_aa, S_fa, S_va, S_bu, S_pro, S_ac, S_h2, S_ch4, S_IC, S_IN, S_I, X_c, X_ch, X_pr, X_li, X_su, X_aa, X_fa, X_c4, X_pro, X_ac, X_h2, X_I, S_cat, S_an, H2O])\n" + ] + } + ], + "source": [ + "# Components \n", + "cmps = pc.create_adm1_cmps() # create state variables for ADM1\n", + "cmps.show() # 26 components in ADM1 + water" + ] + }, + { + "cell_type": "markdown", + "id": "4ee7c0b5", + "metadata": {}, + "source": [ + "**S_su**: Monosaccharides, **S_aa**: Amino acids, **S_fa**: Total long-chain fatty acids, **S_va**: Total valerate, **S_bu**: Total butyrate, **S_pro**: Total propionate, **S_ac**: Total acetate, **S_h2**: Hydrogen gas, **S_ch4**: Methane gas, **S_IC**: Inorganic carbon, **S_IN**: Inorganic nitrogen, **S_I**: Soluble inerts, **X_c**: Composites, **X_ch**: Carobohydrates, **X_pr**: Proteins, **X_li**: Lipids, **X_su**: Biomass uptaking sugars, **X_aa**: Biomass uptaking amino acids, **X_fa**: Biomass uptaking long chain fatty acids, **X_c4**: Biomass uptaking c4 fatty acids (valerate and butyrate), **X_pro**: Biomass uptaking propionate, **X_ac**: Biomass uptaking acetate, **X_h2**: Biomass uptaking hydrogen, **X_I**: Particulate inerts, **S_cat**: Other cations, **S_an**: Other anions" + ] + }, + { + "cell_type": "markdown", + "id": "c4f28ea2", + "metadata": {}, + "source": [ + "### 2.2. The ADM1 `Process`" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "0dd6a5b8", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ADM1([disintegration, hydrolysis_carbs, hydrolysis_proteins, hydrolysis_lipids, uptake_sugars, uptake_amino_acids, uptake_LCFA, uptake_valerate, uptake_butyrate, uptake_propionate, uptake_acetate, uptake_h2, decay_Xsu, decay_Xaa, decay_Xfa, decay_Xc4, decay_Xpro, decay_Xac, decay_Xh2, h2_transfer, ch4_transfer, IC_transfer])\n" + ] + } + ], + "source": [ + "# Processes\n", + "adm1 = pc.ADM1() # create ADM1 processes\n", + "adm1.show() # 22 processes in ADM1" + ] + }, + { + "cell_type": "markdown", + "id": "0b3d103f", + "metadata": {}, + "source": [ + "### 2.3. Petersen matrix of ADM1" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "9a9db08e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
S_suS_aaS_faS_vaS_bu...X_h2X_IS_catS_anH2O
disintegration00000...00.2000
hydrolysis_carbs10000...00000
hydrolysis_proteins01000...00000
hydrolysis_lipids0.0500.9500...00000
uptake_sugars-10000.117...00000
uptake_amino_acids0-100.2120.239...00000
uptake_LCFA00-100...00000
uptake_valerate000-10...00000
uptake_butyrate0000-1...00000
uptake_propionate00000...00000
uptake_acetate00000...00000
uptake_h200000...0.060000
decay_Xsu00000...00000
decay_Xaa00000...00000
decay_Xfa00000...00000
decay_Xc400000...00000
decay_Xpro00000...00000
decay_Xac00000...00000
decay_Xh200000...-10000
h2_transfer00000...00000
ch4_transfer00000...00000
IC_transfer00000...00000
\n", + "

22 rows × 27 columns

\n", + "
" + ], + "text/plain": [ + " S_su S_aa S_fa S_va S_bu ... X_h2 X_I S_cat S_an H2O\n", + "disintegration 0 0 0 0 0 ... 0 0.2 0 0 0\n", + "hydrolysis_carbs 1 0 0 0 0 ... 0 0 0 0 0\n", + "hydrolysis_proteins 0 1 0 0 0 ... 0 0 0 0 0\n", + "hydrolysis_lipids 0.05 0 0.95 0 0 ... 0 0 0 0 0\n", + "uptake_sugars -1 0 0 0 0.117 ... 0 0 0 0 0\n", + "uptake_amino_acids 0 -1 0 0.212 0.239 ... 0 0 0 0 0\n", + "uptake_LCFA 0 0 -1 0 0 ... 0 0 0 0 0\n", + "uptake_valerate 0 0 0 -1 0 ... 0 0 0 0 0\n", + "uptake_butyrate 0 0 0 0 -1 ... 0 0 0 0 0\n", + "uptake_propionate 0 0 0 0 0 ... 0 0 0 0 0\n", + "uptake_acetate 0 0 0 0 0 ... 0 0 0 0 0\n", + "uptake_h2 0 0 0 0 0 ... 0.06 0 0 0 0\n", + "decay_Xsu 0 0 0 0 0 ... 0 0 0 0 0\n", + "decay_Xaa 0 0 0 0 0 ... 0 0 0 0 0\n", + "decay_Xfa 0 0 0 0 0 ... 0 0 0 0 0\n", + "decay_Xc4 0 0 0 0 0 ... 0 0 0 0 0\n", + "decay_Xpro 0 0 0 0 0 ... 0 0 0 0 0\n", + "decay_Xac 0 0 0 0 0 ... 0 0 0 0 0\n", + "decay_Xh2 0 0 0 0 0 ... -1 0 0 0 0\n", + "h2_transfer 0 0 0 0 0 ... 0 0 0 0 0\n", + "ch4_transfer 0 0 0 0 0 ... 0 0 0 0 0\n", + "IC_transfer 0 0 0 0 0 ... 0 0 0 0 0\n", + "\n", + "[22 rows x 27 columns]" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Petersen stoichiometric matrix\n", + "adm1.stoichiometry" + ] + }, + { + "cell_type": "markdown", + "id": "4d14e88c", + "metadata": {}, + "source": [ + "**The rate of production or consumption for a state variable**
\n", + "\n", + "$a_{ij}$: the stoichiometric coefficient of component $j$ in process $i$ (i.e., value on the $i$th row and $j$th column of the stoichiometry matrix)
\n", + "$\\rho_i$: process $i$'s reaction rate
\n", + "$r_j$: the overall production or consumption rate of component $j$
\n", + "$$r_j = \\sum_i{a_{ij}\\cdot\\rho_i}$$\n", + "In matrix notation, this calculation can be neatly described as\n", + "$$\\mathbf{r} = \\mathbf{A^T} \\mathbf{\\rho}$$\n", + "where $\\mathbf{A}$ is the stoichiometry matrix and $\\mathbf{\\rho}$ is the array of process rates." + ] + }, + { + "cell_type": "markdown", + "id": "e2c2360d", + "metadata": {}, + "source": [ + "### 2.4. Influent & effluent" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "a28bc7d2", + "metadata": {}, + "outputs": [], + "source": [ + "# Flow rate, temperature, HRT\n", + "Q = 170 # influent flowrate [m3/d]\n", + "Temp = 273.15+35 # temperature [K]\n", + "HRT = 5 # HRT [d]" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "28a9c8e5", + "metadata": {}, + "outputs": [], + "source": [ + "# WasteStream\n", + "inf = WasteStream('Influent', T=Temp) # influent\n", + "eff = WasteStream('Effluent', T=Temp) # effluent\n", + "gas = WasteStream('Biogas') # gas" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "bdd90569", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "WasteStream: Influent\n", + "phase: 'l', T: 308.15 K, P: 101325 Pa\n", + "flow (g/hr): S_su 70.8\n", + " S_aa 7.08\n", + " S_fa 7.08\n", + " S_va 7.08\n", + " S_bu 7.08\n", + " S_pro 7.08\n", + " S_ac 7.08\n", + " S_h2 7.08e-05\n", + " S_ch4 0.0708\n", + " S_IC 3.4e+03\n", + " S_IN 992\n", + " S_I 142\n", + " X_c 1.42e+04\n", + " X_ch 3.54e+04\n", + " X_pr 1.42e+05\n", + " ... 6.97e+06\n", + " WasteStream-specific properties:\n", + " pH : 7.0\n", + " Alkalinity : 2.5 mg/L\n", + " COD : 57096.0 mg/L\n", + " BOD : 12769.4 mg/L\n", + " TC : 20596.5 mg/L\n", + " TOC : 20116.0 mg/L\n", + " TN : 3683.2 mg/L\n", + " TP : 489.3 mg/L\n", + " TK : 9.8 mg/L\n", + " Component concentrations (mg/L):\n", + " S_su 10.0\n", + " S_aa 1.0\n", + " S_fa 1.0\n", + " S_va 1.0\n", + " S_bu 1.0\n", + " S_pro 1.0\n", + " S_ac 1.0\n", + " S_h2 0.0\n", + " S_ch4 0.0\n", + " S_IC 480.4\n", + " S_IN 140.1\n", + " S_I 20.0\n", + " X_c 2000.0\n", + " X_ch 5000.0\n", + " X_pr 20000.0\n", + " ...\n" + ] + } + ], + "source": [ + "# Set influent concentration\n", + "C_mw = get_mw({'C':1}) # molecular weight of carbon\n", + "N_mw = get_mw({'N':1}) # molecular weight of nitrogen\n", + "\n", + "default_inf_kwargs = {\n", + " 'concentrations': {\n", + " 'S_su':0.01,\n", + " 'S_aa':1e-3,\n", + " 'S_fa':1e-3,\n", + " 'S_va':1e-3,\n", + " 'S_bu':1e-3,\n", + " 'S_pro':1e-3,\n", + " 'S_ac':1e-3,\n", + " 'S_h2':1e-8,\n", + " 'S_ch4':1e-5,\n", + " 'S_IC':0.04*C_mw,\n", + " 'S_IN':0.01*N_mw,\n", + " 'S_I':0.02,\n", + " 'X_c':2.0,\n", + " 'X_ch':5.0,\n", + " 'X_pr':20.0,\n", + " 'X_li':5.0,\n", + " 'X_aa':1e-2,\n", + " 'X_fa':1e-2,\n", + " 'X_c4':1e-2,\n", + " 'X_pro':1e-2,\n", + " 'X_ac':1e-2,\n", + " 'X_h2':1e-2,\n", + " 'X_I':25,\n", + " 'S_cat':0.04,\n", + " 'S_an':0.02,\n", + " },\n", + " 'units': ('m3/d', 'kg/m3'),\n", + " } # concentration of each state variable in influent\n", + "\n", + "inf.set_flow_by_concentration(Q, **default_inf_kwargs) # set influent concentration\n", + "inf" + ] + }, + { + "cell_type": "markdown", + "id": "4bf9c287", + "metadata": {}, + "source": [ + "### 2.5. Reactor" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "1fc90df0", + "metadata": {}, + "outputs": [], + "source": [ + "# SanUnit\n", + "AD = su.AnaerobicCSTR('AD', ins=inf, outs=(gas, eff), model=adm1, V_liq=Q*HRT, V_gas=Q*HRT*0.1, T=Temp)" + ] + }, + { + "cell_type": "markdown", + "id": "0716d4c9", + "metadata": {}, + "source": [ + "**su.AnaerobicCSTR**(\n", + " ID='',\n", + " ins=None,\n", + " outs=(),\n", + " thermo=None,\n", + " init_with='WasteStream',\n", + " V_liq=3400,\n", + " V_gas=300,\n", + " model=None,\n", + " T=308.15,\n", + " headspace_P=1.013,\n", + " external_P=1.013,\n", + " pipe_resistance=50000.0,\n", + " fixed_headspace_P=False,\n", + " retain_cmps=(),\n", + " fraction_retain=0.95,\n", + " isdynamic=True,\n", + " exogenous_vars=(),\n", + " **kwargs,\n", + ")\n", + "\n", + "**Parameters**
\n", + "*ins* : :class:`WasteStream`,\n", + " Influent to the reactor.
\n", + "*outs* : Iterable,\n", + " Biogas and treated effluent(s).
\n", + "*V_liq* : float, optional,\n", + " Liquid-phase volume [m^3]. The default is 3400.
\n", + "*V_gas* : float, optional,\n", + " Headspace volume [m^3]. The default is 300.
\n", + "*model* : :class:`Processes`, optional,\n", + " The kinetic model, typically ADM1-like. The default is None.
\n", + "*T* : float, optional,\n", + " Operation temperature [K]. The default is 308.15.
\n", + "*headspace_P* : float, optional,\n", + " Headspace pressure, if fixed [bar]. The default is 1.013.
\n", + "*external_P* : float, optional,\n", + " External pressure, typically atmospheric pressure [bar]. The default is 1.013.
\n", + "*pipe_resistance* : float, optional,\n", + " Biogas extraction pipe resistance [m3/d/bar]. The default is 5.0e4.
\n", + "*fixed_headspace_P* : bool, optional,\n", + " Whether to assume fixed headspace pressure. The default is False.
\n", + "*retain_cmps* : Iterable[str], optional,\n", + " IDs of the components that are assumed to be retained in the reactor, ideally.\n", + " The default is ().
\n", + "*fraction_retain* : float, optional,\n", + " The assumed fraction of ideal retention of select components. The default is 0.95.
" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "4d403072", + "metadata": { + "scrolled": false + }, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + "AD\n", + "Anaerobic CSTR:c->179376415616:w\n", + "\n", + "\n", + " Biogas\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "AD\n", + "Anaerobic CSTR:c->179376414536:w\n", + "\n", + "\n", + " Effluent\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "179376414696:e->AD\n", + "Anaerobic CSTR:c\n", + "\n", + "\n", + " Influent\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "AD\n", + "Anaerobic CSTR\n", + "\n", + "\n", + "AD\n", + "Anaerobic CSTR\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "179376414696\n", + "\n", + "\n", + "\n", + "\n", + "179376415616\n", + "\n", + "\n", + "\n", + "\n", + "179376414536\n", + "\n", + "\n", + "\n", + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "AnaerobicCSTR: AD\n", + "ins...\n", + "[0] Influent\n", + "phase: 'l', T: 308.15 K, P: 101325 Pa\n", + "flow (g/hr): S_su 70.8\n", + " S_aa 7.08\n", + " S_fa 7.08\n", + " S_va 7.08\n", + " S_bu 7.08\n", + " S_pro 7.08\n", + " S_ac 7.08\n", + " S_h2 7.08e-05\n", + " S_ch4 0.0708\n", + " S_IC 3.4e+03\n", + " S_IN 992\n", + " S_I 142\n", + " X_c 1.42e+04\n", + " X_ch 3.54e+04\n", + " X_pr 1.42e+05\n", + " ... 6.97e+06\n", + " WasteStream-specific properties:\n", + " pH : 7.0\n", + " COD : 57096.0 mg/L\n", + " BOD : 12769.4 mg/L\n", + " TC : 20596.5 mg/L\n", + " TOC : 20116.0 mg/L\n", + " TN : 3683.2 mg/L\n", + " TP : 489.3 mg/L\n", + " TK : 9.8 mg/L\n", + "outs...\n", + "[0] Biogas\n", + "phase: 'l', T: 298.15 K, P: 101325 Pa\n", + "flow: 0\n", + " WasteStream-specific properties: None for empty waste streams\n", + "[1] Effluent\n", + "phase: 'l', T: 308.15 K, P: 101325 Pa\n", + "flow: 0\n", + " WasteStream-specific properties: None for empty waste streams\n" + ] + } + ], + "source": [ + "AD # anaerobic CSTR with influent, effluent, and biogas\n", + " # before running the simulation, 'outs' have nothing" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "b162ac79", + "metadata": {}, + "outputs": [], + "source": [ + "# Set initial condition of the reactor\n", + "default_init_conds = {\n", + " 'S_su': 0.0124*1e3,\n", + " 'S_aa': 0.0055*1e3,\n", + " 'S_fa': 0.1074*1e3,\n", + " 'S_va': 0.0123*1e3,\n", + " 'S_bu': 0.0140*1e3,\n", + " 'S_pro': 0.0176*1e3,\n", + " 'S_ac': 0.0893*1e3,\n", + " 'S_h2': 2.5055e-7*1e3,\n", + " 'S_ch4': 0.0555*1e3,\n", + " 'S_IC': 0.0951*C_mw*1e3,\n", + " 'S_IN': 0.0945*N_mw*1e3,\n", + " 'S_I': 0.1309*1e3,\n", + " 'X_ch': 0.0205*1e3,\n", + " 'X_pr': 0.0842*1e3,\n", + " 'X_li': 0.0436*1e3,\n", + " 'X_su': 0.3122*1e3,\n", + " 'X_aa': 0.9317*1e3,\n", + " 'X_fa': 0.3384*1e3,\n", + " 'X_c4': 0.3258*1e3,\n", + " 'X_pro': 0.1011*1e3,\n", + " 'X_ac': 0.6772*1e3,\n", + " 'X_h2': 0.2848*1e3,\n", + " 'X_I': 17.2162*1e3\n", + " } # concentration of each state variable in reactor\n", + "\n", + "AD.set_init_conc(**default_init_conds) # set initial condition of AD" + ] + }, + { + "cell_type": "markdown", + "id": "051f6b47", + "metadata": {}, + "source": [ + "### 2.6. System set-up" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "85b13876", + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + "Influent:c->Anaerobic_Digestion\n", + "System:c\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Anaerobic_Digestion\n", + "System:c->Biogas\n", + "Effluent:c\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Influent\n", + "\n", + "\n", + "Influent\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Anaerobic_Digestion\n", + "System\n", + "\n", + "\n", + "Anaerobic_Digestion\n", + "System\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Biogas\n", + "Effluent\n", + "\n", + "\n", + "Biogas\n", + "Effluent\n", + "\n", + "\n", + "\n", + "\n", + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "System: Anaerobic_Digestion\n", + "ins...\n", + "[0] Influent \n", + " phase: 'l', T: 308.15 K, P: 101325 Pa\n", + " flow (kmol/hr): S_su 0.000393\n", + " S_aa 0.00708\n", + " S_fa 2.76e-05\n", + " S_va 6.94e-05\n", + " S_bu 8.13e-05\n", + " S_pro 9.69e-05\n", + " S_ac 0.00012\n", + " ... 709\n", + "outs...\n", + "[0] Biogas \n", + " phase: 'l', T: 298.15 K, P: 101325 Pa\n", + " flow: 0\n", + "[1] Effluent \n", + " phase: 'l', T: 308.15 K, P: 101325 Pa\n", + " flow: 0\n" + ] + } + ], + "source": [ + "# System\n", + "sys = System('Anaerobic_Digestion', path=(AD,)) # aggregation of sanunits\n", + "sys.set_dynamic_tracker(eff, gas) # what you want to track changes in concentration\n", + "sys # before running the simulation, 'outs' have nothing" + ] + }, + { + "cell_type": "markdown", + "id": "cd84e41c", + "metadata": {}, + "source": [ + "[Back to top](#top)" + ] + }, + { + "cell_type": "markdown", + "id": "bd50264c", + "metadata": {}, + "source": [ + "## 3. System Simulation " + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "132152fe", + "metadata": {}, + "outputs": [], + "source": [ + "# Simulation settings\n", + "t = 10 # total time for simulation\n", + "t_step = 0.1 # times at which to store the computed solution \n", + "\n", + "method = 'BDF' # integration method to use\n", + "# method = 'RK45'\n", + "# method = 'RK23'\n", + "# method = 'DOP853'\n", + "# method = 'Radau'\n", + "# method = 'LSODA'\n", + "\n", + "# https://docs.scipy.org/doc/scipy/reference/generated/scipy.integrate.solve_ivp.html" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "74bcbaf0", + "metadata": {}, + "outputs": [], + "source": [ + "# Run simulation\n", + "sys.simulate(state_reset_hook='reset_cache',\n", + " t_span=(0,t),\n", + " t_eval=np.arange(0, t+t_step, t_step),\n", + " method=method,\n", + " # export_state_to=f'sol_{t}d_{method}_AD.xlsx', # uncomment to export simulation result as excel file\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "55247c4c", + "metadata": { + "scrolled": false + }, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + "Influent:c->Anaerobic_Digestion\n", + "System:c\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Anaerobic_Digestion\n", + "System:c->Effluent\n", + "Biogas:c\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Influent\n", + "\n", + "\n", + "Influent\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Anaerobic_Digestion\n", + "System\n", + "\n", + "\n", + "Anaerobic_Digestion\n", + "System\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Effluent\n", + "Biogas\n", + "\n", + "\n", + "Effluent\n", + "Biogas\n", + "\n", + "\n", + "\n", + "\n", + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "System: Anaerobic_Digestion\n", + "ins...\n", + "[0] Influent \n", + " phase: 'l', T: 308.15 K, P: 101325 Pa\n", + " flow (kmol/hr): S_su 0.000393\n", + " S_aa 0.00708\n", + " S_fa 2.76e-05\n", + " S_va 6.94e-05\n", + " S_bu 8.13e-05\n", + " S_pro 9.69e-05\n", + " S_ac 0.00012\n", + " ... 709\n", + "outs...\n", + "[0] Biogas \n", + " phase: 'g', T: 308.15 K, P: 101325 Pa\n", + " flow (kmol/hr): S_h2 0.00119\n", + " S_ch4 8.5\n", + " S_IC 0.414\n", + " H2O 0.205\n", + "[1] Effluent \n", + " phase: 'l', T: 308.15 K, P: 101325 Pa\n", + " flow (kmol/hr): S_su 0.00164\n", + " S_aa 0.129\n", + " S_fa 0.0222\n", + " S_va 0.00332\n", + " S_bu 0.00447\n", + " S_pro 0.0106\n", + " S_ac 0.639\n", + " ... 587\n" + ] + } + ], + "source": [ + "sys # now you have 'outs' info." + ] + }, + { + "cell_type": "markdown", + "id": "7b57f738", + "metadata": {}, + "source": [ + "### 3.1. Check simulation results: Effluent" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "990d5e59", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(
,\n", + " )" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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v7JMiUZE5Cmys7Ph7++PzMzMHH9//0rbsgeYiKgGuX76FPb8/jMuHSmaqUYQ0LRzN8QOHoqQBo0qnAWiWI+RzzP8ElG1xwBMVi1fvhwvvGB9erbIyEicPHmyiisiojslSRKunjyGPb/+hKunjgMABJkMLeK6o9NjwxBcN8LStklsVwycPKXcPMDa4FroMfJ5NIntWuX1ExE5m8cE4JkzZ3pNmzZN9eKLL+o/+eSTQgAoKCjAK6+8ol6xYoVCr9cLPXv2NH7++ee68PBwy7xaly5dEsaNG6fevn27wtfXVxo+fLghPj6+UKlUWs69efNm+b/+9S91YmKirF69etJbb71VOGbMGM7HVYmBAwciNjbW6r6SX1si8lySJOHikQPY89vPSDp7GgAgkyvQ8sGH0WngPxAQFm71uCaxXdG4Y2yFK8EREVV3HhGA9+zZI1uyZIlXdHR0qSswXnrpJfXatWsVP//8c4G/v780ceJE9eOPP+69e/fufAAwGo3o37+/T2hoqLRjx468pKQkYdSoUd5KpRLx8fGFAHD+/Hlh4MCBPmPHjtUvX77csHHjRvm4cePUderUEfv162dyx/OtDrRaLbRarbvLIKI7IIki/t6/B3t++9myVLFC6YVWD/dBhwGPw69WbZvnkMnkiGhp34qQRETVjdsDcE5ODp555hnvL774omDWrFmq4u2ZmZlYunSp8rvvvivo1auXCQC++eYbXcuWLX137dolj4uLM61bt05++vRp2aZNm3KLe4Xfeeedwrffflv93nvvFapUKixcuNArMjJSnD9/fiEAtGzZUty1a5di3rx5qn79+uW751kTETmfaDLhzO4d2Pv7CqRfuwIAUKrUaN27Hzo8+hh8OXsDEREAD5gHePz48epHHnnE2KdPn1K9sfv375cbDAb07t3bWLwtKipKjIiIkBISEuQAkJCQoGjZsqVYckjEI488YszOzsbx48dlALB37175ww8/bCx57t69exv37dtX4Xt5Op0OWVlZlo/sbE6UQUSey2Q04PjWDfhm8jj8+cmHSL92BSofX3Qe8iTGfvY1ug8fzfBLRFSCW3uAly9frjh8+LD8wIED5ZYdS05OFry8vBAYWPqPdkhIiJScnCwAQEpKihASElJq/eCwsDCp+PjK2mRnZyM/Px8+VpYsnjlzpmrWrFled/v8iIhcyajX48TW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803Bd999p3z77bctCf38+fPCwIEDfR588EHjoUOH8iZOnFg4btw49Z9//skea7JJFE24evIYEnf9hasnjyHl0gX8OP11ZCYnwa92KJ76dzxCG93n7jKJiIjIToIkSdmOHPCPf/zD29/fX/rqq690Wq1We+TIkdyQkBBp4MCBPhEREeLdzBKRkpIihIWFabZs2ZLfo0cPU2ZmJkJCQrTfffddwRNPPGEEgFOnTslatmzpu3Pnzvy4uDjTH3/8IR80aJDPtWvXcsPDwyUA+PTTT5Vvv/22OjU1NUelUuFf//qXau3atYpTp07lFT/W0KFDvTMzM4WNGzfm26orKysLAQEB2qysLPj5+d3p06Nq6NzeBGxZuhi5t9JubxQEQJJQKyISQ6b8G5qgYPcVSERERACA7Oxs+Pv7IzMzM8ff37/Stg73AM+dO1eXkJAgb968ua9Op8M///lP7wYNGmiuX78uxMfHF95x1TAHTQAIDg6WAGD//v1yg8GA3r17G4vbREVFiREREVJCQoIcABISEhQtW7YUi8MvADzyyCPG7OxsHD9+XAYAe/fulT/88MPGko/Vu3dv4759+6z2AOt0OmRlZVk+ime6oJrl3N4ErJ77funwCwCS+Uetw4AhDL9ERETVkMOzQNSvX186duxY3o8//qg4cuSIPC8vT3j22WcNI0aMMPj4+NxxISaTCS+99JK6S5cuppiYGBEAkpOTBS8vLwQGBpZqGxISIiUnJwuAudc4JCSk1NCLsLAwqfj4ytpkZ2cjPz8fZeueOXOmatasWV53/GSo2hNFE7YsXVxpm10/f4sW93fnUsZERETVjMMBGACUSiVGjBhhHDFihNF2a/uMHz9eferUKfmOHTvybLd2ralTpxa+9tprlt7s7Oxs1K9fX+vOmqhqXU88Wb7nt4yc9DRcTzzJi9+IiIiqGYeHQLz33nteixcvLjeDwuLFi5V32ms6fvx49Z9//qnYsmVLXv369S09tWFhYZJer0dGRkap9qmpqUJxL29oaKiUmppaalaI4p5fW238/PzK9f4CgFqthr+/v+WD435rntzMDNuNHGhHREREnsPhALxkyRKvFi1aiGW3R0dHi19++aVDAVgURYwfP169atUqxebNm/MbN25caphCx44dTUqlEhs3brT0VCcmJsquXr0qdO3a1QQAXbt2NZ48eVJWHHoB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wHZQKBRu6/318/ODreX8yHPx+1f98XtY/fF7WL3x+1f9VeX3MDAw0K52vAiOiIiIiGoUBmAiIiIiqlEYgD2USqXC22+/rVepVO4uhe4Av3/VH7+H1R+/h9Ubv3/Vnyd/D3kRHBERERHVKOwBJiIiIqIahQGYiIiIiGoUBmAiIiIiqlEYgImIiIioRmEA9kALFixQRkZGatRqtbZjx44+u3fv5vepmnjvvfe82rdv76vVarW1a9fWDBgwwDsxMZHfv2pq5syZXoIgaCdOnOh5lzBTha5evSo89dRT6qCgII23t7e2ZcuWvnv37uXvYTVhNBrx1ltvqRo0aKDx9vbWNmrUSDNjxgwvURTdXRpVYOvWrfJ+/fp5h4eHawRB0P7666+lFloTRRFTpkxRhYWFaby9vbU9evTwOXPmjFt/J/kHwcP88MMPitdff109derUwgMHDuTFxMSI/fr1801OThbcXRvZtn37dsX48eP1CQkJeevXr883GAzo06ePT25urrtLIwft2bNHtmTJEq/o6Gj+161Gbt26hW7duvkqlUqsWbMm/8SJE7kffvihLigoSHJ3bWSf999/32vx4sXKjz/+WHfy5Mnc2bNn6+bOnauaP3++l7trI+vy8vIQExMjfvLJJzpr+2fPnu21cOFCr4ULF+p2796d5+vrK/Xt29enoKCgqku14DRoHqZjx44+HTp0ED///HMdAJhMJkRERGgmTJignzp1qt7d9ZFjUlJShLCwMM2WLVvye/ToYXJ3PWSfnJwctGvXzvfTTz/VzZo1S9W6dWvTJ598Uujuusi2V199VbV79275rl278t1dC92ZRx55xDs0NFRaunSpJUwNHjzY29vbW/rxxx+tBizyHIIgaH/55ZeCIUOGGAFz72+dOnU0L7/8sv7NN9/UA0BmZibCwsK0X331VcHTTz9tdEed7AH2IIWFhTh8+LC8Z8+elh8GuVyOhx56yLhnzx65O2ujO5OVlQUACA4OZu9TNTJ+/Hj1I488YuzTpw9ftFQzf/zxh6J9+/amxx9/3Lt27dqa1q1b+37++edKd9dF9uvSpYtp69atitOnT8sA4NChQ7KEhAT5I4884pagRHfnwoULQkpKitCrVy/L9y8gIAAdO3Y07d69223ZRmG7CVWVmzdvCiaTCaGhoaXCUkhIiOTusTLkOJPJhJdeekndpUsX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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "eff.scope.plot_time_series(('S_aa', 'S_fa', 'S_va', 'S_bu', 'S_pro', 'S_ac')) # you can plot how each state variable changes over time" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "6f674fab", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(
,\n", + " )" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "eff.scope.plot_time_series(('S_IC'))" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "2ea79de8", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(
,\n", + " )" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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vn37uJEjR4YkKJEbc/78eSYvL48ZOnSo8/en0WjQq1cvfv/+/SHLNhLvRUh9KSgoYHieR3JysltYSkpKEkM9Vob4j+d5zJ49W9GvXz++S5cu9DV6hPj0008lx44d43755ZeKULeF+O/ChQvs+++/L5s1a5Z50aJFpkOHDnFz585VyOVyTJ482RLq9hHvFi9ebC4tLWU6dOgQxXEceJ7H888/b3rkkUcoAEegnJwcFgC0Wm21bJObmxuybEMBmJAgycjIUJw6dYrbs2cPBakIcfHiRWbOnDmK7du365VKZaibQ+pAEAR0796df/nll00A0LNnT+HkyZPse++9J6UAHBk2btwo2bhxo3TDhg2GTp06CceOHePmzp0rb9asmUi/QxIo1KsYRhITE0WO45CXl+d2wVt+fj5TtVeYhLeMjAzFd999J9m1a1dFamoq/e4ixC+//MIVFBQwPXv2jJJIJGqJRKLes2cP984778gkEonaaqUOqHCn1WrF9u3bu33j0q5dO+Hy5cv09y5CLFiwQDF//nzT+PHjrV27dhUmTZpkmTVrlnnlypV0EVwESklJEQCg6sX8+fn5jFarDdm3o/SGEEbkcjluueUWfufOnc6eeZ7nsXv3bknfvn1pLGIEEAQBGRkZis2bN0t27typb926NYXfCDJ06FDrr7/+WnH06FHnrXv37sJDDz1kOXr0aIVEQl+ahbt+/frxZ8+edfvblpWVxaamptIwpAih1+vBsu7xhOM4iCK9nUaiVq1aicnJyeKOHTucb6A6nQ6HDx/m+vXrF7JsQ+/mYWbOnDnmyZMnK3v27Mn36dOHf+ONN2R6vZ6ZMmUKfe0TATIyMhSff/65dNOmTXq1Wi1eu3aNAQCNRiOqVKpQN494ERMTg6rjtaOiosSEhASRxnFHhjlz5pgGDhwY9cILL8geeughy8GDB7m1a9fKVq9eHbr5lohfRo0aZV25cqW8ZcuWYqdOnfijR49yq1atkj366KP0dzBMlZWVwfWD5/nz59kjR46wCQkJYlpampiZmWleuXKlvG3btkKrVq2EJUuWyFNSUsR77703ZF+r0TRoYejNN9+Uvv766/K8vDymS5cu/KpVq0z9+/enHuAIwDCM2tP2Dz74wDh16lR6845At912m4qmQYssmzdvlixatEh+7tw5tmXLlsJTTz1lzsjIoH9/EaK0tBSLFy+Wb968WVpQUMCkpKSI48aNszz//PMmuZzWpAlHO3fu5IYMGVKtl2fChAmWDRs2GAVBwJL/b+/+Qdp44ziOP5c/eknv1Gi1KoVaHGJEUaEVpdDBf1CxU61CQbNYqIEMpW4KLR3FRRwCRVFEEFxcSoZiB0VEokNxMIJClwq2glGbqNXk7jeUiP2VWiP1T7j3Cw7uDp57vjflw5Pv3fX0pA4NDVl3dnak6urqmM/nOygqKrqyhQUCMAAAAAyFHmAAAAAYCgEYAAAAhkIABgAAgKEQgAEAAGAoBGAAAAAYCgEYAAAAhkIABgAAgKEQgAEAAGAoBGAASBJtbW3y48ePbZc97+DgoFWSJFWSJNXr9Z76Ka47d+4ofX19KSeP42NDodDFFwsAZ2C56gIAAH/+jHZcd3f34cDAwIGu65dV0i/S0tJEMBgMK4qSUAGBQCAyMzNjbmlpufTgDgB/QgAGgGtgfX09HN8fHx+3vn37NjUYDB6fU1VVV9VTM/KFkiRJ5OfnJ5y+b926pWdmZl5NageAP6AFAgCugfz8fD2+paen6/HAGd9UVf2tBeLhw4d2j8cje73eVIfDoebk5Cg+n88aDodFe3u7rKqqWlhYqLx//958cq6lpSVTQ0ODXVEUNScnR3n27Jm8ubkpJVrzxsaG1NjYaLPZbGpBQYEyOjrKogqApEAABoAkNjY2Zs3KytLn5+cjHo/n0Ov1yk+ePLFVV1fHFhcXI3V1dVG3222LRCJCCCFCoZCora21l5eXxwKBQMTv9+99/frV9PTp04RbFNxut/zlyxfT1NTU3sTExJ7P50s5T5AGgMtGAAaAJFZaWhp78+bNodPp1Hp6eg5lWRY3b97UOzs7j5xOp/b69esfW1tb0qdPn8xCCNHf359SVlam9fb2/iguLtbu3bunDQ8P709PT5tXVlbO/JuwsrJi+vDhg+Xdu3f7Dx48iFVWVmpDQ0MH+/v7F3ezAPCP8HcVACSx0tJSLb5vsVhEZmamXlJScnwuNzdXF0KIb9++SUIIsbS0ZJ6ZmTErivJbQ/Ha2ppUVFR0pnmXl5dNFotF3L9//3iu4uJiLSMj4/w3AwCXhAAMAEnMarX+8oCZJEnCarUeH5tMPxd1Ne1nTg2Hw1JjY2O0t7f34P/XOs9DbgCQjAjAAGAgFRUVscnJScvdu3f1k0E5US6XS4tGo2JhYcFUVVWlCSFEMBg0bW9v/6tSAeDC0AMMAAbi9XoPQ6GQ1Nraapufnzetrq5Kfr/f3N7eLkej0TNfx+VyafX19bEXL17Y5ubmzIFAwNTR0SHbbLzuF8D1RwAGAAO5ffu2Pjs7uxeLxcSjR49ulJWVKS9fvpQzMjL0eLvEWY2MjOzn5eVpNTU19ubmZvvz58+PsrOzaaMAcO1Juq7vXnURAIDra3Bw0NrV1SVvb29/P8/4jx8/muvq6uxbW1vfHQ7Hvy4PABLGCjAA4K92dnaEoijqq1evUhMZ53K5bjQ1Ndkvqi4AOA9WgAEAp9rd3RUbGxuSEEI4HA6RSJvD58+fpaOjIyGEEIWFhbrZbP7LCAC4eARgAAAAGAotEAAAADAUAjAAAAAMhQAMAAAAQyEAAwAAwFAIwAAAADAUAjAAAAAMhQAMAAAAQyEAAwAAwFD+A/fWK90JbqE6AAAAAElFTkSuQmCC\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "eff.scope.plot_time_series(('X_aa', 'X_fa', 'X_c4', 'X_pro', 'X_ac', 'X_h2'))" + ] + }, + { + "cell_type": "markdown", + "id": "1f991148", + "metadata": {}, + "source": [ + "### 3.2. Check simulation results: Gas" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "d54aeb58", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(
,\n", + " )" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "gas.scope.plot_time_series(('S_h2'))" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "e021d8fb", + "metadata": { + "scrolled": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(
,\n", + " )" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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qMoNKUlWMPeLnPPCLwDvY3O/yIEkh+zTTURERNRk1OmqqiNHjgiTJ0/WDxo0yDBo0CDDlClTdEeOHAm6MbW4uBi7du0Sd+3aJQLAL7/8Iu7atUs8duyYIIoi7r//fuezzz6r+/TTT9U//PCDOG7cuBiLxaJ4VoXo0qWLPGTIEOnvf/97zI4dO8TNmzerHnjgAf0tt9zibt26tQIAd955p0uj0Sh/+9vf9Pv27RPff/999auvvqp96KGHnLXV1piycwu9bQ++KADyrHZk5/q+iI+IiIiIahZ0C8TXX3+t+stf/mLo2rWr1L9/fwkAtm/frrrsssuMn332WVlGRkbAO8FlZ2erKm+e8eijj+oA6MaNG+d699137U888YSztLRUuPfee/VWq1Xo16+f9M0335TFxHiX+cXq1avLJk+eHDN06NBYURRxww03uF555RVveoyPj8e6devKpkyZou/Vq1dsYmKiMmPGDEckL4F2orjm8FuXcURERER0jqAoSlBrfHXr1i12yJAh7ueff95R+fFp06bp1q9fr967d29paEtsfFarFfHx8Sar1Yq4uLiwv96Oo6dx2xtZfsetntQX/donhr0eIiIiokhns9lgNptRVFRUbDabax0bdAvETz/9JE6aNOm82dOJEye6Dh06xIVqQ6B3agIsZj1q6ikRAFjMevROjcweZiIiIqJIFnRgbd68ubJnz57znrdnzx6xRYsWvCorBFSigDmj0wDgvBDsuT9ndBrXAyYiIiKqg6B7gCdMmODMzMyMOXr0qGPAgAESAGzdulX1wgsv6B544AGHv+dTYDLSLVg2rkeVdYABINms5zrARERERPUQdA+wLMt44YUXtEuWLNF6dluzWCzKww8/7Jw6dapTFC+8LoiG7gGuTJIVXPv8f3G8sAyPD++ESVddzJlfIiIiomqC6QEOegZYFEVMnz7dOX36dKfNVpGdGzoUNiUqUYBKVRF4u7eJZ/glIiIiqqegA3BlDL4Nw+Gq2LBOr1E1ciVERERE0S+gANy9e/fYjRs3liYkJKBbt26xglDzLOSFuAxaY3O4K5ZW1msuvPYSIiIiooYWUAAePXq0S6fTeT52C4LA1R4akGcGWKfmDDARERFRfQUUgOfNm+fdNnj+/Plc6aGB2TkDTERERBQyQSeq1NRU46lTp87rgThz5gxSU1ONoSmLPCRZ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S/nQoUMNR44cETp06OB70WI/Zs2a5Zg+fbq3P9hmsyElJaXBN/lwuM99mvUaBmAiIiKiUAi6BeKhhx5yLlmyRCvLDZuBc3NzhU2bNqn8bcXcr18/CQCOHDkiAhU9xCdOnKgy0+uZ+fXMBFen1+thNpu9t7i4uNC8iSCVO891lOjVQX+piIiIiMiHoGeAt23bptq8ebN63bp16s6dO0sajabKcU/rQaitWLFC26JFC8Vf68Xu3btVAOC5KK5///7uRYsWafPz8wVP4F23bp06Li4O6enpEb1xR6mz4q2qBGDnsTPonZrAPmAiIiKiego6AMfHxytjxoxp0EV0JUnCO++8oxk3bpyrcuA+cuSIsGrVKs3IkSPdzZs3V3744QfVI488or/yyiul7t27ywCQkZEhderUSR43blzM4sWL7fn5+cKcOXN099xzj1Ovj9zd1Nbuz8Psz/YDACQFuO2NLFi4DBoRERFRvQmKojTOEgdB+Oabb1QjRowwHDx4sLRTp07eWdvjx48L48aNizlw4IBYVlYmtG7dWh4zZox7zpw5DrPZ7H1+bm6ucO+99+q3bNmiNhgMyp133ulavHixo/rsdU2sVivi4+NNVqu1Qdoh1u7PQ+aq3eftBOeZ+102rgdDMBEREVElNpsNZrMZRUVFxZVzoC91CsAulwsbN25U/fzzz+Kdd97piouLw++//y6YzWbFZGrwa8XCriEDsCQruHLRxiobYFTm2Qp562PXsh2CiIiI6KxgAnDQLRC5ublCRkaG4ffffxcdDgeGDRvmjouLU5599lmtw+EQ3njjDd/JjQKSnVtYY/gFAAVAntWO7NxC9Guf2HCFEREREV0ggl5a4IEHHtD37NlTKiwsLI6JifE+/pe//MW9adMmrtVVTyeKA/v5IdBxRERERFRVnVaB2LZtW1n1LYRTU1PlP//8k2t11VNLU2AX5gU6joiIiIiqCjqwKooiSNL5Ox7/9ttvotForNPGE3RO79QEWMx61NTdKwCwmPXonZrQkGURERERXTCCDsDXXXed+6WXXvJuEywIAoqLi/HUU0/pMjIyGnx75AuNShQwppvlvBUgKpszOo0XwBERERHVUdAtEC+++KJ92LBhhk6dOsXa7XbcfvvtMT///LOYmJiorF692uH/DFSbtfvzsHxzbo3H/z4wlUugEREREdVDnZdBW716tXrv3r2q0tJS4fLLL5fuuusul8FgCEeNja6hlkHztwQaUNH+wCXQiIiIiKoK6zJoAKDRaHDXXXe577rrLrY8hJC/JdAALoFGREREVF9B9wDPmzdPu3z58vO2UFu+fLlmwYIFWl/PocBwCTQiIiKi8As6AL/55pvazp07y9UfT09Pl9944w0G4HrgEmhERERE4Rd0AC4oKBBatWp1XgBu2bKlnJ+fz8bUeuASaEREREThF3QAbt26tbx169bzeoe3bt2qtlgsXAe4HlSigDmj02pcAk0Bl0AjIiIiqq+gL4KbMGGC6+GHH9Y5nU4MHjzYDQDfffed+oknntA99NBDztCXSEREREQUOkEH4Mcff9x5+vRp4YEHHtA7nRV5V6/X45FHHnHMnj2bAbgeJFnB3C9yajwuAJj7RQ6GpCVzFpiIiIiojuq0DjAAFBcX48CBA6LBYEDHjh1lvf7CvTCrodYB3nH0NG57I8vvuNWT+nIZNCIiIqJKwr4OMACYTCb07dv3vIvhqO64DBoRERFR+AUdgEtKSrBgwQLdpk2bVCdPnhRluWoGzs3NLQlZdU0Ml0EjIiIiCr+6XAQXs2XLFtXtt9/uslgsbkFgL2qoeJZBy7fafa4EIQBI5jJoRERERPUSdAD+9ttv1Z9//nnZwIEDpXAU1JR5lkG7d9Vun8e5DBoRERFR/QW9DnB8fLySmJjI9X6JiIiIKCoFHYDnzp3rmD17tq60tDQc9TRpgS6DJsn8+YOIiIioroJugXjxxRe1ubm5YnJysiklJUXWaDRVju/du5fJuI6ycwuRZ615hQcFQJ7VjuzcQi6DRkRERFRHQQfgMWPGuMJRCHEZNCIiIqKGEHQAnjdvHnd7CxMug0ZEREQUfnXeCCM7O1s8ePCgCgC6dOkiXXHFFdwUo556tm0GUQBqa/EVhYpxRERERFQ3QQfg/Px8YezYsTGbN29WxcfHAwCKiopw9dVXSx988EF5UlISr9Cqo13Hz9QafoGKcLzr+Bn2ABMRERHVUdCrQNx333364uJiYd++faWFhYXFhYWFxT/++GOpzWYT7r//fv5uvh7YA0xEREQUfkHPAH/33XfqdevWlXbp0sXb8pCeni6/8sor5cOHD48NbXlNS3OjLqTjiIiIiOh8Qc8Ay7KM6kufAYBGo4Essw24XgJtHmGTCREREVGdBR2Ar776avdDDz2k//3337378f7222/Cww8/rL/mmmvcoS2vaTlV6gjpOCIiIiI6X9AB+NVXX7XbbDbh4osvNnpu7du3N9psNuGVV15hc2o9cBk0IiIiovALuge4bdu2yp49e0q//fZb1cGDB0UASEtLk4cNGyaFvrymhcugEREREYVfndYBFkURGRkZUkZGBkNvCHEZNCIiIqLwC7gF4rvvvlN16tQp1mq1nnesqKgInTt3jv3vf/+rCmVxs2bN0gmCYKp869ixo3elifLyctx77736hIQEo9FoNN1www0xeXl5QuVzHDt2TMjIyIgxGAymFi1aGKdOnapzuSJzN2cug0ZEREQUfgEH4CVLlmjvvvtul9lsPu9YfHw8Jk2a5HrxxRe1Ia0OQOfOneU//vijxHPbtm1bmefYgw8+qP/qq6/UH374YfnGjRtL8/LyhBtvvDHGc9ztdmPkyJEGp9MpbNmypfStt94qf/fddzUzZ86MyHXEjp0qDWgce4CJiIiI6i7gFoh9+/apFi9eXOPyAxkZGe6XXnop5AFYrVajVatW5zUGFBUVYeXKlZp33323fMiQIRIAvPXWW/YuXbrEbtu2TTVgwABp7dq1qkOHDonr168vsVgsCgA89dRTjpkzZ+rnzZvn0Ol852C73Q6H49xbtdlsoX5b55FkBauzf/U7zmLWo3dqQtjrISIiIrpQBTwDfOLECUGj0dTYoapWq5VTp04JNR2vq6NHj4oWi8WYmppqHDt2bMyxY8cEANi5c6fK5XJh6NCh3qXX0tLS5DZt2ijbt29XAcD27dvVXbp0kT3hFwCGDx/uttls2LdvX43vff78+br4+HiT55aSkmIK9fuqLju3EPk2/8ubje2VApUY8k8zERERUZMRcABu1aqVsm/fvhp7fH/44QdVcnJySLdo6Nu3r3vFihXl33zzTdmrr75afuzYMWHgwIGxNpsN+fn5glarRbNmVVdEaNmypZKfny8AQEFBgdCyZcsqNXlq9IzxZdasWY6ioqJiz+3XX38tDuX78iXQvt52zQ1hroSIiIjowhZwC0RGRob7ySef1I0YMcIdExNT5VhZWRmeeuop3YgRI0J6ddmoUaO8q0x0794d/fr1K2vXrp1p9erVGoPBELb90PR6PfT6hu2z5TbIRERERA0j4AA8e/Zsx2effRbbsWNHY2ZmprNTp04yABw8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5c0339Q0dm0AoPnpy5COIyIiIiLfmkQAdjgc2LNnj2rw4MFuz2MqlQrXXnutOysr67w9he12O6xWq/dms9nCXmNqUWCrOwQ6joiIiIh8axIB+OTJk4IkSUhKSqqyzUTLli2VgoKC81og5s+fr4uPjzd5bikpKaZw11jD/hd1HkdEREREvjWJABysWbNmOYqKioo9t19//bU43K+ZE9M3pOOIiIiIyDd1YxfQEFq0aKGoVCpUn+09ceKEUH1WGAD0ej30en3DFQigT+ZyKC+0BuB7NzjPFsl9Mpc3YFVEREREF54mMQOs0+lw+eWXSxs2bPAGfkmSsGnTJnXfvn2lxqzNwxhnQpa2D4BzYdfDcz9L2wfGuLB3YxARERFd0JpEAAaAqVOnOv/1r39p/vWvf2kOHDgg3nPPPfqysjLh7rvvdjV2bR79Zn7rDcHVZWn7oN/Mbxu4IiIiIqILT5NogQCA22+/3X3ixAn7U089pSsoKBC6du0qffXVV2UWiyWirivrN/NblNiKkb08E3Hlv8EW0wa9/74M/TjzS0RERBQSgqIo4V/jK8pZrVbEx8ebrFYr4uLiGrscIiIiIqrGZrPBbDajqKio2Gw21zq2ybRAEBEREREBDMBERERE1MQwABMRERFRk8IATERERERNCgMwERERETUpDMBERERE1KQwABMRERFRk8IATERERERNSpPZCa4+FKViszibjXuGEBEREUUiT07z5LbaMAAHoLi4GADQpk2bRq6EiIiIiGpTXFyM+Pj4WsdwK+QAuN1u5OXlwWg0QhQbpmvEZrMhJSXF9OuvvxZz++Xow69f9OPXMPrxaxjd+PWLfg39NZRlGSUlJbBYLFCra5/j5QxwANRqdaPN/sbFxcHfftYUufj1i378GkY/fg2jG79+0a8hv4bNmjULaBwvgiMiIiKiJoUBmIiIiIiaFAbgCKXT6TBz5kynTqdr7FKoDvj1i378GkY/fg2jG79+0S+Sv4a8CI6IiIiImhTOABMRERFRk8IATERERERNCgMwERERETUpDMBERERE1KQwAEegpUuXatq2bWvU6/WmXr16GXbs2MGvU5SYN2+etmfPnrEmk8nUokUL4+jRo2MOHjzIr1+Umj9/vlYQBNP9998feZcwU41+++034bbbbtMnJCQYY2JiTF26dIn9v//7P/47jBJutxtPPPGErl27dsaYmBjTxRdfbJwzZ45WluXGLo1qsGnTJtWIESNiLBaLURAE08cff1xlozVZljFjxgxdcnKyMSYmxjRo0CDDTz/91Kj/JvkfQoR5//331Y8++qh+1qxZju+//760a9eu8ogRI2Lz8/OFxq6N/Nu8ebM6MzPTuX379tJ169aVuVwuDBs2zFBSUtLYpVGQsrKyxDfffFObnp7O77pRpLCwEFdeeWWsRqPBV199VbZ///6S559/3p6QkKA0dm0UmGeeeUa7fPlyzT/+8Q/7gQMHShYuXGh/8cUXdUuWLNE2dm3kW2lpKbp27Sq//PLLdl/HFy5cqH3ttde0r732mn3Hjh2lsbGxSkZGhqG8vLyhS/XiMmgRplevXoYrrrhCXrZsmR0AJElCmzZtjJMnT3bOmjXL2dj1UXAKCgqE5ORk48aNG8sGDRokNXY9FJji4mL06NEj9pVXXrEvWLBA161bN+nll192NHZd5N+0adN0O3bsUG3btq2ssWuhuhk+fHhMUlKSsnLlSm+YuuGGG2JiYmKU1atX+wxYFDkEQTD95z//Kb/pppvcQMXsb6tWrYwPPfSQ8/HHH3cCQFFREZKTk00rVqwov+OOO9yNUSdngCOIw+HAnj17VIMHD/b+ZVCpVLj22mvdWVlZqsasjerGarUCABITEzn7FEUyMzP1w4cPdw8bNow/tESZL7/8Ut2zZ0/pxhtvjGnRooWxW7duscuWLdM0dl0UuH79+kmbNm1SHzp0SASA3bt3i9u3b1cNHz68UYIS1c8vv/wiFBQUCEOGDPF+/eLj49GrVy9px44djZZt1P6HUEM5efKkIEkSkpKSqoSlli1bKo3dK0PBkyQJDz74oL5fv35S165d+Wv0KPHee++p9+zZo/r+++9LG7sWCt6xY8fE5cuXax944AHnjBkzHNnZ2aqHH35Yr9PpMGHCBFdj10f+zZw502mz2YS0tLRYlUoFSZIwd+5cx1133cUAHIXy8vJEAEhOTj4v2+Tn5zdatmEAJgqTzMxMfU5OjmrLli0MUlHi+PHjwtSpU/XffvttWUxMTGOXQ3UgyzJ69OghLV682AEAV1xxhXzgwAHx9ddf1zAAR4cPPvhA/cEHH2jefffd8vT0dHnPnj2qhx9+WHfRRRcp/BpSqHBWMYK0aNFCUalUKCgoqHLB24kTJ4Tqs8IU2TIzM/Vff/21euPGjaUpKSn82kWJ77//XnXy5EnhiiuuiFWr1Sa1Wm3asmWL6tVXX9Wq1WqT280JqEiXnJysdO7cucpvXDp16iT/9ttv/H4XJR577DH99OnTHXfccYe7W7du8vjx410PPPCA89lnn+VFcFHIYrHIAFD9Yv4TJ04IycnJjfbbUf6HEEF0Oh0uv/xyacOGDd6ZeUmSsGnTJnXfvn3ZixgFZFlGZmamfs2aNeoNGzaUtW/fnuE3igwZMsT9ww8/lO7evdt769Gjhzx27FjX7t27S9Vq/tIs0vXr1086fPhwle9tR44cEVNSUtiGFCXKysogilXjiUqlgqLwv9NodPHFFytJSUnK+vXrvf+BWq1W7Ny5U9WvX79Gyzb83zzCTJ061TlhwoSYK664QurTp4/00ksvacvKyoS7776bv/aJApmZmfoPP/xQ88knn5SZTCblzz//FAAgPj5eMRgMjV0e+REXF4fq/dqxsbFKYmKiwj7u6DB16lTHVVddFfv0009rx44d6/q///s/1YoVK7TLli1rvPWWKCgjR450P/vss7q2bdsq6enp0u7du1VLly7V/vWvf+X3wQhVXFyMyj94/vLLL+KuXbvExMREpV27dsr999/vfPbZZ3UdO3aUL774YnnWrFk6i8Wi3HjjjY32azUugxaBlixZonnxxRd1BQUFQteuXaWlS5c6+vfvzxngKCAIgsnX42+88YZ94sSJ/M87Cg0cONDAZdCiy5o1a9QzZszQHT16VGzbtq380EMPOTMzM/nvL0rYbDbMnDlTt2bNGs3JkycFi8Wi3Hrrra65c+c6dDruSROJNmzYoBo8ePB5szzjxo1zvfvuu3ZZljFr1izdihUrNFarVejXr5+0bNkye6dOnRptYoEBmIiIiIiaFPYAExEREVGTwgBMRERERE0KAzARERERNSkMwERERETUpDAAExEREVGTwgBMRERERE0KAzARERERNSkMwERERETUpDAAExFFiTvvvFM/evTomIZ+3TfffFMjCIJJEATT/fffX+tWXG3btjU+//zz2sr3Pc89c+ZM+IslIgqAurELICKimrfR9pg5c6bz5ZdftiuK0lAlVREXF4eDBw+WGI3GoArIzs4u3bx5s+rWW29t8OBORFQTBmAiogjwxx9/lHg+Xr16tebpp5/WHTx40PuYyWRSTKZaM3JYCYKAVq1aBZ2+k5KSlISEhMZJ7URENWALBBFRBGjVqpXiuZnNZsUTOD03k8l0XgvEwIEDDZMnT9bff//9umbNmplatmxpXLZsmaakpAR33XWX3mQymdq3b2/88ssvVZVf68cffxSHDh1qMBqNppYtWxpvv/12/cmTJ4Vga87PzxdGjBgRExMTY2rXrp3xnXfe4aQKEUUFBmAioii2atUqTWJiopKVlVU6efJk5/3336+/6aabYvr16yd9//33pYMHD3b/9a9/jSktLQUAnDlzBtddd52he/fuUnZ2dunXX39dVlBQIN5yyy1Btyj89a9/1f/+++/i+vXryz766KOyZcuWaesSpImIGhoDMBFRFLvsssukp556ynnppZfKs2bNcur1ejRv3lzJzMx0XXrppfKcOXMchYWFwt69e1UAsHTpUm23bt3kxYsXO9LS0uQrrrhCfuutt8r/97//qQ4dOhTw94RDhw6J3377rXr58uXlAwYMkHr37i2vWLHCXl5eHr43S0QUIvx1FRFRFLvssstkz8dqtRoJCQlKenq697Hk5GQFAE6cOCEAwI8//qjavHmzymg0ntdQ/PPPPwudOnUK6HVzcnJEtVqNXr16eV8rLS1Njo+Pr/ubISJqIAzARERRTKPRVLnATBAEaDQa731RrJjUleWKnFpSUiKMGDHCvXjxYnv1c9XlIjciomjEAExE1IRcfvnl0qeffqpOTU1VKgflYHXu3Fl2u93YuXOn2LdvXxkADh48KBYVFYWqVCKisGEPMBFRE3L//fc7z5w5I/y///f/YrKyssQjR44IX3/9tequu+7Su93ugM/TuXNneciQIdK9994bs337dlV2drY4ceJEfUwMl/slosjHAExE1IS0bt1a2bp1a5kkSRg+fHhst27djFOnTtXHx8crnnaJQK1cubLcYrHI1157reHmm282TJo0ydWiRQu2URBRxBMURbE1dhFERBS53nzzTc20adP0RUVFxXV5/oYNG1SDBw82FBYWFjdr1izU5RERBY0zwERE5JfVaoXRaDQ98sgjumCe17lz59hRo0YZwlUXEVFdcAaYiIhqZbPZkJ+fLwBAs2bNEEybQ25uruByuQAA7du3V1QqlZ9nEBGFHwMwERERETUpbIEgIiIioiaFAZiIiIiImhQGYCIiIiJqUhiAiYiIiKhJYQAmIiIioiaFAZiIiIiImhQGYCIiIiJqUhiAiYiIiKhJ+f9Q08vRVFV3KAAAAABJRU5ErkJggg==\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "gas.scope.plot_time_series(('S_ch4','S_IC'))" + ] + }, + { + "cell_type": "markdown", + "id": "ccde4d80", + "metadata": {}, + "source": [ + "### 3.3. Check simulation results: Total VFAs" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "56f3fad7", + "metadata": {}, + "outputs": [], + "source": [ + "# Total VFAs = 'S_va' + 'S_bu' + 'S_pro' + 'S_ac' (you can change the equations based on your assumption)\n", + "idx_vfa = cmps.indices(['S_va', 'S_bu', 'S_pro', 'S_ac'])\n", + "\n", + "t_stamp = eff.scope.time_series\n", + "\n", + "vfa = eff.scope.record[:,idx_vfa]\n", + "total_vfa = np.sum(vfa, axis=1)" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "a879f514", + "metadata": { + "scrolled": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0, 0.5, 'Total VFA [mg/l]')" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAkQAAAGwCAYAAABIC3rIAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy81sbWrAAAACXBIWXMAAA9hAAAPYQGoP6dpAABVuklEQVR4nO3deViU9foG8PudhVmYGRZlV9wXQBEXNJdKTc2l1OyopWYe05NamlubUrZoi782j5VWmpWZ6dFKc8lcU8MFxR3cUETZFZhhm/39/YGQpCko8AJzf65rrmp4h3mYdObmuzxfQRRFE4iIiIhcmEzqAoiIiIikxkBERERELo+BiIiIiFweAxERERG5PAYiIiIicnkMREREROTyGIiIiIjI5SmkLqAmsNvtSE1NhU6ng0zGDElERFQTOJ1O5OXlISAgAArF7SMPA1EZpKamIjg4WC91HURERFR+SUlJufXr17/tNQxEZaDXF2Why5cvw2AwSFwNERERlYXJZEL9+vVLPsdvh4GoDARBAAAYDAYGIiIiohqm+HP8drgghoiIiFweAxERERG5PAYiIiIicnkMREREROTyGIiIiIjI5TEQERERkctjICIiIiKXx0BERERELo+BiIiIiFweAxERERG5PAYiIiIicnkMREREROTyGIiIiIhIUln5VpxLz5W0Bp52T0RERFUiO9+Kcxl5OJuei3PpuTibnodzGbm4mmdFYx937JjRXbLaGIiIiIioQhkLbDibkXs9+BQFoLPpebiaZ/nHxzidIhxOEXKZUIWV/oWBiIiIiO6Kwyni4tU8nEox4WSyEfGpRSEoI/efg0+QpwbN/HRo7qdHM9+ifzb11cFdJW0kYSAiIiKiO7LanTibnou4FBNOphhxKsWEuBQTCm2OW14f6KFGMz89mvvprv+zKPjoJA4+/6R6VkVERESSKbDaEZ+ai7gUI04mm3Aq1YgzabmwOcSbrtUo5QgJ0KNVkAdCAwxo7l808qNXKyWo/O4xEBEREbkwY6ENp1KMRSM/yUUjPwmZeXDenH1gUCvQKsgDYYGGkn82qquTbN1PRWIgIiIichGZuRac/Fv4ScoquOW1PnoVWgUaEBbogVZBRf+s56WBINT88HMrDERERES1jCiKSM4pxMlkU9G01/UA9E+Lnet5adAqsPTIj69BXcVVS4uBiIiIqAZzOkVc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b06XEVUT/NWEiGqlyZMnW7Ozs4Xhw4dr9u/fLzt37pywadMm+ejRo9V2u73M3yckJMTZu3dvx4QJEzTR0dHygwcPysaNG6fWaP5am920aVOnzWbDJ5984nb+/Hnhm2++UX711Vc3NW309vbGoEGD7K+++qrqoYcecgQHB3OLPVE1wUBERLVSvXr1xL179xY4HA7069fPvU2bNrpp06apPT09xfJOU33zzTeFAQEBzp49e2r/9a9/acePH2/z8fEpCTPt2rVzzp8/3/LBBx+4hYeH61asWKGcN2+e5Vbf65lnnrFarVaMHTvWequvE5E0BFEU2QCDiAhFR3fMnDlTnZOTk1tZz/HNN98oZ8yYoUpJSclTqW5uSF0VNRDRzbiGiIjoBkajETqdTv/ss89aP/zww1uO8tyN/Px8pKSkCO+//77buHHjbLcKQzqdTm+326FWqyvqaYmojDhCRER0nclkQlpamgAAXl5euHFa7F5FRUWp3n//fbdu3bo51q9fX3Cr40nOnj0rAIBcLkeTJk24voioCjEQERERkcvjomoiIiJyeQxERERE5PIYiIiIiMjlMRARERGRy2MgIiIiIpfHQEREREQuj4GIiIiIXB4DEREREbm8/wdwP9T6LDL75QAAAABJRU5ErkJggg==\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import matplotlib.pyplot as plt\n", + "plt.plot(t_stamp, total_vfa)\n", + "plt.xlabel(\"Time [day]\")\n", + "plt.ylabel(\"Total VFA [mg/l]\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.13" + }, + "varInspector": { + "cols": { + "lenName": 16, + "lenType": 16, + "lenVar": 40 + }, + "kernels_config": { + "python": { + "delete_cmd_postfix": "", + "delete_cmd_prefix": "del ", + "library": "var_list.py", + "varRefreshCmd": "print(var_dic_list())" + }, + "r": { + "delete_cmd_postfix": ") ", + "delete_cmd_prefix": "rm(", + "library": "var_list.r", + "varRefreshCmd": "cat(var_dic_list()) " + } + }, + "types_to_exclude": [ + "module", + "function", + "builtin_function_or_method", + "instance", + "_Feature" + ], + "window_display": false + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/source/tutorials/12_Chlorination.ipynb b/docs/source/tutorials/12_Chlorination.ipynb deleted file mode 100644 index 7b416768..00000000 --- a/docs/source/tutorials/12_Chlorination.ipynb +++ /dev/null @@ -1,1046 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "28c4658c", - "metadata": {}, - "source": [ - "# Process Design Example: Chlorination \n", - "\n", - "- **Prepared by:**\n", - " \n", - " - [Yalin Li](https://qsdsan.readthedocs.io/en/latest/authors/Yalin_Li.html)\n", - " - [Philipp Steiner](https://www.eawag.ch/en/aboutus/portrait/organisation/staff/profile/philipp-steiner/show/)\n", - " - [Eva Reynaert](https://www.eawag.ch/en/aboutus/portrait/organisation/staff/profile/eva-reynaert/show/)\n", - "\n", - "- **Covered topics:**\n", - "\n", - " - [1. Design Algorithms](#s1)\n", - " - [2. Process Algorithms](#s2)\n", - " - [3. Unit Classes](#s3)\n", - " - [4. System, TEA, and LCA](#s4)" - ] - }, - { - "cell_type": "markdown", - "id": "90d4bb2f", - "metadata": {}, - "source": [ - "---\n", - "### Note\n", - "This tutorial is under active development." - ] - }, - { - "cell_type": "markdown", - "id": "903ee36f", - "metadata": {}, - "source": [ - "---" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "8e608f02", - "metadata": {}, - "outputs": [], - "source": [ - "# Add the path to your cloned repos\n", - "import os, sys\n", - "coding_path = os.path.abspath(os.path.join(sys.path[0], '../../../../'))\n", - "for abbr in ('tmo', 'bst', 'qs'):\n", - " sys.path.append(os.path.join(coding_path, abbr))" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "3dc1138e", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "This tutorial was made with qsdsan vNone.\n" - ] - } - ], - "source": [ - "import qsdsan as qs\n", - "print(f'This tutorial was made with qsdsan v{qs.__version__}.')" - ] - }, - { - "cell_type": "markdown", - "id": "efa4852f", - "metadata": {}, - "source": [ - "### Summary\n", - "In this example, we will show how we can set up a chlorination process in `QSDsan`, which would include a contact zone, mixing/storage tanks for the chemical sodium hypochlorite (NaOCl) and treated water, and pumps (contact zone, NaOCl dosing, water storage).\n", - "\n", - "The algorithms largely follows the methodoly presented in Jones et al., Life cycle environmental impacts of disinfection technologies used in small drinking water systems. *Environmental Science & Technology*, **2018**, *52* (5), 2998-3007. https://doi.org/10.1021/acs.est.7b04448" - ] - }, - { - "cell_type": "markdown", - "id": "b7f9ccfc", - "metadata": {}, - "source": [ - "## 1. Design Algorithms " - ] - }, - { - "cell_type": "markdown", - "id": "de048b24", - "metadata": {}, - "source": [ - "### 1.1. Contact zone" - ] - }, - { - "cell_type": "markdown", - "id": "ac3a9505", - "metadata": {}, - "source": [ - "In the contact zone, chlorine (in the form of NaOCl) is added and reacts with the influent stream to inactivate microorganims (e.g., viruses, bacteria, protoza). In this example, the contact zone is modeled as a serpentine tubing." - ] - }, - { - "cell_type": "markdown", - "id": "29d2f234", - "metadata": {}, - "source": [ - "To determine the amount of NaOCl to be added, we will need to calculate the CT (concentration$*$time) values required by the inactivation target." - ] - }, - { - "cell_type": "markdown", - "id": "e5e4fdaa", - "metadata": {}, - "source": [ - "Let's assume that we will use the following table from U.S. Environmental Protection Agency to determine the CT (in min-mg/L) for 4-log inactivation of viruses by free chlorine (Table B-2 on Page B-3 in this [Disinfection Profiling and Benchmarking Technical Guidance Manual](https://www.epa.gov/system/files/documents/2022-02/disprof_bench_3rules_final_508.pdf))." - ] - }, - { - "cell_type": "markdown", - "id": "c7f08d0a", - "metadata": {}, - "source": [ - "| Temperature (°C) | pH=6-9 | pH=10 |\n", - "| :-: | :-: | :-: |\n", - "| 0.5 | 12 | 90 |\n", - "| 5 | 8 | 60 |\n", - "| 10 | 6 | 45 |\n", - "| 15 | 4 | 30 |\n", - "| 20 | 3 | 22 |\n", - "| 25 | 2 | 15 |" - ] - }, - { - "cell_type": "markdown", - "id": "11e1d04c", - "metadata": {}, - "source": [ - "With the CT value, the desired contact time $T_{contact}$ can be calculated from the desired residual chlorine concentration $C_{res}$ (see Section 2.1): \n", - "$$\n", - "T_{contact}[min] = \\frac{CT [\\frac{mg*min}{L}]}{C_{res}[\\frac{mg}{L}]}\n", - "$$\n", - " \n", - "To get the required detention time $T_{DT}$, the desired contact time needs to be corrected by a baffling factor (BF) that accounts for potential short-circuiting:\n", - "\n", - "$$\n", - "T_{DT} [min] = \\frac{T_{contact}}{BF} \n", - "$$\n", - "\n", - "A BF value of 0.7 is typical for the serpentine tubing configuration." - ] - }, - { - "cell_type": "markdown", - "id": "c524fc12", - "metadata": {}, - "source": [ - "Dimensions of the serpentine tubing can then be calculated from the $T_{DT}$:\n", - "\n", - "$$\n", - "T_{DT} [min] = \\frac{L_p}{v} = L_p * \\frac{\\pi*{(\\frac{d_p}{2})^2}}{Q} = (AS*d_p) * \\frac{\\pi*{(\\frac{d_p}{2})^2}}{Q}\n", - "$$\n", - "\n", - "where:\n", - "- $L_p$ and $d_p$ are the length and diameter of the pipe (both in m), respectively\n", - "- AS is the aspect ratio as in $\\frac{L_p}{d_p}$, recommended to be ≧160 by the Colorado Department of Public Health and Environment as in page 16 of this [Baffling Factor Guidance Manual](https://www.colorado.gov/pacific/sites/default/files/CDPHE%20Baffling%20Factor%20Guidance%20Manual.pdf)\n", - "- Q and v are the volumetric flow rate and velocity of the influent stream, respectively" - ] - }, - { - "cell_type": "markdown", - "id": "13b2bd02", - "metadata": {}, - "source": [ - "Solve for $d_p$:\n", - "\n", - "$$\n", - "d_p [m] = (\\frac{4T_{DT}*Q}{\\pi*AS})^{1/3}\n", - "$$" - ] - }, - { - "cell_type": "markdown", - "id": "e28c8246", - "metadata": {}, - "source": [ - "Then we can calculate the amount of material needed:\n", - "\n", - "$$\n", - "V_{PVC} [m^3] = \\pi * L_p * ((\\frac{d_p}{2}+t_{pipe})^2 - d_p^2)\n", - "$$\n", - "\n", - "where $t_{pipe}$ is the thickness of the pipe." - ] - }, - { - "cell_type": "markdown", - "id": "a903a2e1", - "metadata": {}, - "source": [ - "### 1.2. Chlorine tank" - ] - }, - { - "cell_type": "markdown", - "id": "01ed4c90", - "metadata": {}, - "source": [ - "A cylindrical tank will be used for the storage of the NaOCl solution. For a certain refill inteval $t_{refill}$, volume of the storage tank for 15 wt% NaOCl solution will be:\n", - " \n", - "$$\n", - "V_{NaOCl_{sol}}[m^3] = \\frac{M_{Cl_2}[\\frac{kg}{hr}]*t_{refill}[day]*24[\\frac{hr}{day}]*\\frac{MW_{NaOCl}}{MW_{Cl_2}}}{0.15*\\rho_{sol}[\\frac{kg}{m^3}]}\n", - "$$\n", - " \n", - "where:\n", - "- $M_{Cl_2}$ is the mass flowrate of $Cl_2$ (can be calculated from $C_{res}$, refer to Section 2.1.)\n", - "- $MW_{Cl_2}$ and $MW_{NaOCl}$ are the molar mass of $Cl_2$ (70.91 $\\frac{g}{mol}$) and NaOCl (74.44 $\\frac{g}{mol}$), respectively\n", - "- $\\rho_{sol}$ is the density of a 15% NaOCl solution (1200 $\\frac{kg}{m^3}$)" - ] - }, - { - "cell_type": "markdown", - "id": "a585fe1b", - "metadata": {}, - "source": [ - "Given that\n", - "\n", - "$$\n", - "V_{NaOCl_{sol}} = \\frac{\\pi}{4}d_{cyl}^2*h_{cyl} = \\frac{\\pi}{4}d_{cyl}^2*AS*d_{cyl} = \\frac{\\pi}{2}d_{cyl}^3\n", - "$$\n", - "\n", - "The diameter of the cylinder tank needed to hold this volume is:\n", - " \n", - "$$\n", - "d_{cyl} = \\sqrt[3]{\\frac{2*V_{NaOCl_{sol}}}{\\pi}}\n", - "$$\n", - "\n", - "The corresponding PVC volume is:\n", - "\n", - "$$\n", - "V_{wall} = \\pi*h_{cyl}*((d_{cyl}+2*t_{cyl})^2-d_{cyl}^2) = \\pi*AS*d_{cyl}*((d_{cyl}+2*t_{cyl})^2-d_{cyl}^2)\n", - "$$\n", - "\n", - "$$\n", - "V_{floor} = \\pi*(d_{cyl}+2*t_{cyl})^2*t_{cyl}\n", - "$$\n", - "\n", - "$$\n", - "V_{PVC} [m^3] = V_{wall}+V_{floor}\n", - "$$\n", - "\n", - "where:\n", - "- $h_{cyl}$, $d_{cyl}$, and $t_{cyl}$ are the height, inner diameter, and wall thickness of the cylindrical tank (all in m), respectively\n", - "- AS is the aspect ratio as in $\\frac{h_cyl}{d_cyl}$" - ] - }, - { - "cell_type": "markdown", - "id": "73664ea6", - "metadata": {}, - "source": [ - "### 1.3. Pumps" - ] - }, - { - "cell_type": "markdown", - "id": "564e9603", - "metadata": {}, - "source": [ - "For the design of the pumps, we will use the general algorithms in the `WWTpump` class in `QSDsan` (despite of the name, the pump algorithms are not limited to wastewater treatment settings)." - ] - }, - { - "cell_type": "markdown", - "id": "3954fc5f", - "metadata": {}, - "source": [ - "## 2. Process Algorithms " - ] - }, - { - "cell_type": "markdown", - "id": "fcd1a1e5", - "metadata": {}, - "source": [ - "### 2.1. Chlorine dose" - ] - }, - { - "cell_type": "markdown", - "id": "c02f398c", - "metadata": {}, - "source": [ - "Based on the following equation to take into account the amount of chlorine lost to reactions with organics (quantified as the total organic carbon, TOC and ultraviolet absorbance, UVA), we can back-calculate $C_0$ using $C_{res}$:\n", - "\n", - "$$\n", - "C_{res} = -0.8404C_0*ln\\frac{C_0}{C_{res}} - 0.404TOC [\\frac{mg_{C}}{L}]*T_{contact}*(\\frac{C_0}{UVA [1/cm]})^{-0.9108} + C_0\n", - "$$" - ] - }, - { - "cell_type": "markdown", - "id": "5f38c936", - "metadata": {}, - "source": [ - "\n", - " \n", - "- Q1: I'm not sure how TOC and UVA are quantified (e.g., units for them in the equation above)? \n", - "- Q2: Are there two solutions of $C_0$ at a certain $C_{res}$? If so, we probably would want to use the lower value.\n", - " - We might want to double-check the results get from ``scipy`` vs. ``flexsolve``\n", - " \n", - "" - ] - }, - { - "cell_type": "markdown", - "id": "c5a87356", - "metadata": {}, - "source": [ - "With $C_0$ solved, we will know how much NaOCl we need to add to achieve the desired CT:\n", - "\n", - "$$\n", - "M_{Cl_2}[\\frac{kg}{hr}] = Q[\\frac{m^3}{hr}]*C_0[\\frac{g}{m^3}]*\\frac{1[kg]}{1000[g]}\n", - "$$\n", - "\n", - "where:\n", - "- $M_{Cl_2}$ is the mass flowrate of $Cl_2$\n", - "- $Q$ is the volumetric flowrate of the influent" - ] - }, - { - "cell_type": "markdown", - "id": "69b8d85f", - "metadata": {}, - "source": [ - "### 2.2. Pumping energy" - ] - }, - { - "cell_type": "markdown", - "id": "87c78ea5", - "metadata": {}, - "source": [ - "Pumping energy can be calculated based on the flow rate and head pressure/loss as:\n", - "\n", - "$$\n", - "P [kW] = \\frac{mgH}{1000\\eta}\n", - "$$\n", - "\n", - "where:\n", - "- $m$ is mass flow rate in $[\\frac{kg}{s}]$\n", - "- $H$ is the head pressure/loss $[m]$\n", - "- $\\eta$ is the typical pump efficiency (set to 60%)" - ] - }, - { - "cell_type": "markdown", - "id": "a6659f39", - "metadata": {}, - "source": [ - "#### 2.2.1. For the contact zone" - ] - }, - { - "cell_type": "markdown", - "id": "6a7a1048", - "metadata": {}, - "source": [ - "In the case of serpentine tubing, head loss is the sum of the major head loss ($H_f$; due to friction) and minor head loss ($H_m$; due to bends in flow):\n", - "\n", - "$$\n", - "H [m] = H_f + H_m\n", - "$$" - ] - }, - { - "cell_type": "markdown", - "id": "2996a35b", - "metadata": {}, - "source": [ - "For the major head loss, the [Hazen-Williams equation](https://en.wikipedia.org/wiki/Hazen%E2%80%93Williams_equation) can be used (coefficients from [here](https://www.engineeringtoolbox.com/hazen-williams-water-d_797.html)):\n", - "\n", - "$$\n", - "H_f = \\frac{0.2083*(\\frac{100*Q}{C})^{1.852}}{100*d_p^{4.8655}} * L_p\n", - "$$\n", - "\n", - "where C is the roughness coefficient and assumed to be 150 for PVC." - ] - }, - { - "cell_type": "markdown", - "id": "7d9d7b46", - "metadata": {}, - "source": [ - "The minor head loss can be calculated as:\n", - "\n", - "$$\n", - "H_m = \\frac{\\epsilon*v^2}{2g} * N_{bend}\n", - "$$\n", - "\n", - "where:\n", - "- $\\epsilon$ is the minor loss coefficient and assumed to be 1.5\n", - "- $N_{bend}$ is the number of bends can be calculated by dividing the total length by the segment length\n", - "\n", - "$N_{bend}$ can be calculated as\n", - "\n", - "$$\n", - "N_{bend} = \\frac{L_p}{L_{seg}}\n", - "$$\n", - "\n", - "and the segment length $L_{seg}$ can be calculated based on the segment length-to-diameter ratio (recommended to be ≦40 by the Colorado Department of Public Health and Environment as in page 15 of this [Baffling Factor Guidance Manual](https://www.colorado.gov/pacific/sites/default/files/CDPHE%20Baffling%20Factor%20Guidance%20Manual.pdf))." - ] - }, - { - "cell_type": "markdown", - "id": "9f0a999c", - "metadata": {}, - "source": [ - "#### 2.2.2. For the storage tank" - ] - }, - { - "cell_type": "markdown", - "id": "aa0e0f0c", - "metadata": {}, - "source": [ - "For the cylindrical storage tank, there is no minor head loss, therefore the total head loss only comes from the friction loss. However, we need to consider head pressure needed for clorine addition, which is assumed to be 70.3 m. Therefore, the total head needed is\n", - "\n", - "$$\n", - "H = H_f + H_p = \\frac{0.2083*(\\frac{100*Q}{C})^{1.852}}{100*d_{cyl}^{4.8655}} * h_{cyl} + 70.3\n", - "$$" - ] - }, - { - "cell_type": "markdown", - "id": "2f3ff298", - "metadata": {}, - "source": [ - "[Back to top](#top)" - ] - }, - { - "cell_type": "markdown", - "id": "b6a928f2", - "metadata": {}, - "source": [ - "## 3. Unit Classes " - ] - }, - { - "cell_type": "markdown", - "id": "d5691410", - "metadata": {}, - "source": [ - "### 3.1. Contact zone" - ] - }, - { - "cell_type": "markdown", - "id": "bbe29d99", - "metadata": {}, - "source": [ - "For the contact zone, we need to create a new class. Check out the tutorials on `SanUnit` ([basic](https://qsdsan.readthedocs.io/en/latest/tutorials/4_SanUnit_basic.html), [advanced](https://qsdsan.readthedocs.io/en/latest/tutorials/5_SanUnit_advanced.html)) for how to make a new `SanUnit` subclass." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "2081965a", - "metadata": {}, - "outputs": [], - "source": [ - "from warnings import warn\n", - "from math import log, pi, ceil\n", - "from flexsolve import IQ_interpolation\n", - "from qsdsan import SanUnit, Construction\n", - "from qsdsan.sanunits import WWTpump\n", - "\n", - "class ContactZone(SanUnit):\n", - " '''\n", - " Contact zone for water disinfection using chlorine (in the form of sodium hypochlorite, NaOCl).\n", - "\n", - " Parameters\n", - " ----------\n", - " ins : Iterable(obj)\n", - " Influent stream, NaOCl (updated upon unit simulation).\n", - " outs : obj\n", - " Disinfected stream.\n", - " target_CT : float\n", - " Desired CT (concentration*time) for microorganism in min-mg/L.\n", - " C_res : float\n", - " Desired residual concentration of disinfectant in mg/L.\n", - " UVA : float\n", - " Disinfection credit from UVA.\n", - " PVC_thickness : float\n", - " Thickness of the PVC material in m.\n", - "\n", - " References\n", - " ----------\n", - " [1] Jones et al., Life cycle environmental impacts of disinfection technologies\n", - " used in small drinking water systems.\n", - " Environmental Science & Technology, 2018, 52 (5), 2998-3007.\n", - " https://doi.org/10.1021/acs.est.7b04448\n", - " [2] Disinfection Profiling and Benchmarking Technical Guidance Manual.\n", - " U.S. Environmental Protection Agency.\n", - " https://www.epa.gov/system/files/documents/2022-02/disprof_bench_3rules_final_508.pdf\n", - " [3] Baffling Factor Guidance Manual.\n", - " Colorado Department of Public Health and Environment.\n", - " https://www.colorado.gov/pacific/sites/default/files/CDPHE%20Baffling%20Factor%20Guidance%20Manual.pdf\n", - "\n", - " Examples\n", - " --------\n", - " Here we will skip this as we will show how to use it later.\n", - " '''\n", - "\n", - " _N_ins = 2 # influent stream, NaOCl solution\n", - " _N_outs = 1 # disinfected water\n", - " baffling_factor = 0.7\n", - " aspect_ratio = 160 # length over diamteter\n", - " segment_L_to_dia = 40 # segment length to diameter ratio\n", - " C = 150 # roughness coefficient\n", - " epsilon = 1.5 # minor loss coefficient\n", - " pump_eff = 0.6\n", - "\n", - " def __init__(self, ID='', ins=None, outs=(), thermo=None, init_with='WasteStream',\n", - " target_CT=4, # based on the table, set default at T=15°C and pH=6-9\n", - " C_res=10, UVA=1, #!!! need to update\n", - " PVC_thickness=0.005,\n", - " **kwargs):\n", - " SanUnit.__init__(self, ID, ins, outs, thermo, init_with)\n", - " self.target_CT = target_CT\n", - " self.C_res= C_res\n", - " self.UVA = UVA\n", - " self.PVC_thickness = PVC_thickness\n", - " for attr, val in kwargs: setattr(self, kwargs)\n", - "\n", - " # To consider LCA impacts from the construction material\n", - " self.construction = (\n", - " Construction('ContactZone_PVC', linked_unit=self,\n", - " item='PVC', quantity_unit='kg'),\n", - " Construction('ContactZone_SS', linked_unit=self,\n", - " item='StainlessSteel', quantity_unit='kg'),\n", - " )\n", - "\n", - " # Pump\n", - " ID = self.ID\n", - " eff = self.outs[0]\n", - " self.pump = WWTpump(\n", - " ID=ID+'_pump', ins=eff.proxy(eff.ID+'_proxy'),\n", - " pump_type='', # use the generic pump algorithm\n", - " N_pump=1, capacity_factor=1, include_pump_cost=True,\n", - " include_building_cost=False, include_OM_cost=False,\n", - " )\n", - "\n", - " # Target function to solve C_0 -->\n", - " #i first thought the found result was not matching another solver,\n", - " # however the equation just has 2 solutions.\n", - " # We'll probably have to discuss with Eva, which solution to use.\n", - " @staticmethod\n", - " def _C_res_at_C_0(C_0, TOC, contact_time, UVA, C_res):\n", - " C_res2 = -0.8404*C_0*log(C_0/C_res) - 0.404*TOC*contact_time*(C_0/UVA)**(-0.9108) + C_0\n", - " return C_res2-C_res\n", - "\n", - " # Implement process algorithms\n", - " def _run(self):\n", - " inf, naocl = self.ins\n", - " eff, = self.outs\n", - "\n", - " # Calculate contact time and C_0\n", - " TOC = inf.TOC # in mg/L\n", - " UVA = self.UVA\n", - " C_res = self.C_res\n", - " contact_time = self.target_CT / self.C_res\n", - " try:\n", - " C_0 = IQ_interpolation( # in mg/L\n", - " f=self._C_res_at_C_0, x0=C_res, x1=100*C_res, # assume that C_0 won't be >100X of C_res\n", - " ytol=1e-6, args=(TOC, contact_time, UVA, C_res),\n", - " checkbounds=False)\n", - " except:\n", - " warn('Could not find C_0 for the specified values of TOC, contact_time, UVA and C_res.'\n", - " 'C_0 is assumed to be the same as C_res, resullts may be faulty!')\n", - " C_0 = C_res # assumed\n", - "\n", - " C_naocl = C_0/70.91*74.44 # 1-to-1 molar conversion of C_0 (for Cl2) to NaOCl\n", - " naocl.imass['NaOCl'] = m_naocl = inf.F_vol * C_naocl / 1000 # m3*mg/L/1000 = kg\n", - " naocl.imass['Water'] = m_naocl/0.15 - m_naocl\n", - "\n", - " eff.mix_from(self.ins)\n", - " eff.imass['NaOCl'] *= C_res/C_0 # account for the consumed NaOCl\n", - "\n", - " _units = { # units of measure for the design parameters\n", - " 'Pipe diameter': 'm',\n", - " 'Pipe length': 'm',\n", - " 'Total PVC': 'm3',\n", - " 'Pump head': 'm',\n", - " 'Pump stainless steel': 'kg',\n", - " }\n", - "\n", - " # Implement design algorithms\n", - " def _design(self):\n", - " D = self.design_results\n", - "\n", - " # Pipe dimensions\n", - " contact_time=self.target_CT / self.C_res\n", - " t_DT = contact_time / self.baffling_factor # theoretical detention time\n", - " Q = self.F_vol_in # m3/hr\n", - " t_PVC, AS, C = self.PVC_thickness, self.aspect_ratio, self.C\n", - " dia = (4*t_DT*Q/(pi*AS))**(1/3)\n", - " dia_out = dia + 2*t_PVC\n", - " D['Pipe diameter'] = dia\n", - " L_p = D['Pipe length'] = dia * AS\n", - " V_PVC = D['Total PVC'] = pi * L_p * ((dia_out/2)**2-dia**2)\n", - "\n", - " # Pump head\n", - " H_f = 0.2083*(100*Q/C)**1.852/(100*dia**4.8655)*L_p # m\n", - " v = Q/(pi*dia**2)\n", - " N_bend = ceil(L_p/(dia*self.segment_L_to_dia))\n", - " H_m = self.epsilon*v**2/(2*9.81)*N_bend\n", - " H = D['Pump head'] = H_f + H_m\n", - "\n", - " # Pump\n", - " pump = self.pump\n", - " pump.simulate()\n", - " m_ss = D['Pump stainless steel'] = pump.design_results['Pump stainless steel']\n", - " self.power_utility.rate = self.F_mass_in*9.81*H/(1000*self.pump_eff)\n", - "\n", - " #!!! Will need CAPEX/OPEX/impacts of the UVA lights as well\n", - "\n", - " # Construction materials for TEA/LCA\n", - " self.construction[0].quantity = V_PVC\n", - " self.construction[1].quantity = m_ss\n", - " self.add_construction(add_cost=True) # this will add PVC and SS cost\n", - "\n", - " _F_BM_default = {\n", - " 'PVC': 1,\n", - " 'StainlessSteel': 1,\n", - " 'Pump': 1.18*(1+0.007/100),\n", - " }\n", - " def _cost(self):\n", - " C = self.baseline_purchase_costs\n", - " C['Pump'] = self.pump.baseline_purchase_costs['Pump']" - ] - }, - { - "cell_type": "markdown", - "id": "d336dead", - "metadata": {}, - "source": [ - "### 3.2. ChlorineTank" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "1af670fd", - "metadata": {}, - "outputs": [], - "source": [ - "from qsdsan.sanunits import MixTank\n", - "\n", - "class ChlorineTank(MixTank):\n", - " '''\n", - " A subclass of `MixTank` with an auxiliary pump for chlorine storage.\n", - "\n", - " Parameters\n", - " ----------\n", - " ins : Iterable(obj)\n", - " NaOCl, water.\n", - " outs : obj\n", - " NaOCl solution.\n", - " t_refill : float\n", - " Tank refill interval in d.\n", - " head_pressure : float\n", - " Assumed head pressure for the pump in m.\n", - " PVC_thickness : float\n", - " Thickness of the PVC material in m.\n", - "\n", - " See Also\n", - " --------\n", - " `qsdsan.sanunits.MixTank `_\n", - " '''\n", - "\n", - " aspect_ratio = 2 # height over diameter\n", - " C = 150 # roughness coefficient\n", - " pump_eff = 0.6\n", - "\n", - " def __init__(self, ID='', ins=None, outs=(), thermo=None, init_with='WasteStream',\n", - " t_refill=7, head_pressure=70.3, PVC_thickness=0.02, **kwargs):\n", - " MixTank.__init__(self, ID, ins, outs, thermo)\n", - " self.head_pressure = head_pressure\n", - " self.PVC_thickness = PVC_thickness\n", - " for attr, val in kwargs: setattr(self, kwargs)\n", - "\n", - " # To consider LCA impacts from the construction material\n", - " self.construction = (\n", - " Construction('ChlorineTank_PVC', linked_unit=self,\n", - " item='PVC', quantity_unit='kg'),\n", - " Construction('ChlorineTank_SS', linked_unit=self,\n", - " item='StainlessSteel', quantity_unit='kg'),\n", - " )\n", - " eff = self.outs[0]\n", - " self.pump = WWTpump(\n", - " ID=self.ID+'_pump', ins=eff.proxy(eff.ID+'_proxy'),\n", - " pump_type='', # use the generic pump algorithm\n", - " N_pump=1, capacity_factor=1, include_pump_cost=True,\n", - " include_building_cost=False, include_OM_cost=False,\n", - " )\n", - "\n", - " def _run(self):\n", - " naocl, water = self.ins # NaOCl dose will be adjusted when assesmbling the system\n", - " eff = self.outs[0]\n", - " naocl.copy_flow(eff, IDs=('NaOCl',))\n", - " water.copy_flow(eff, IDs=('Water',))\n", - "\n", - "\n", - " _units = { # units of measure for the design parameters\n", - " 'Tank diameter': 'm',\n", - " 'Tank height': 'm',\n", - " 'Total PVC': 'm3',\n", - " 'Pump head': 'm',\n", - " 'Pump stainless steel': 'kg',\n", - " }\n", - " def _design(self):\n", - " MixTank._design(self)\n", - " D = self.design_results\n", - " eff = self.outs[0]\n", - "\n", - " # Cylindrical tank\n", - " V_naocl = self.ins[0].F_vol #!!! the simulated density is ~1.1 g/mL, want to use 1.2?\n", - " AS, t_PVC = self.aspect_ratio, self.PVC_thickness\n", - " dia = 2*((V_naocl/(pi*AS))**(1/3))\n", - " dia_out = dia + 2*self.PVC_thickness\n", - "\n", - " D['Tank diameter'] = dia\n", - " h_cyl = D['Tank height'] = dia * AS\n", - " V_wall = pi*h_cyl*(dia_out**2-dia**2)\n", - " V_floor = pi * dia_out**2 * t_PVC\n", - " V_PVC = D['Total PVC'] = V_wall + V_floor\n", - "\n", - " # Pump\n", - " Q = eff.F_vol\n", - " C = self.C\n", - " pump = self.pump\n", - " H_f = 0.2083*(100*Q/C)**1.852/(100*dia**4.8655) * h_cyl # m\n", - " H_p = self.head_pressure\n", - " D['Pump head'] = H_f + H_p\n", - " pump.simulate()\n", - " m_ss = D['Pump stainless steel'] = pump.design_results['Pump stainless steel']\n", - "\n", - " # # This is if want to use the default algorithms for calculating electricity usage,\n", - " # # it's more conservative (i.e., the efficiency is lower)\n", - " # pump._H_f = H_f * 3.28 # ft\n", - " # pump._H_p = H_p * 3.28 # ft\n", - "\n", - " # Construction materials for TEA/LCA\n", - " self.construction[0].quantity = V_PVC\n", - " self.construction[1].quantity = m_ss\n", - " self.add_construction(add_cost=True) # this will add PVC and SS cost\n", - "\n", - " _F_BM_default = {\n", - " 'PVC': 1,\n", - " 'StainlessSteel': 1,\n", - " 'Pump': 1.18*(1+0.007/100),\n", - " }\n", - " def _cost(self):\n", - " MixTank._cost(self) #!!! this will also add the cost for a stainless steel tank\n", - " pump = self.pump\n", - " self.baseline_purchase_costs['Pump'] = pump.baseline_purchase_costs['Pump']\n", - " H = self.design_results['Pump head']\n", - " self.power_utility.rate += self.F_mass_in*9.81*H/(1000*self.pump_eff)" - ] - }, - { - "cell_type": "markdown", - "id": "476ac60d", - "metadata": {}, - "source": [ - "\n", - " \n", - "- Q3: Density of the simulated NaOCl solution is ~1.1 g/mL instead of 1.2, which will make the design more conservative (since volume is larger), do we want to stick to the 1.2?\n", - " - Related, when calculating tank volume, we typically considers a \"working volume\" factor (<1, our default is 0.8) since we don't want to fill the tank 100% full. So the actual volume will be $\\frac{V_{calculated}}{factor}$, do we want to do the same for this storage tank?\n", - " \n", - "" - ] - }, - { - "cell_type": "markdown", - "id": "62d9849e", - "metadata": {}, - "source": [ - "## 4. System, TEA, and LCA " - ] - }, - { - "cell_type": "markdown", - "id": "650a5f5c", - "metadata": {}, - "source": [ - "Finally it's time to create and simulate the entire system." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "68423ea8", - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/Users/yalinli_cabbi/Library/CloudStorage/OneDrive-Personal/Coding/bst/biosteam/_unit.py:635: RuntimeWarning: the purchase cost item, 'Tanks', has no defined bare-module factor in the 'ChlorineTank.F_BM' dictionary; bare-module factor now has a default value of 1\n", - " warn(warning)\n" - ] - } - ], - "source": [ - "# Identify the components needed for simulation\n", - "import qsdsan as qs\n", - "from qsdsan import Component, Components, set_thermo, WasteStream, \\\n", - " System, SimpleTEA, ImpactIndicator, ImpactItem, StreamImpactItem, LCA\n", - "\n", - "# Set up components to be used in simulation\n", - "kwargs = {\n", - " 'phase': 'l',\n", - " 'particle_size': 'Soluble',\n", - " 'degradability': 'Undegradable',\n", - " 'organic': False,\n", - "}\n", - "H2O = Component('H2O', **kwargs)\n", - "\n", - "kwargs['phase'] = 's'\n", - "kwargs['particle_size'] = 'Particulate'\n", - "NaOCl = Component('NaOCl', **kwargs)\n", - "NaOCl.copy_models_from(qs.Component('HOCl', **kwargs), ['V']) # this gives a rho of ~1.1 g/mL for 15 wt% solution\n", - "\n", - "cmps = Components([H2O, NaOCl])\n", - "cmps.compile()\n", - "cmps.set_alias('H2O', 'Water')\n", - "set_thermo(cmps)\n", - "\n", - "# # Redundant codes, remove after module done\n", - "# HCl = Component('HCl', **kwargs)\n", - "# HOCl = Component('HOCl', **kwargs)\n", - "# NH3 = Component('NH3', **kwargs) # assumed to be liquefied NH3\n", - "# cmps = Components([H2O, NaOCl, HCl, HOCl, NH3])\n", - "# cmps.set_alias('NH3', 'Ammonia')\n", - "# s = WasteStream(Water=85, NaOCl=15, units='kg/hr')\n", - "\n", - "\n", - "# Impact items for LCA, values all made-up now\n", - "GWP = ImpactIndicator('GWP', unit='kg CO2')\n", - "PVC = ImpactItem('PVC', GWP=1, price=1)\n", - "StainlessSteel = ImpactItem('StainlessSteel', GWP=5, price=5)\n", - "NaOCl_item = StreamImpactItem('naocl_item', GWP=2)\n", - "e_item = ImpactItem('e_item', functional_unit='kWh', GWP=1.1)\n", - "\n", - "# Streams\n", - "influent = WasteStream('influent', Water=100, units='kg/hr') # an assumed fake stream\n", - "naocl = WasteStream('naocl', price=1, stream_impact_item=NaOCl_item, units='kg/hr') # price is made-up\n", - "water = WasteStream('water')\n", - "disinfected = WasteStream('disinfected')\n", - "\n", - "U1 = ContactZone('U1', ins=(influent, 'naocl_solution'), outs=disinfected)\n", - "U2 = ChlorineTank('U2', ins=(naocl, water), outs=1-U1)\n", - "\n", - "sys = System('sys', path=(U1, U2))\n", - "sys.simulate()\n", - "\n", - "tea = SimpleTEA(sys, discount_rate=0.5, income_tax=0.3, lifetime=10)\n", - "\n", - "get_e_item_quantity = lambda: (sys.get_electricity_consumption()-sys.get_electricity_production())*tea.lifetime\n", - "lca = LCA(sys, lifetime=tea.lifetime, e_item=get_e_item_quantity)" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "2fd30f57", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "450.8329582699148" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# TEA results\n", - "def get_price():\n", - " price = tea.solve_price(disinfected)\n", - " price = price*disinfected.F_mass/disinfected.F_vol # per m3\n", - " return price\n", - "\n", - "get_price()" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "72440dfe", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "37102.10326916433" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# LCA results\n", - "def get_impact():\n", - " impact = lca.get_total_impacts(time=1)['GWP'] # per hour\n", - " impact = impact/disinfected.F_vol # per m3\n", - " return impact\n", - "\n", - "get_impact()" - ] - }, - { - "cell_type": "markdown", - "id": "8bdf18d1", - "metadata": {}, - "source": [ - "\n", - " \n", - "We need the following data for TEA/LCA (below are ones I can think of now, there might be more)\n", - "- Lifetime of the equipment and TEA/LCA\n", - "- TEA\n", - " - Costs of the unit. If we don't have the cost for the entire unit, we can calculate based on the materials (and we would need the unit costs of PVC/stainless steel), as well as the UVA lights\n", - " - Costs of NaOCl (pure vs. solution?)\n", - " - Electricity usage of UVA lights\n", - " - Other assumptions like discount rate, income tax, etc.\n", - "- LCA\n", - " - Life cycle inventory assessment method (e.g., ReCiPe) with the corresponding characterization factors for materials (PVC, stainless steel, UVA lights), chemicals (NaOCl), and electricity. I only used GWP here as an example, we can do any number of LCIA methods/indicators you like.\n", - " \n", - "" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "33c60ab6", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a83ecc29", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "df2274a3", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "257b7c8a", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a592d8a3", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "7e7599b4", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "275115c2", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "markdown", - "id": "9d9a485a", - "metadata": {}, - "source": [ - "[Back to top](#top)" - ] - } - ], - "metadata": { - "interpreter": { - "hash": "5c4384bbfe0fafd87c455cafafefa588d87617773c75dc9eb96f43c39a856362" - }, - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.8.12" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/docs/source/tutorials/1_Helpful_Basics.ipynb b/docs/source/tutorials/1_Helpful_Basics.ipynb index 81bc154b..1f4b8afa 100644 --- a/docs/source/tutorials/1_Helpful_Basics.ipynb +++ b/docs/source/tutorials/1_Helpful_Basics.ipynb @@ -9,8 +9,8 @@ "\n", "- **Prepared by:**\n", " \n", - " - [Yalin Li](https://qsdsan.readthedocs.io/en/latest/authors/Yalin_Li.html)\n", - " - [Joy Zhang](https://qsdsan.readthedocs.io/en/latest/authors/Joy_Zhang.html)\n", + " - [Yalin Li](https://qsdsan.readthedocs.io/en/latest/CONTRIBUTING.html)\n", + " - [Joy Zhang](https://qsdsan.readthedocs.io/en/latest/CONTRIBUTING.html)\n", " \n", "- **Covered topics:**\n", "\n", @@ -19,9 +19,9 @@ " \n", "- **Video demo:**\n", "\n", - " - [Yalin Li](https://qsdsan.readthedocs.io/en/latest/authors/Yalin_Li.html)\n", + " - [Yalin Li](https://qsdsan.readthedocs.io/en/latest/CONTRIBUTING.html)\n", " \n", - "To run tutorials in your browser, go to this [Binder page](https://mybinder.org/v2/gh/QSD-Group/QSDsan/main?filepath=%2Fdocs%2Fsource%2Ftutorials).\n", + "To run tutorials in your browser, go to this [Binder page](https://mybinder.org/v2/gh/QSD-Group/QSDsan-env/main?urlpath=git-pull%3Frepo%3Dhttps%253A%252F%252Fgithub.com%252FQSD-group%252FQSDsan%26urlpath%3Dtree%252FQSDsan%252Fdocs%252Fsource%252Ftutorials%26branch%3Dmain).\n", " \n", "You can also watch a video demo on [YouTube](https://www.youtube.com/watch?v=g8mXWycdi4E) (subscriptions & likes appreciated!)." ] @@ -690,7 +690,36 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.12" + "version": "3.9.13" + }, + "varInspector": { + "cols": { + "lenName": 16, + "lenType": 16, + "lenVar": 40 + }, + "kernels_config": { + "python": { + "delete_cmd_postfix": "", + "delete_cmd_prefix": "del ", + "library": "var_list.py", + "varRefreshCmd": "print(var_dic_list())" + }, + "r": { + "delete_cmd_postfix": ") ", + "delete_cmd_prefix": "rm(", + "library": "var_list.r", + "varRefreshCmd": "cat(var_dic_list()) " + } + }, + "types_to_exclude": [ + "module", + "function", + "builtin_function_or_method", + "instance", + "_Feature" + ], + "window_display": false } }, "nbformat": 4, diff --git a/docs/source/tutorials/2_Component.ipynb b/docs/source/tutorials/2_Component.ipynb index 3e667b23..078db8c2 100644 --- a/docs/source/tutorials/2_Component.ipynb +++ b/docs/source/tutorials/2_Component.ipynb @@ -8,8 +8,8 @@ "\n", "- **Prepared by:**\n", " \n", - " - [Yalin Li](https://qsdsan.readthedocs.io/en/latest/authors/Yalin_Li.html)\n", - " - [Joy Zhang](https://qsdsan.readthedocs.io/en/latest/authors/Joy_Zhang.html)\n", + " - [Yalin Li](https://qsdsan.readthedocs.io/en/latest/CONTRIBUTING.html)\n", + " - [Joy Zhang](https://qsdsan.readthedocs.io/en/latest/CONTRIBUTING.html)\n", "\n", "- **Covered topics:**\n", "\n", @@ -22,7 +22,7 @@ "\n", " - [Tori Morgan](https://qsdsan.readthedocs.io/en/latest/authors/Tori_Morgan.html)\n", " \n", - "To run tutorials in your browser, go to this [Binder page](https://mybinder.org/v2/gh/QSD-Group/QSDsan/main?filepath=%2Fdocs%2Fsource%2Ftutorials).\n", + "To run tutorials in your browser, go to this [Binder page](https://mybinder.org/v2/gh/QSD-Group/QSDsan-env/main?urlpath=git-pull%3Frepo%3Dhttps%253A%252F%252Fgithub.com%252FQSD-group%252FQSDsan%26urlpath%3Dtree%252FQSDsan%252Fdocs%252Fsource%252Ftutorials%26branch%3Dmain).\n", "\n", "You can also watch a video demo on [YouTube](https://www.youtube.com/watch?v=1OlGsjbqUX8) (subscriptions & likes appreciated!)." ] @@ -1575,6 +1575,35 @@ "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.13" + }, + "varInspector": { + "cols": { + "lenName": 16, + "lenType": 16, + "lenVar": 40 + }, + "kernels_config": { + "python": { + "delete_cmd_postfix": "", + "delete_cmd_prefix": "del ", + "library": "var_list.py", + "varRefreshCmd": "print(var_dic_list())" + }, + "r": { + "delete_cmd_postfix": ") ", + "delete_cmd_prefix": "rm(", + "library": "var_list.r", + "varRefreshCmd": "cat(var_dic_list()) " + } + }, + "types_to_exclude": [ + "module", + "function", + "builtin_function_or_method", + "instance", + "_Feature" + ], + "window_display": false } }, "nbformat": 4, diff --git a/docs/source/tutorials/3_WasteStream.ipynb b/docs/source/tutorials/3_WasteStream.ipynb index 6fb337ee..8434a853 100644 --- a/docs/source/tutorials/3_WasteStream.ipynb +++ b/docs/source/tutorials/3_WasteStream.ipynb @@ -8,8 +8,8 @@ "\n", "- **Prepared by:**\n", " \n", - " - [Yalin Li](https://qsdsan.readthedocs.io/en/latest/authors/Yalin_Li.html)\n", - " - [Joy Zhang](https://qsdsan.readthedocs.io/en/latest/authors/Joy_Zhang.html)\n", + " - [Yalin Li](https://qsdsan.readthedocs.io/en/latest/CONTRIBUTING.html)\n", + " - [Joy Zhang](https://qsdsan.readthedocs.io/en/latest/CONTRIBUTING.html)\n", "\n", "- **Covered topics:**\n", "\n", @@ -20,7 +20,7 @@ "\n", " - [Hannah Lohman](https://qsdsan.readthedocs.io/en/latest/authors/Hannah_Lohman.html)\n", " \n", - "To run tutorials in your browser, go to this [Binder page](https://mybinder.org/v2/gh/QSD-Group/QSDsan/main?filepath=%2Fdocs%2Fsource%2Ftutorials).\n", + "To run tutorials in your browser, go to this [Binder page](https://mybinder.org/v2/gh/QSD-Group/QSDsan-env/main?urlpath=git-pull%3Frepo%3Dhttps%253A%252F%252Fgithub.com%252FQSD-group%252FQSDsan%26urlpath%3Dtree%252FQSDsan%252Fdocs%252Fsource%252Ftutorials%26branch%3Dmain).\n", "\n", "You can also watch a video demo on [YouTube](https://youtu.be/yCOZ0F6E1Sw) (subscriptions & likes appreciated!)." ] @@ -1012,7 +1012,36 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.12" + "version": "3.9.13" + }, + "varInspector": { + "cols": { + "lenName": 16, + "lenType": 16, + "lenVar": 40 + }, + "kernels_config": { + "python": { + "delete_cmd_postfix": "", + "delete_cmd_prefix": "del ", + "library": "var_list.py", + "varRefreshCmd": "print(var_dic_list())" + }, + "r": { + "delete_cmd_postfix": ") ", + "delete_cmd_prefix": "rm(", + "library": "var_list.r", + "varRefreshCmd": "cat(var_dic_list()) " + } + }, + "types_to_exclude": [ + "module", + "function", + "builtin_function_or_method", + "instance", + "_Feature" + ], + "window_display": false } }, "nbformat": 4, diff --git a/docs/source/tutorials/4_SanUnit_basic.ipynb b/docs/source/tutorials/4_SanUnit_basic.ipynb index 6eaf6216..7837358c 100644 --- a/docs/source/tutorials/4_SanUnit_basic.ipynb +++ b/docs/source/tutorials/4_SanUnit_basic.ipynb @@ -8,7 +8,7 @@ "\n", "- **Prepared by:**\n", " \n", - " - [Yalin Li](https://qsdsan.readthedocs.io/en/latest/authors/Yalin_Li.html)\n", + " - [Yalin Li](https://qsdsan.readthedocs.io/en/latest/CONTRIBUTING.html)\n", "\n", "- **Covered topics:**\n", "\n", @@ -17,9 +17,9 @@ " \n", "- **Video demo:**\n", "\n", - " - [Tori Morgan](https://qsdsan.readthedocs.io/en/latest/authors/Tori_Morgan.html)\n", + " - [Tori Morgan](https://qsdsan.readthedocs.io/en/latest/CONTRIBUTING.html)\n", " \n", - "To run tutorials in your browser, go to this [Binder page](https://mybinder.org/v2/gh/QSD-Group/QSDsan/main?filepath=%2Fdocs%2Fsource%2Ftutorials).\n", + "To run tutorials in your browser, go to this [Binder page](https://mybinder.org/v2/gh/QSD-Group/QSDsan-env/main?urlpath=git-pull%3Frepo%3Dhttps%253A%252F%252Fgithub.com%252FQSD-group%252FQSDsan%26urlpath%3Dtree%252FQSDsan%252Fdocs%252Fsource%252Ftutorials%26branch%3Dmain).\n", "\n", "You can also watch a video demo on [YouTube](https://youtu.be/s9zr0rCX3UY) (subscriptions & likes appreciated!)." ] @@ -1113,6 +1113,35 @@ "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.13" + }, + "varInspector": { + "cols": { + "lenName": 16, + "lenType": 16, + "lenVar": 40 + }, + "kernels_config": { + "python": { + "delete_cmd_postfix": "", + "delete_cmd_prefix": "del ", + "library": "var_list.py", + "varRefreshCmd": "print(var_dic_list())" + }, + "r": { + "delete_cmd_postfix": ") ", + "delete_cmd_prefix": "rm(", + "library": "var_list.r", + "varRefreshCmd": "cat(var_dic_list()) " + } + }, + "types_to_exclude": [ + "module", + "function", + "builtin_function_or_method", + "instance", + "_Feature" + ], + "window_display": false } }, "nbformat": 4, diff --git a/docs/source/tutorials/5_SanUnit_advanced.ipynb b/docs/source/tutorials/5_SanUnit_advanced.ipynb index 4ba7fdf3..7c0a4179 100644 --- a/docs/source/tutorials/5_SanUnit_advanced.ipynb +++ b/docs/source/tutorials/5_SanUnit_advanced.ipynb @@ -8,7 +8,7 @@ "\n", "- **Prepared by:**\n", " \n", - " - [Yalin Li](https://qsdsan.readthedocs.io/en/latest/authors/Yalin_Li.html)\n", + " - [Yalin Li](https://qsdsan.readthedocs.io/en/latest/CONTRIBUTING.html)\n", "\n", "- **Covered topics:**\n", "\n", @@ -19,9 +19,9 @@ " \n", "- **Video demo:**\n", "\n", - " - [Hannah Lohman](https://qsdsan.readthedocs.io/en/latest/authors/Hannah_Lohman.html)\n", + " - [Hannah Lohman](https://qsdsan.readthedocs.io/en/latest/CONTRIBUTING.html)\n", " \n", - "To run tutorials in your browser, go to this [Binder page](https://mybinder.org/v2/gh/QSD-Group/QSDsan/main?filepath=%2Fdocs%2Fsource%2Ftutorials).\n", + "To run tutorials in your browser, go to this [Binder page](https://mybinder.org/v2/gh/QSD-Group/QSDsan-env/main?urlpath=git-pull%3Frepo%3Dhttps%253A%252F%252Fgithub.com%252FQSD-group%252FQSDsan%26urlpath%3Dtree%252FQSDsan%252Fdocs%252Fsource%252Ftutorials%26branch%3Dmain).\n", "\n", "You can also watch a video demo on [YouTube](https://youtu.be/G20J2U8g7Dg) (subscriptions & likes appreciated!)." ] @@ -1614,6 +1614,35 @@ "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.13" + }, + "varInspector": { + "cols": { + "lenName": 16, + "lenType": 16, + "lenVar": 40 + }, + "kernels_config": { + "python": { + "delete_cmd_postfix": "", + "delete_cmd_prefix": "del ", + "library": "var_list.py", + "varRefreshCmd": "print(var_dic_list())" + }, + "r": { + "delete_cmd_postfix": ") ", + "delete_cmd_prefix": "rm(", + "library": "var_list.r", + "varRefreshCmd": "cat(var_dic_list()) " + } + }, + "types_to_exclude": [ + "module", + "function", + "builtin_function_or_method", + "instance", + "_Feature" + ], + "window_display": false } }, "nbformat": 4, diff --git a/docs/source/tutorials/6_System.ipynb b/docs/source/tutorials/6_System.ipynb index 875fb145..b95c555a 100644 --- a/docs/source/tutorials/6_System.ipynb +++ b/docs/source/tutorials/6_System.ipynb @@ -8,7 +8,7 @@ "\n", "* **Prepared by:**\n", " \n", - " - [Yalin Li](https://qsdsan.readthedocs.io/en/latest/authors/Yalin_Li.html)\n", + " - [Yalin Li](https://qsdsan.readthedocs.io/en/latest/CONTRIBUTING.html)\n", "\n", "* **Covered topics:**\n", "\n", @@ -17,9 +17,9 @@ "\n", "- **Video demo:**\n", "\n", - " - [Tori Morgan](https://qsdsan.readthedocs.io/en/latest/authors/Tori_Morgan.html)\n", + " - [Tori Morgan](https://qsdsan.readthedocs.io/en/latest/CONTRIBUTING.html)\n", "\n", - "To run tutorials in your browser, go to this [Binder page](https://mybinder.org/v2/gh/QSD-Group/QSDsan/main?filepath=%2Fdocs%2Fsource%2Ftutorials).\n", + "To run tutorials in your browser, go to this [Binder page](https://mybinder.org/v2/gh/QSD-Group/QSDsan-env/main?urlpath=git-pull%3Frepo%3Dhttps%253A%252F%252Fgithub.com%252FQSD-group%252FQSDsan%26urlpath%3Dtree%252FQSDsan%252Fdocs%252Fsource%252Ftutorials%26branch%3Dmain).\n", "\n", "You can also watch a video demo on [YouTube](https://youtu.be/iIx28JkNjQ8) (subscriptions & likes appreciated!)." ] @@ -668,7 +668,36 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.12" + "version": "3.9.13" + }, + "varInspector": { + "cols": { + "lenName": 16, + "lenType": 16, + "lenVar": 40 + }, + "kernels_config": { + "python": { + "delete_cmd_postfix": "", + "delete_cmd_prefix": "del ", + "library": "var_list.py", + "varRefreshCmd": "print(var_dic_list())" + }, + "r": { + "delete_cmd_postfix": ") ", + "delete_cmd_prefix": "rm(", + "library": "var_list.r", + "varRefreshCmd": "cat(var_dic_list()) " + } + }, + "types_to_exclude": [ + "module", + "function", + "builtin_function_or_method", + "instance", + "_Feature" + ], + "window_display": false } }, "nbformat": 4, diff --git a/docs/source/tutorials/7_TEA.ipynb b/docs/source/tutorials/7_TEA.ipynb index 559b9b74..cfcdb544 100755 --- a/docs/source/tutorials/7_TEA.ipynb +++ b/docs/source/tutorials/7_TEA.ipynb @@ -8,7 +8,7 @@ "\n", "* **Prepared by:**\n", " \n", - " - [Yalin Li](https://qsdsan.readthedocs.io/en/latest/authors/Yalin_Li.html)\n", + " - [Yalin Li](https://qsdsan.readthedocs.io/en/latest/CONTRIBUTING.html)\n", "\n", "* **Covered topics:**\n", "\n", @@ -17,9 +17,9 @@ " \n", "- **Video demo:**\n", "\n", - " - [Hannah Lohman](https://qsdsan.readthedocs.io/en/latest/authors/Hannah_Lohman.html)\n", + " - [Hannah Lohman](https://qsdsan.readthedocs.io/en/latest/CONTRIBUTING.html)\n", "\n", - "To run tutorials in your browser, go to this [Binder page](https://mybinder.org/v2/gh/QSD-Group/QSDsan/main?filepath=%2Fdocs%2Fsource%2Ftutorials).\n", + "To run tutorials in your browser, go to this [Binder page](https://mybinder.org/v2/gh/QSD-Group/QSDsan-env/main?urlpath=git-pull%3Frepo%3Dhttps%253A%252F%252Fgithub.com%252FQSD-group%252FQSDsan%26urlpath%3Dtree%252FQSDsan%252Fdocs%252Fsource%252Ftutorials%26branch%3Dmain).\n", "\n", "You can also watch a video demo on [YouTube](https://youtu.be/v3qNNZypTKY) (subscriptions & likes appreciated!)." ] @@ -729,6 +729,35 @@ "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.13" + }, + "varInspector": { + "cols": { + "lenName": 16, + "lenType": 16, + "lenVar": 40 + }, + "kernels_config": { + "python": { + "delete_cmd_postfix": "", + "delete_cmd_prefix": "del ", + "library": "var_list.py", + "varRefreshCmd": "print(var_dic_list())" + }, + "r": { + "delete_cmd_postfix": ") ", + "delete_cmd_prefix": "rm(", + "library": "var_list.r", + "varRefreshCmd": "cat(var_dic_list()) " + } + }, + "types_to_exclude": [ + "module", + "function", + "builtin_function_or_method", + "instance", + "_Feature" + ], + "window_display": false } }, "nbformat": 4, diff --git a/docs/source/tutorials/8_LCA.ipynb b/docs/source/tutorials/8_LCA.ipynb index 0c4819d8..998c86a0 100644 --- a/docs/source/tutorials/8_LCA.ipynb +++ b/docs/source/tutorials/8_LCA.ipynb @@ -8,7 +8,7 @@ "\n", "* **Prepared by:**\n", "\n", - " - [Yalin Li](https://qsdsan.readthedocs.io/en/latest/authors/Yalin_Li.html)\n", + " - [Yalin Li](https://qsdsan.readthedocs.io/en/latest/CONTRIBUTING.html)\n", "\n", "* **Covered topics:**\n", "\n", @@ -19,9 +19,9 @@ "\n", "- **Video demo:**\n", "\n", - " - [Tori Morgan](https://qsdsan.readthedocs.io/en/latest/authors/Tori_Morgan.html)\n", + " - [Tori Morgan](https://qsdsan.readthedocs.io/en/latest/CONTRIBUTING.html)\n", "\n", - "To run tutorials in your browser, go to this [Binder page](https://mybinder.org/v2/gh/QSD-Group/QSDsan/main?filepath=%2Fdocs%2Fsource%2Ftutorials).\n", + "To run tutorials in your browser, go to this [Binder page](https://mybinder.org/v2/gh/QSD-Group/QSDsan-env/main?urlpath=git-pull%3Frepo%3Dhttps%253A%252F%252Fgithub.com%252FQSD-group%252FQSDsan%26urlpath%3Dtree%252FQSDsan%252Fdocs%252Fsource%252Ftutorials%26branch%3Dmain).\n", "\n", "You can also watch a video demo on [YouTube](https://youtu.be/ULmFYO8nTrM) (subscriptions & likes appreciated!)." ] @@ -1148,7 +1148,36 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.12" + "version": "3.9.13" + }, + "varInspector": { + "cols": { + "lenName": 16, + "lenType": 16, + "lenVar": 40 + }, + "kernels_config": { + "python": { + "delete_cmd_postfix": "", + "delete_cmd_prefix": "del ", + "library": "var_list.py", + "varRefreshCmd": "print(var_dic_list())" + }, + "r": { + "delete_cmd_postfix": ") ", + "delete_cmd_prefix": "rm(", + "library": "var_list.r", + "varRefreshCmd": "cat(var_dic_list()) " + } + }, + "types_to_exclude": [ + "module", + "function", + "builtin_function_or_method", + "instance", + "_Feature" + ], + "window_display": false } }, "nbformat": 4, diff --git a/docs/source/tutorials/9_Uncertainty_and_Sensitivity_Analyses.ipynb b/docs/source/tutorials/9_Uncertainty_and_Sensitivity_Analyses.ipynb index ba80082b..97c51a74 100644 --- a/docs/source/tutorials/9_Uncertainty_and_Sensitivity_Analyses.ipynb +++ b/docs/source/tutorials/9_Uncertainty_and_Sensitivity_Analyses.ipynb @@ -9,7 +9,7 @@ "\n", "* **Prepared by:**\n", "\n", - " - [Yalin Li](https://qsdsan.readthedocs.io/en/latest/authors/Yalin_Li.html)\n", + " - [Yalin Li](https://qsdsan.readthedocs.io/en/latest/CONTRIBUTING.html)\n", "\n", "* **Covered topics:**\n", "\n", @@ -18,9 +18,9 @@ " \n", "- **Video demo:**\n", "\n", - " - [Hannah Lohman](https://qsdsan.readthedocs.io/en/latest/authors/Hannah_Lohman.html)\n", + " - [Hannah Lohman](https://qsdsan.readthedocs.io/en/latest/CONTRIBUTING.html)\n", "\n", - "To run tutorials in your browser, go to this [Binder page](https://mybinder.org/v2/gh/QSD-Group/QSDsan/main?filepath=%2Fdocs%2Fsource%2Ftutorials).\n", + "To run tutorials in your browser, go to this [Binder page](https://mybinder.org/v2/gh/QSD-Group/QSDsan-env/main?urlpath=git-pull%3Frepo%3Dhttps%253A%252F%252Fgithub.com%252FQSD-group%252FQSDsan%26urlpath%3Dtree%252FQSDsan%252Fdocs%252Fsource%252Ftutorials%26branch%3Dmain).\n", "\n", "You can also watch a video demo on [YouTube](https://youtu.be/_pIfUEda2jc) (subscriptions & likes appreciated!)." ] @@ -767,7 +767,36 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.12" + "version": "3.9.13" + }, + "varInspector": { + "cols": { + "lenName": 16, + "lenType": 16, + "lenVar": 40 + }, + "kernels_config": { + "python": { + "delete_cmd_postfix": "", + "delete_cmd_prefix": "del ", + "library": "var_list.py", + "varRefreshCmd": "print(var_dic_list())" + }, + "r": { + "delete_cmd_postfix": ") ", + "delete_cmd_prefix": "rm(", + "library": "var_list.r", + "varRefreshCmd": "cat(var_dic_list()) " + } + }, + "types_to_exclude": [ + "module", + "function", + "builtin_function_or_method", + "instance", + "_Feature" + ], + "window_display": false } }, "nbformat": 4, diff --git a/docs/source/tutorials/Tutorial_11.ipynb b/docs/source/tutorials/Tutorial_11.ipynb deleted file mode 100644 index 545c1773..00000000 --- a/docs/source/tutorials/Tutorial_11.ipynb +++ /dev/null @@ -1,401 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "9b7ba848", - "metadata": { - "scrolled": true - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "This tutorial was made with qsdsan v1.2.5 and exposan v1.2.5\n" - ] - } - ], - "source": [ - "import qsdsan as qs, exposan\n", - "print(f'This tutorial was made with qsdsan v{qs.__version__} and exposan v{exposan.__version__}')" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "a31c8f69", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "System: bsm1_sys\n", - "ins...\n", - "[0] wastewater\n", - " phase: 'l', T: 293.15 K, P: 101325 Pa\n", - " flow (kmol/hr): S_I 23.1\n", - " S_S 53.4\n", - " X_I 39.4\n", - " X_S 155\n", - " X_BH 21.7\n", - " S_NH 1.34\n", - " S_ND 0.381\n", - " ... 4.26e+04\n", - "outs...\n", - "[0] effluent\n", - " phase: 'l', T: 293.15 K, P: 101325 Pa\n", - " flow: 0\n", - "[1] WAS\n", - " phase: 'l', T: 293.15 K, P: 101325 Pa\n", - " flow: 0\n" - ] - } - ], - "source": [ - "# Let's load the BSM1 system first\n", - "from exposan import bsm1\n", - "bsm1.load()\n", - "sys = bsm1.sys\n", - "sys.show()" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "5fe1776f", - "metadata": {}, - "outputs": [ - { - "data": { - "image/svg+xml": [ - "\n", - "\n", - "\n", - "\n", - "A1CSTR:c->A2CSTR:c\n", - "\n", - "\n", - "\n", - " ws1\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "A2CSTR:c->O1CSTR:c\n", - "\n", - "\n", - "\n", - " ws3\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "O1CSTR:c->O2CSTR:c\n", - "\n", - "\n", - "\n", - " ws5\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "O2CSTR:c->O3CSTR:c\n", - "\n", - "\n", - "\n", - " ws7\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "O3CSTR:c->A1CSTR:c\n", - "\n", - "\n", - "\n", - " RWW\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "O3CSTR:c->C1Flat bottom circular clarifier:c\n", - "\n", - "\n", - "\n", - " treated\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "C1Flat bottom circular clarifier:c->A1CSTR:c\n", - "\n", - "\n", - "\n", - " RAS\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "C1Flat bottom circular clarifier:c-> effluent:w\n", - "\n", - "\n", - " effluent\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "C1Flat bottom circular clarifier:c-> WAS:w\n", - "\n", - "\n", - " WAS\n", - "\n", - "\n", - "\n", - "\n", - "\n", - " wastewater:e->A1CSTR:c\n", - "\n", - "\n", - " wastewater\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "A1CSTR\n", - "\n", - "\n", - "A1CSTR\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "A2CSTR\n", - "\n", - "\n", - "A2CSTR\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "O1CSTR\n", - "\n", - "\n", - "O1CSTR\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "O2CSTR\n", - "\n", - "\n", - "O2CSTR\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "O3CSTR\n", - "\n", - "\n", - "O3CSTR\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "C1Flat bottom circular clarifier\n", - "\n", - "\n", - "C1Flat bottom circular clarifier\n", - "\n", - "\n", - "\n", - "\n", - "\n", - " wastewater\n", - "\n", - "\n", - "\n", - "\n", - " effluent\n", - "\n", - "\n", - "\n", - "\n", - " WAS\n", - "\n", - "\n", - "\n", - "" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# The BSM1 system is composed of 5 CSTRs in series,\n", - "# followed by a flat-bottom circular clarifier.\n", - "sys.diagram()\n", - "# sys.units" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "98d2662c", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# We can verify that by\n", - "sys.isdynamic" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "e2c64ce0", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{: True,\n", - " : True,\n", - " : True,\n", - " : True,\n", - " : True,\n", - " : True}" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# This is because the system contains at least one dynamic SanUnit\n", - "{u: u.isdynamic for u in sys.units}\n", - "\n", - "# If we disable dynamic simulation, then `simulate` would work as usual\n", - "# sys.isdynamic = False\n", - "# sys.simulate()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d8cc6e48", - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "C:\\Users\\joy_c\\anaconda3\\envs\\tut\\lib\\site-packages\\qsdsan\\sanunits\\_suspended_growth_bioreactor.py:44: NumbaPerformanceWarning: \u001b[1m\u001b[1m'@' is faster on contiguous arrays, called on (array(float64, 1d, A), array(float64, 2d, A))\u001b[0m\u001b[0m\n", - " flow_in = Q_ins @ C_ins / V_arr\n", - "C:\\Users\\joy_c\\anaconda3\\envs\\tut\\lib\\site-packages\\numba\\core\\typing\\npydecl.py:913: NumbaPerformanceWarning: \u001b[1m'@' is faster on contiguous arrays, called on (array(float64, 1d, A), array(float64, 2d, A))\u001b[0m\n", - " warnings.warn(NumbaPerformanceWarning(msg))\n" - ] - } - ], - "source": [ - "# Let's try simulating the BSM1 system from day 0 to day 50\n", - "sys.simulate(t_span=(0, 50), method='BDF', state_reset_hook='reset_cache')\n", - "sys.show()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ba51c0b9", - "metadata": {}, - "outputs": [], - "source": [ - "# This shows the units/streams whose state variables are kept track of\n", - "# during dynamic simulations.\n", - "sys.scope.subjects" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c4c0bdfd", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f1d690bf", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c05808bc", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "37df12a9", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python [conda env:tut]", - "language": "python", - "name": "conda-env-tut-py" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.0" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/docs/source/tutorials/_index.rst b/docs/source/tutorials/_index.rst index 6fa4cbe7..c32424f2 100644 --- a/docs/source/tutorials/_index.rst +++ b/docs/source/tutorials/_index.rst @@ -28,6 +28,7 @@ Topical Tutorials 9_Uncertainty_and_Sensitivity_Analyses 10_Process 11_Dynamic_Simulation + 12_Anaerobic_Digestion_Model_No_1 Additional Resources diff --git a/docs/source/tutorials/_bkm.tsv b/docs/source/tutorials/assets/_bkm.tsv similarity index 100% rename from docs/source/tutorials/_bkm.tsv rename to docs/source/tutorials/assets/_bkm.tsv diff --git a/docs/source/tutorials/assets/adm1.jpg b/docs/source/tutorials/assets/adm1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f3d7dd9826e21d7ee3ffd893f756eb09ce2340c2 GIT binary patch literal 57220 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

6~~`7R!=dSzH|E4~^-pmk1P95Rbhi&6!1O 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 z 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 E0cg66#Q*>R literal 0 HcmV?d00001 diff --git a/qsdsan/_impact_item.py b/qsdsan/_impact_item.py index 98fc476a..395bfb1c 100644 --- a/qsdsan/_impact_item.py +++ b/qsdsan/_impact_item.py @@ -414,7 +414,7 @@ def load_from_file(cls, path_or_dict, index_col=None): This Excel should have multiple sheets: - The "info" sheet should have three columns: "ID" (e.g., Cement) \ - "functional_unit" (e.g., kg), and "kind" ("ImpactItem" or "StreamImpactItem") + "functional_unit" (e.g., kg), and "kind" ("ImpactItem" or "StreamImpactItem") \ of different impact items. - The remaining sheets should contain characterization factors of \ diff --git a/qsdsan/sanunits/_abstract.py b/qsdsan/sanunits/_abstract.py index 8a99237e..0b29272e 100644 --- a/qsdsan/sanunits/_abstract.py +++ b/qsdsan/sanunits/_abstract.py @@ -58,10 +58,11 @@ class Mixer(SanUnit, BSTMixer): _graphics = BSTMixer._graphics def __init__(self, ID='', ins=None, outs=(), thermo=None, init_with='WasteStream', F_BM_default=None, isdynamic=False, - rigorous=False): + rigorous=False, conserve_phases=False): SanUnit.__init__(self, ID, ins, outs, thermo, init_with, F_BM_default=F_BM_default, isdynamic=isdynamic) self.rigorous = rigorous + self.conserve_phases = conserve_phases @property diff --git a/qsdsan/sanunits/_activated_sludge_process.py b/qsdsan/sanunits/_activated_sludge_process.py index 3996cd1b..21659a81 100644 --- a/qsdsan/sanunits/_activated_sludge_process.py +++ b/qsdsan/sanunits/_activated_sludge_process.py @@ -26,6 +26,10 @@ from ..equipments import Blower, GasPiping from ..utils import auom, calculate_excavation_volume + +from qsdsan.utils import ospath, time_printer, load_data, get_SRT, get_oxygen_heterotrophs, get_oxygen_autotrophs, get_airflow, get_P_blower + + __all__ = ('ActivatedSludgeProcess','TreatmentTrains',) _ft2_to_m2 = auom('ft2').conversion_factor('m2') @@ -367,6 +371,7 @@ def _run(self): 'Pump pipe stainless steel': 'kg', 'Pump stainless steel': 'kg', } + def _design(self): D = self.design_results D['HRT'] = self.HRT @@ -1093,8 +1098,23 @@ def _design(self): D['Q recirculation'] = self.Q_recirculation # Blower and gas piping (taken from 'ActivatedSludgeProcess' SanUnit) - Q_air_design = self.Q_air_design # in m3 - air_cfm = auom('m3/hr').convert(Q_air_design, 'cfm') + + + # oxygen_autotroph = get_oxygen_autotrophs(inf.F_vol*24, inf.COD, eff_sCOD, + # inf_TKN, SRT=srt, Y_H=0.625, U_AUT=1.0, + # b_H=0.4, b_AUT=0.15, f_d=0.1, SF_DO=1.25) + + # oxygen_autotroph = get_oxygen_autotrophs(inf.F_vol*24, inf.COD, eff_sCOD, + # inf_TKN, SRT=srt, Y_H=0.625, U_AUT=1.0, + # b_H=0.4, b_AUT=0.15, f_d=0.1, SF_DO=1.25) + + # airflow = get_airflow(oxygen_heterotrophs = oxygen_heterotroph, oxygen_autotrophs = oxygen_autotroph, + # oxygen_transfer_efficiency = 15) # in m3/min + + + + Q_air_design = self.Q_air_design # in m3/min + air_cfm = auom('m3/min').convert(Q_air_design, 'cfm') blower, piping = self.equipments blower.N_reactor = piping.N_reactor = D['Number of trains'] diff --git a/qsdsan/sanunits/_suspended_growth_bioreactor.py b/qsdsan/sanunits/_suspended_growth_bioreactor.py index 09c8e6b2..6f34e06b 100644 --- a/qsdsan/sanunits/_suspended_growth_bioreactor.py +++ b/qsdsan/sanunits/_suspended_growth_bioreactor.py @@ -80,12 +80,12 @@ class CSTR(SanUnit): V_max : float Designed volume, in [m^3]. The default is 1000. - W_tank : float - The design width of the tank, in [m]. The default is 6.4 m (21 ft). [1, Yalin's adaptation of code] - D_tank : float - The design depth of the tank in [m]. The default is 3.65 m (12 ft). [1, Yalin's adaptation of code] - freeboard : float - Freeboard added to the depth of the reactor tank, [m]. The default is 0.61 m (2 ft). [1, Yalin's adaptation of code] + # W_tank : float + # The design width of the tank, in [m]. The default is 6.4 m (21 ft). [1, Yalin's adaptation of code] + # D_tank : float + # The design depth of the tank in [m]. The default is 3.65 m (12 ft). [1, Yalin's adaptation of code] + # freeboard : float + # Freeboard added to the depth of the reactor tank, [m]. The default is 0.61 m (2 ft). [1, Yalin's adaptation of code] aeration : float or :class:`Process`, optional Aeration setting. Either specify a targeted dissolved oxygen concentration @@ -129,12 +129,12 @@ def __init__(self, ID='', ins=None, outs=(), split=None, thermo=None, self._mixed = WasteStream() self.split = split - # Design parameters - self._W_tank = W_tank - self._D_tank = D_tank - self._freeboard = freeboard - self._t_wall = t_wall - self._t_slab = t_slab + # # Design parameters + # self._W_tank = W_tank + # self._D_tank = D_tank + # self._freeboard = freeboard + # self._t_wall = t_wall + # self._t_slab = t_slab # for attr, value in kwargs.items(): # setattr(self, attr, value) @@ -148,57 +148,57 @@ def V_max(self): def V_max(self, Vm): self._V_max = Vm - @property - def W_tank(self): - '''[float] The design width of the tank, in m.''' - return self._W_tank - - @W_tank.setter - def W_tank(self, W_tank): - self._W_tank = W_tank + # @property + # def W_tank(self): + # '''[float] The design width of the tank, in m.''' + # return self._W_tank + + # @W_tank.setter + # def W_tank(self, W_tank): + # self._W_tank = W_tank - @property - def D_tank(self): - '''[float] The design depth of the tank, in m.''' - return self._D_tank - - @D_tank.setter - def D_tank(self, D_tank): - self._D_tank = D_tank + # @property + # def D_tank(self): + # '''[float] The design depth of the tank, in m.''' + # return self._D_tank + + # @D_tank.setter + # def D_tank(self, D_tank): + # self._D_tank = D_tank - @property - def freeboard(self): - '''[float] Freeboard added to the depth of the reactor tank, [m].''' - return self._freeboard + # @property + # def freeboard(self): + # '''[float] Freeboard added to the depth of the reactor tank, [m].''' + # return self._freeboard - @freeboard.setter - def freeboard(self, i): - self._freeboard = i + # @freeboard.setter + # def freeboard(self, i): + # self._freeboard = i - @property - def t_wall(self): - ''' - [float] Thickness of the wall concrete, [m]. - default to be minimum of 1 ft with 1 in added for every ft of depth over 12 ft. - ''' - D_tank = self.D_tank*39.37 # m to inches - return self._t_wall or (1 + max(D_tank - 12, 0)/12)*0.3048 # from feet to m + # @property + # def t_wall(self): + # ''' + # [float] Thickness of the wall concrete, [m]. + # default to be minimum of 1 ft with 1 in added for every ft of depth over 12 ft. + # ''' + # D_tank = self.D_tank*39.37 # m to inches + # return self._t_wall or (1 + max(D_tank - 12, 0)/12)*0.3048 # from feet to m - @t_wall.setter - def t_wall(self, i): - self._t_wall = i - - @property - def t_slab(self): - ''' - [float] Concrete slab thickness, [m], - default to be 2 in thicker than the wall thickness. - ''' - return self._t_slab or (self.t_wall + 2/12)*0.3048 # from feet to m + # @t_wall.setter + # def t_wall(self, i): + # self._t_wall = i + + # @property + # def t_slab(self): + # ''' + # [float] Concrete slab thickness, [m], + # default to be 2 in thicker than the wall thickness. + # ''' + # return self._t_slab or (self.t_wall + 2/12)*0.3048 # from feet to m - @t_slab.setter - def t_slab(self, i): - self._t_slab = i + # @t_slab.setter + # def t_slab(self, i): + # self._t_slab = i @property def aeration(self): @@ -364,49 +364,47 @@ def dy_dt(t, QC_ins, QC, dQC_ins): self._ODE = dy_dt - _units = { - 'Tank volume': 'm3', - 'Tank width': 'm', - 'Tank depth': 'm', - 'Tank length': 'm', - - 'Volume of concrete wall': 'm3', - 'Volume of concrete slab': 'm3' - } - - def _design(self): - self._mixed.mix_from(self.ins) - mixed = self._mixed - D = self.design_results - - D['Tank volume'] = self.V_max - D['Tank width'] = self.W_tank - D['Tank depth'] = self.D_tank - D['Tank length'] = D['Volume']/(D['Tank width']*D['Tank depth']) + # _units = { + # 'Tank volume': 'm3', + # 'Tank width': 'm', + # 'Tank depth': 'm', + # 'Tank length': 'm', + # 'Volume of concrete wall': 'm3', + # 'Volume of concrete slab': 'm3' + # } + + # def _design(self): + # self._mixed.mix_from(self.ins) + # mixed = self._mixed + # D = self.design_results - t_wall, t_slab = self.t_wall, self.t_slab - t = t_wall + t_slab - D_tot = D['Tank depth'] + self.freeboard + # D['Tank volume'] = self.V_max + # D['Tank width'] = self.W_tank + # D['Tank depth'] = self.D_tank + # D['Tank length'] = D['Volume']/(D['Tank width']*D['Tank depth']) - # get volume of wall concrete - VWC = 2*((D['Tank length'] + 2*t_wall)*t_wall*D_tot) + 2*(D['Tank width']*t_wall*D_tot) + # t_wall, t_slab = self.t_wall, self.t_slab + # t = t_wall + t_slab + # D_tot = D['Tank depth'] + self.freeboard - # get volume of slab concrete - VSC = (D['Tank length'] + 2*t_wall)*(D['Tank width'] + 2*t_wall)*t + # # get volume of wall concrete + # VWC = 2*((D['Tank length'] + 2*t_wall)*t_wall*D_tot) + 2*(D['Tank width']*t_wall*D_tot) + # # get volume of slab concrete + # VSC = (D['Tank length'] + 2*t_wall)*(D['Tank width'] + 2*t_wall)*t - D['Volume of concrete wall'] = VWC - D['Volume of concrete slab'] = VSC + # D['Volume of concrete wall'] = VWC + # D['Volume of concrete slab'] = VSC - def _cost(self): + # def _cost(self): - self._mixed.mix_from(self.ins) + # self._mixed.mix_from(self.ins) - D = self.design_results - C = self.baseline_purchase_costs + # D = self.design_results + # C = self.baseline_purchase_costs - # Construction of concrete and stainless steel walls - C['Wall concrete'] = D['Volume of concrete wall']*self.wall_concrete_unit_cost - C['Slab concrete'] = D['Volume of concrete slab']*self.slab_concrete_unit_cost + # # Construction of concrete and stainless steel walls + # C['Wall concrete'] = D['Volume of concrete wall']*self.wall_concrete_unit_cost + # C['Slab concrete'] = D['Volume of concrete slab']*self.slab_concrete_unit_cost #%% class BatchExperiment(SanUnit): diff --git a/requirements.txt b/requirements.txt index d58c4abe..37a3c2d7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,7 +13,4 @@ sphinx-copybutton sphinx-design furo nbsphinx -pandoc -ipywidgets -jupyterlab-widgets -widgetsnbextension \ No newline at end of file +pandoc \ No newline at end of file diff --git a/setup.py b/setup.py index 62bbe8bd..57034c09 100644 --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ setup( name='qsdsan', packages=['qsdsan'], - version='1.3.0', + version='1.3.1', license='UIUC', author='Quantitative Sustainable Design Group', author_email='quantitative.sustainable.design@gmail.com', From cccb4eb95bbd6c3f5255c2ba247b8bdff0f64b4b Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Wed, 15 Nov 2023 12:07:06 -0800 Subject: [PATCH 225/424] consolidate thickener parameter update --- qsdsan/sanunits/_sludge_treatment.py | 65 +++++++++++++--------------- 1 file changed, 29 insertions(+), 36 deletions(-) diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py index 565e5534..9898434b 100644 --- a/qsdsan/sanunits/_sludge_treatment.py +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -39,6 +39,7 @@ } default_F_BM.update(default_WWTpump_F_BM) +#%% Thickener class Thickener(SanUnit): """ @@ -169,6 +170,9 @@ def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, self.F_BM.update(F_BM) self._mixed = WasteStream(f'{ID}_mixed') self._sludge = self.outs[0].copy(f'{ID}_sludge') + self._thickener_factor = None + self._thinning_factor = None + self._Qu_factor = None @property def thickener_perc(self): @@ -212,36 +216,32 @@ def TSS_removal_perc(self, TSS_rmv): @property def thickener_factor(self): - self._mixed.mix_from(self.ins) - inf = self._mixed - _cal_thickener_factor = self._cal_thickener_factor - if not self.ins: return - elif inf.isempty(): return - else: - TSS_in = inf.get_TSS() - thickener_factor = _cal_thickener_factor(TSS_in) - return thickener_factor + if self._thickener_factor is None: + self._mixed.mix_from(self.ins) + inf = self._mixed + _cal_thickener_factor = self._cal_thickener_factor + if not self.ins: return + elif inf.isempty(): return + else: + TSS_in = inf.get_TSS() + self._thickener_factor = _cal_thickener_factor(TSS_in) + return self._thickener_factor @property def thinning_factor(self): - self._mixed.mix_from(self.ins) - inf = self._mixed - TSS_in = inf.get_TSS() - _cal_thickener_factor = self._cal_thickener_factor - thickener_factor = _cal_thickener_factor(TSS_in) - _cal_parameters = self._cal_parameters - Qu_factor, thinning_factor = _cal_parameters(thickener_factor) - return thinning_factor + if self._thinning_factor is None: + self._Qu_factor, self._thinning_factor = self._cal_Qu_fthin(self.thickener_factor) + return self._thinning_factor def _cal_thickener_factor(self, TSS_in): if TSS_in > 0: thickener_factor = self._tp*10000/TSS_in - if thickener_factor<1: - thickener_factor=1 + if thickener_factor < 1: thickener_factor = 1 return thickener_factor - else: raise ValueError(f'Influent TSS is not valid ({TSS_in:.2f} mg/L).') + else: + raise ValueError(f'Influent TSS is not valid: ({TSS_in:.2f} mg/L).') - def _cal_parameters(self, thickener_factor): + def _cal_Qu_fthin(self, thickener_factor): if thickener_factor<1: Qu_factor = 1 thinning_factor=0 @@ -251,19 +251,12 @@ def _cal_parameters(self, thickener_factor): return Qu_factor, thinning_factor def _update_parameters(self): - # Thickener_factor, Thinning_factor, and Qu_factor need to be - # updated again and again. while dynamic simulations - + # updated again and again. while dynamic simulations cmps = self.components - TSS_in = np.sum(self._state[:-1]*cmps.i_mass*cmps.x) - _cal_thickener_factor = self._cal_thickener_factor - self.updated_thickener_factor = _cal_thickener_factor(TSS_in) - _cal_parameters = self._cal_parameters - - updated_thickener_factor = self.updated_thickener_factor - self.updated_Qu_factor, self.updated_thinning_factor = _cal_parameters(updated_thickener_factor) + self._thickener_factor = f_thick = self._cal_thickener_factor(TSS_in) + self._Qu_factor, self._thinning_factor = self._cal_Qu_fthin(f_thick) def _run(self): self._mixed.mix_from(self.ins) @@ -318,9 +311,9 @@ def _update_state(self): # here they are split by concentration. Therefore, the split factors are different. # Updated intrinsic modelling parameters are used for dynamic simulation - thickener_factor = self.updated_thickener_factor - thinning_factor = self.updated_thinning_factor - Qu_factor = self.updated_Qu_factor + thickener_factor = self.thickener_factor + thinning_factor = self.thinning_factor + Qu_factor = self._Qu_factor cmps = self.components # For sludge, the particulate concentrations are multipled by thickener factor, and @@ -629,7 +622,7 @@ def _cost(self): self.power_utility.rate += pumping self.power_utility.rate += scraper_power -# %% +#%% Centrifuge # Asign a bare module of 1 to all @@ -872,7 +865,7 @@ def _cost(self): pumping = pumping*D['Number of pumps'] self.power_utility.rate += pumping self.power_utility.rate += conveyor_power -# %% +#%% Incinerator class Incinerator(SanUnit): From a88287e885bda5155e92694a6543e4dd3487870a Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Wed, 15 Nov 2023 12:07:22 -0800 Subject: [PATCH 226/424] debug CSTR design --- qsdsan/_process.py | 3 ++ .../sanunits/_suspended_growth_bioreactor.py | 47 ++++++++++--------- 2 files changed, 27 insertions(+), 23 deletions(-) diff --git a/qsdsan/_process.py b/qsdsan/_process.py index d795d362..8343de1a 100644 --- a/qsdsan/_process.py +++ b/qsdsan/_process.py @@ -690,10 +690,13 @@ def _parse_rate_eq(self, eq): def _rate_eq2func(self): var_kw = self._components.IDs var = list(symbols(var_kw)) + [symbols(p) for p in self._parameters.keys()] + # var = list(symbols(self._components.IDs+('Q',))) \ + # + [symbols(p) for p in self._parameters.keys()] lamb = lambdify(var, self._rate_equation, 'numpy') def f(state_arr, params={}): states = dict(zip(var_kw, state_arr)) return lamb(**states, **params) + # return lamb(*state_arr, **params) self.kinetics(function=f, parameters=self.parameters) def _normalize_stoichiometry(self, new_ref): diff --git a/qsdsan/sanunits/_suspended_growth_bioreactor.py b/qsdsan/sanunits/_suspended_growth_bioreactor.py index 09c8e6b2..ad8e4813 100644 --- a/qsdsan/sanunits/_suspended_growth_bioreactor.py +++ b/qsdsan/sanunits/_suspended_growth_bioreactor.py @@ -375,38 +375,39 @@ def dy_dt(t, QC_ins, QC, dQC_ins): } def _design(self): - self._mixed.mix_from(self.ins) - mixed = self._mixed - D = self.design_results + pass + # self._mixed.mix_from(self.ins) + # # mixed = self._mixed + # D = self.design_results - D['Tank volume'] = self.V_max - D['Tank width'] = self.W_tank - D['Tank depth'] = self.D_tank - D['Tank length'] = D['Volume']/(D['Tank width']*D['Tank depth']) + # D['Tank volume'] = V = self.V_max + # D['Tank width'] = W = self.W_tank + # D['Tank depth'] = depth = self.D_tank + # D['Tank length'] = L = V/(W*depth) - t_wall, t_slab = self.t_wall, self.t_slab - t = t_wall + t_slab - D_tot = D['Tank depth'] + self.freeboard + # t_wall, t_slab = self.t_wall, self.t_slab + # t = t_wall + t_slab + # D_tot = depth + self.freeboard - # get volume of wall concrete - VWC = 2*((D['Tank length'] + 2*t_wall)*t_wall*D_tot) + 2*(D['Tank width']*t_wall*D_tot) + # # get volume of wall concrete + # VWC = 2*((L + 2*t_wall)*t_wall*D_tot) + 2*(W*t_wall*D_tot) - # get volume of slab concrete - VSC = (D['Tank length'] + 2*t_wall)*(D['Tank width'] + 2*t_wall)*t + # # get volume of slab concrete + # VSC = (L + 2*t_wall)*(W + 2*t_wall)*t - D['Volume of concrete wall'] = VWC - D['Volume of concrete slab'] = VSC + # D['Volume of concrete wall'] = VWC + # D['Volume of concrete slab'] = VSC def _cost(self): - - self._mixed.mix_from(self.ins) + pass + # self._mixed.mix_from(self.ins) - D = self.design_results - C = self.baseline_purchase_costs + # D = self.design_results + # C = self.baseline_purchase_costs - # Construction of concrete and stainless steel walls - C['Wall concrete'] = D['Volume of concrete wall']*self.wall_concrete_unit_cost - C['Slab concrete'] = D['Volume of concrete slab']*self.slab_concrete_unit_cost + # # Construction of concrete and stainless steel walls + # C['Wall concrete'] = D['Volume of concrete wall']*self.wall_concrete_unit_cost + # C['Slab concrete'] = D['Volume of concrete slab']*self.slab_concrete_unit_cost #%% class BatchExperiment(SanUnit): From 82832fdd8397858181d9678102da1bde3ff52b32 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Wed, 15 Nov 2023 13:28:22 -0800 Subject: [PATCH 227/424] debug thickener --- qsdsan/sanunits/_sludge_treatment.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py index 9898434b..8886e028 100644 --- a/qsdsan/sanunits/_sludge_treatment.py +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -341,9 +341,9 @@ def _update_dstate(self): # here they are split by concentration. Therefore, the split factors are different. # Updated intrinsic modelling parameters are used for dynamic simulation - thickener_factor = self.updated_thickener_factor - thinning_factor = self.updated_thinning_factor - Qu_factor = self.updated_Qu_factor + thickener_factor = self.thickener_factor + thinning_factor = self.thinning_factor + Qu_factor = self._Qu_factor cmps = self.components # For sludge, the particulate concentrations are multipled by thickener factor, and From 52cb8c4b15b8fa601c9d3635f1ed2f3597b2a020 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Wed, 15 Nov 2023 15:05:08 -0800 Subject: [PATCH 228/424] Update _clarifier.py --- qsdsan/sanunits/_clarifier.py | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index 8461ce3f..49f4a434 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -40,19 +40,7 @@ 'Pump stainless steel': 15, } - -def _settling_flux(X, v_max, v_max_practical, X_min, rh, rp, n0): - X_star = npmax(X-X_min, n0) - v = npmin(v_max_practical, v_max*(npexp(-rh*X_star) - npexp(-rp*X_star))) - return X*npmax(v, n0) - -# from math import exp -# def _settling_flux(X, v_max, v_max_practical, X_min, rh, rp, n0): -# X_star = max(X-X_min, 0) -# v = min(v_max_practical, v_max*(exp(-rh*X_star) - exp(-rp*X_star))) -# return X*max(v, 0) - -# Asign a bare module of 1 to all +# Assign a bare module of 1 to all default_F_BM = { 'Wall concrete': 1., 'Slab concrete': 1., @@ -63,6 +51,13 @@ def _settling_flux(X, v_max, v_max_practical, X_min, rh, rp, n0): } default_F_BM.update(default_WWTpump_F_BM) +#%% Takács Clarifer + +def _settling_flux(X, v_max, v_max_practical, X_min, rh, rp, n0): + X_star = npmax(X-X_min, n0) + v = npmin(v_max_practical, v_max*(npexp(-rh*X_star) - npexp(-rp*X_star))) + return X*npmax(v, n0) + class FlatBottomCircularClarifier(SanUnit): """ A flat-bottom circular clarifier with a simple 1-dimensional @@ -1320,7 +1315,7 @@ def yt(t, QC_ins, dQC_ins): # self.power_utility.consumption += pumping # self.power_utility.consumption += scraper_power - +#%% # Assign a bare module of 1 to all default_F_BM = { 'Wall concrete': 1., From c948da434312ac1be66bb2f6a803801a51e4e4e0 Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Wed, 6 Dec 2023 13:36:14 -0600 Subject: [PATCH 229/424] Discarding scraper Discarding scraper capital cost because not sure whether the base flow is flow of sludge or effluent. Discarding scraper power because the scaling exponent is not correct. --- qsdsan/sanunits/_clarifier.py | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index 49f4a434..792dabc5 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -714,12 +714,13 @@ def _cost(self): # Scraper # Source: https://www.alibaba.com/product-detail/Peripheral-driving-clarifier-mud-scraper-waste_1600891102019.html?spm=a2700.details.0.0.47ab45a4TP0DLb - base_cost_scraper = 2500 - base_flow_scraper = 1 # in m3/hr (!!! Need to know whether this is for solids or influent !!!) + # base_cost_scraper = 2500 + # base_flow_scraper = 1 # in m3/hr (!!! Need to know whether this is for solids or influent !!!) clarifier_flow = D['Volumetric flow']/24 # in m3/hr - C['Scraper'] = D['Number of clarifiers']*base_cost_scraper*(clarifier_flow/base_flow_scraper)**0.6 - base_power_scraper = 2.75 # in kW - scraper_power = D['Number of clarifiers']*base_power_scraper*(clarifier_flow/base_flow_scraper)**0.6 + # C['Scraper'] = D['Number of clarifiers']*base_cost_scraper*(clarifier_flow/base_flow_scraper)**0.6 + # base_power_scraper = 2.75 # in kW + # THE EQUATION BELOW IS NOT CORRECT TO SCALE SCRAPER POWER REQUIREMENTS + # scraper_power = D['Number of clarifiers']*base_power_scraper*(clarifier_flow/base_flow_scraper)**0.6 # v notch weir # Source: https://www.alibaba.com/product-detail/50mm-Tube-Settler-Media-Modules-Inclined_1600835845218.html?spm=a2700.galleryofferlist.normal_offer.d_title.69135ff6o4kFPb @@ -762,7 +763,7 @@ def _cost(self): pumping = pumping*D['Number of clarifiers'] self.power_utility.rate += pumping - self.power_utility.consumption += scraper_power + # self.power_utility.consumption += scraper_power # %% @@ -1879,12 +1880,15 @@ def _cost(self): # Scraper # Source: https://www.alibaba.com/product-detail/Peripheral-driving-clarifier-mud-scraper-waste_1600891102019.html?spm=a2700.details.0.0.47ab45a4TP0DLb - base_cost_scraper = 2500 - base_flow_scraper = 1 # in m3/hr (!!! Need to know whether this is for solids or influent !!!) + # base_cost_scraper = 2500 + # base_flow_scraper = 1 # in m3/hr (!!! Need to know whether this is for solids or influent !!!) clarifier_flow = D['Volumetric flow']/24 - C['Scraper'] = D['Number of clarifiers']*base_cost_scraper*(clarifier_flow/base_flow_scraper)**0.6 - base_power_scraper = 2.75 # in kW - scraper_power = D['Number of clarifiers']*base_power_scraper*(clarifier_flow/base_flow_scraper)**0.6 + + # C['Scraper'] = D['Number of clarifiers']*base_cost_scraper*(clarifier_flow/base_flow_scraper)**0.6 + + # base_power_scraper = 2.75 # in kW + # THE EQUATION BELOW IS NOT CORRECT TO SCALE SCRAPER POWER REQUIREMENTS + # scraper_power = D['Number of clarifiers']*base_power_scraper*(clarifier_flow/base_flow_scraper)**0.6 # v notch weir # Source: https://www.alibaba.com/product-detail/50mm-Tube-Settler-Media-Modules-Inclined_1600835845218.html?spm=a2700.galleryofferlist.normal_offer.d_title.69135ff6o4kFPb @@ -1925,4 +1929,4 @@ def _cost(self): pumping = pumping*D['Number of clarifiers'] self.power_utility.rate += pumping - self.power_utility.rate += scraper_power + # self.power_utility.rate += scraper_power From 5479df5997e3de51b256209b1c882da918119b33 Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Wed, 6 Dec 2023 15:40:03 -0600 Subject: [PATCH 230/424] Removed scraper, edited capital of centrifuge Removed scraper from thickener. Changed design of centrifuge since it is an equipment. --- qsdsan/sanunits/_sludge_treatment.py | 81 ++++++++++++++++------------ 1 file changed, 46 insertions(+), 35 deletions(-) diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py index 8886e028..d5c75210 100644 --- a/qsdsan/sanunits/_sludge_treatment.py +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -574,12 +574,13 @@ def _cost(self): # Scraper # Source: https://www.alibaba.com/product-detail/Peripheral-driving-clarifier-mud-scraper-waste_1600891102019.html?spm=a2700.details.0.0.47ab45a4TP0DLb - base_cost_scraper = 2500 - base_flow_scraper = 1 # in m3/hr (!!! Need to know whether this is for solids or influent !!!) + # base_cost_scraper = 2500 + # base_flow_scraper = 1 # in m3/hr (!!! Need to know whether this is for solids or influent !!!) thickener_flow = mixed.get_total_flow('m3/hr')/D['Number of thickeners'] - C['Scraper'] = D['Number of thickeners']*base_cost_scraper*(thickener_flow/base_flow_scraper)**0.6 - base_power_scraper = 2.75 # in kW - scraper_power = D['Number of thickeners']*base_power_scraper*(thickener_flow/base_flow_scraper)**0.6 + # C['Scraper'] = D['Number of thickeners']*base_cost_scraper*(thickener_flow/base_flow_scraper)**0.6 + # base_power_scraper = 2.75 # in kW + # THE EQUATION BELOW IS NOT CORRECT TO SCALE SCRAPER POWER REQUIREMENTS + # scraper_power = D['Number of thickeners']*base_power_scraper*(thickener_flow/base_flow_scraper)**0.6 # v notch weir # Source: https://www.alibaba.com/product-detail/50mm-Tube-Settler-Media-Modules-Inclined_1600835845218.html?spm=a2700.galleryofferlist.normal_offer.d_title.69135ff6o4kFPb @@ -620,7 +621,7 @@ def _cost(self): pumping = pumping*D['Number of thickeners'] self.power_utility.rate += pumping - self.power_utility.rate += scraper_power + # self.power_utility.rate += scraper_power #%% Centrifuge @@ -689,6 +690,8 @@ class Centrifuge(Thickener): _N_outs = 2 _ins_size_is_fixed = False + pumps = ('sludge',) + # Costs stainless_steel_unit_cost=1.8 # $/Kg (Taken from Joy's METAB code) https://www.alibaba.com/product-detail/brushed-stainless-steel-plate-304l-stainless_1600391656401.html?spm=a2700.details.0.0.230e67e6IKwwFd polymer_cost_by_weight = 2.2 # $/Kg (Source: https://www.alibaba.com/product-detail/dewatering-pool-chemicals-cationic-polyacrylamide-cas_1600194474507.html?spm=a2700.galleryofferlist.topad_classic.i5.5de8615c4zGAhg) @@ -777,27 +780,30 @@ def _design(self): total_mass_dry_solids_removed = (TSS_rmv/100)*((mixed.get_TSS()*self.ins[0].F_vol)/1000) # in kg/hr D['Number of centrifuges'] = np.ceil(total_mass_dry_solids_removed/solids_feed_rate) - k = 0.00000056 # Based on emprical formula (pg. 24-23 of [3]) - g = 9.81 # m/s2 - # The inner diameterof the bowl is calculated based on an empirical formula. 1000 is used to convert mm to m. - D['Diameter of bowl'] = (self.g_factor*g)/(k*np.square(self.rotational_speed)*1000) # in m - D['Total length of bowl'] = self.LtoD*D['Diameter of bowl'] - # Sanity check: L should be between 1-7 m, diameter should be around 0.25-0.8 m (Source: [4]) - - fraction_cylindrical_portion = 0.8 - fraction_conical_portion = 1 - fraction_cylindrical_portion - D['Length of cylindrical portion'] = fraction_cylindrical_portion*D['Total length of bowl'] - D['Length of conical portion'] = fraction_conical_portion*D['Total length of bowl'] - thickness_of_bowl_wall = 0.1 # in m (!!! NEED A RELIABLE SOURCE !!!) - inner_diameter = D['Diameter of bowl'] - outer_diameter = inner_diameter + 2*thickness_of_bowl_wall - - volume_cylindrical_wall = (np.pi*D['Length of cylindrical portion']/4)*(outer_diameter**2 - inner_diameter**2) - volume_conical_wall = (np.pi/3)*(D['Length of conical portion']/4)*(outer_diameter**2 - inner_diameter**2) - D['Volume of bowl'] = volume_cylindrical_wall + volume_conical_wall # in m3 - density_ss = 7930 # kg/m3, 18/8 Chromium - D['Stainless steel for bowl'] = D['Volume of bowl']*density_ss # in kg + # HAVE COMMENTED ALL OF THIS SINCE CENTRIFUGE WOULD PROBABLY BE BROUGHT NOT CONSTRUCTED AT THE FACILITY + + # k = 0.00000056 # Based on emprical formula (pg. 24-23 of [3]) + # g = 9.81 # m/s2 + # # The inner diameterof the bowl is calculated based on an empirical formula. 1000 is used to convert mm to m. + # D['Diameter of bowl'] = (self.g_factor*g)/(k*np.square(self.rotational_speed)*1000) # in m + # D['Total length of bowl'] = self.LtoD*D['Diameter of bowl'] + # # Sanity check: L should be between 1-7 m, diameter should be around 0.25-0.8 m (Source: [4]) + + # fraction_cylindrical_portion = 0.8 + # fraction_conical_portion = 1 - fraction_cylindrical_portion + # D['Length of cylindrical portion'] = fraction_cylindrical_portion*D['Total length of bowl'] + # D['Length of conical portion'] = fraction_conical_portion*D['Total length of bowl'] + # thickness_of_bowl_wall = 0.1 # in m (!!! NEED A RELIABLE SOURCE !!!) + # inner_diameter = D['Diameter of bowl'] + # outer_diameter = inner_diameter + 2*thickness_of_bowl_wall + + # volume_cylindrical_wall = (np.pi*D['Length of cylindrical portion']/4)*(outer_diameter**2 - inner_diameter**2) + # volume_conical_wall = (np.pi/3)*(D['Length of conical portion']/4)*(outer_diameter**2 - inner_diameter**2) + # D['Volume of bowl'] = volume_cylindrical_wall + volume_conical_wall # in m3 + + # density_ss = 7930 # kg/m3, 18/8 Chromium + # D['Stainless steel for bowl'] = D['Volume of bowl']*density_ss # in kg polymer_dosage_rate = 0.000453592*self.polymer_dosage # convert from (polymer (lbs)/solids (tonne)) to (polymer (kg)/solids (kg)) D['Polymer feed rate'] = (polymer_dosage_rate*solids_feed_rate) # in polymer (kg)/hr @@ -818,17 +824,22 @@ def _cost(self): self._mixed.mix_from(self.ins) mixed = self._mixed + # HAVE COMMENTED SINCE CENTRIFUGE WOULD PROBABLY BE BROUGHT NOT CONSTRUCTED AT THE FACILITY # Construction of concrete and stainless steel walls - C['Bowl stainless steel'] = D['Number of centrifuges']*D['Stainless steel for bowl']*self.stainless_steel_unit_cost + # C['Bowl stainless steel'] = D['Number of centrifuges']*D['Stainless steel for bowl']*self.stainless_steel_unit_cost # Conveyor - # Source: https://www.alibaba.com/product-detail/Engineers-Available-Service-Stainless-Steel-U_60541536633.html?spm=a2700.galleryofferlist.normal_offer.d_title.1fea1f65v6R3OQ&s=p - base_cost_conveyor = 1800 - base_flow_conveyor = 10 # in m3/hr - thickener_flow = mixed.get_total_flow('m3/hr')/D['Number of centrifuges'] - C['Conveyor'] = D['Number of centrifuges']*base_cost_conveyor*(thickener_flow/base_flow_conveyor)**0.6 - base_power_conveyor = 2.2 # in kW - conveyor_power = D['Number of centrifuges']*base_power_conveyor*(thickener_flow/base_flow_conveyor)**0.6 + # Source: https://www.alibaba.com/product-detail/Sludge-Dewatering-Centrifuge-Decanter-Centrifuge-For_60448094522.html?spm=a2700.galleryofferlist.p_offer.d_title.1c5c5229I5pQeP&s=p + base_cost_centrifuge = 16000 + base_mass_flow_centrifuge = 80 # in tonne/hr + thickener_mass_flow = (mixed.get_total_flow('m3/hr')*mixed.get_TSS())/D['Number of centrifuges'] # IN gm/hr + gm_to_tonne = 0.000001 + thickener_mass_flow = thickener_mass_flow*gm_to_tonne + C['Centrifuge'] = D['Number of centrifuges']*base_cost_centrifuge*(thickener_mass_flow/base_mass_flow_centrifuge)**0.6 + + # base_power_conveyor = 55 # in kW + # THIS IS NOT THE CORRECT EXPRESSION TO SCALE UP POWER OF CENTRIFUGE + # conveyor_power = D['Number of centrifuges']*base_power_conveyor*(thickener_flow/base_flow_conveyor)**0.6 # Pump (construction and maintainance) pumps = self.pumps @@ -864,7 +875,7 @@ def _cost(self): pumping = pumping*D['Number of pumps'] self.power_utility.rate += pumping - self.power_utility.rate += conveyor_power + # self.power_utility.rate += conveyor_power #%% Incinerator class Incinerator(SanUnit): From c2d5da051ef1b5449f28c5fd155bfab0fd27cfd8 Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Wed, 6 Dec 2023 15:52:20 -0600 Subject: [PATCH 231/424] Scaling centrifuge motor power scaling the motor power in the centrifuge linearly w.r.t influent mass flowrate. --- qsdsan/sanunits/_sludge_treatment.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py index d5c75210..0c0307ae 100644 --- a/qsdsan/sanunits/_sludge_treatment.py +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -837,9 +837,9 @@ def _cost(self): thickener_mass_flow = thickener_mass_flow*gm_to_tonne C['Centrifuge'] = D['Number of centrifuges']*base_cost_centrifuge*(thickener_mass_flow/base_mass_flow_centrifuge)**0.6 - # base_power_conveyor = 55 # in kW + base_power_motor = 55 # in kW # THIS IS NOT THE CORRECT EXPRESSION TO SCALE UP POWER OF CENTRIFUGE - # conveyor_power = D['Number of centrifuges']*base_power_conveyor*(thickener_flow/base_flow_conveyor)**0.6 + motor_power = D['Number of centrifuges']*base_power_motor*(thickener_mass_flow/base_mass_flow_centrifuge) # Pump (construction and maintainance) pumps = self.pumps @@ -875,7 +875,7 @@ def _cost(self): pumping = pumping*D['Number of pumps'] self.power_utility.rate += pumping - # self.power_utility.rate += conveyor_power + self.power_utility.rate += motor_power #%% Incinerator class Incinerator(SanUnit): From 6ee1c36aa41aaafc1626ccbaa78227af454df49f Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Thu, 7 Dec 2023 00:33:50 -0600 Subject: [PATCH 232/424] Coded function to compile pumping power demand in WRRF New function to retrieve the pumping costs and other motor costs of all units involve in sludge management is coded. --- qsdsan/utils/wwt_design.py | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/qsdsan/utils/wwt_design.py b/qsdsan/utils/wwt_design.py index 16c9957a..e02ecee2 100644 --- a/qsdsan/utils/wwt_design.py +++ b/qsdsan/utils/wwt_design.py @@ -18,10 +18,11 @@ 'get_oxygen_heterotrophs', 'get_oxygen_autotrophs', 'get_airflow', - 'get_P_blower') + 'get_P_blower', + 'get_power_utility') #%% - + def get_SRT(system, biomass_IDs, wastage=None, active_unit_IDs=None): """ Estimate sludge residence time (SRT) of an activated sludge system. @@ -276,4 +277,26 @@ def get_P_blower(q_air, T=20, p_atm=101.325, P_inlet_loss=1, Q_air = q_air/60 # m3/min to m3/s WP = (Q_air / air_molar_vol) * R * T / K * ((p_out/p_in)**K - 1) / efficiency # in W return WP/1000 + +def get_power_utility(system, active_unit_IDs=None): + ''' + Parameters + ---------- + system : :class:`biosteam.System` + The system whose power will be calculated. + active_unit_IDs : tuple[str], optional + IDs of all units whose power needs to be accounted for. The default is None, meaning to include + all units in the system. + + Returns + ------- + Required power [kW]. + ''' + power_consumption = 0 + + for y in system.flowsheet.unit: + if y.ID in active_unit_IDs: + power_consumption += y.power_utility.power + + return power_consumption \ No newline at end of file From 093ed31dd73eed1808154811cb2d235f573c8460 Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Mon, 11 Dec 2023 13:38:58 -0600 Subject: [PATCH 233/424] Scaling motor power linearly Scaling the power of motor deployed in centrifuge linearly with respect to mass flowrate. --- qsdsan/sanunits/_sludge_treatment.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py index 0c0307ae..bb2fed4e 100644 --- a/qsdsan/sanunits/_sludge_treatment.py +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -839,7 +839,8 @@ def _cost(self): base_power_motor = 55 # in kW # THIS IS NOT THE CORRECT EXPRESSION TO SCALE UP POWER OF CENTRIFUGE - motor_power = D['Number of centrifuges']*base_power_motor*(thickener_mass_flow/base_mass_flow_centrifuge) + motor_power = base_power_motor*(thickener_mass_flow/base_mass_flow_centrifuge) + total_motor_power = D['Number of centrifuges']*motor_power # Pump (construction and maintainance) pumps = self.pumps @@ -875,7 +876,7 @@ def _cost(self): pumping = pumping*D['Number of pumps'] self.power_utility.rate += pumping - self.power_utility.rate += motor_power + self.power_utility.rate += total_motor_power #%% Incinerator class Incinerator(SanUnit): From b2498bd104bc5e6e17f6f09a6a9bb74f2ac300eb Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Mon, 18 Dec 2023 10:40:18 -0600 Subject: [PATCH 234/424] Change in default total static head Changing H_ts based on Jeremy's recommendation. --- qsdsan/sanunits/_pumping.py | 2 +- qsdsan/utils/wwt_design.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/qsdsan/sanunits/_pumping.py b/qsdsan/sanunits/_pumping.py index 083d3280..3bf2354d 100644 --- a/qsdsan/sanunits/_pumping.py +++ b/qsdsan/sanunits/_pumping.py @@ -713,7 +713,7 @@ def design_sludge(self, Q_mgd=None, N_pump=None, **kwargs): val_dct = dict( L_s=50, # length of suction pipe, [ft] L_d=50, # length of discharge pipe, [ft] - H_ts=0., # H_ds_LIFT (D) - H_ss_LIFT (0) + H_ts=1., # H_ds_LIFT (D) - H_ss_LIFT (0) H_p=0. # no pressure ) val_dct.update(kwargs) diff --git a/qsdsan/utils/wwt_design.py b/qsdsan/utils/wwt_design.py index e02ecee2..04ed0828 100644 --- a/qsdsan/utils/wwt_design.py +++ b/qsdsan/utils/wwt_design.py @@ -246,7 +246,7 @@ def get_P_blower(q_air, T=20, p_atm=101.325, P_inlet_loss=1, h_submergance : float Diffuser submergance depth in m. The default is 17 feet (5.18 m) efficiency : float - Blower efficiency. Default is 0.8. + Blower efficiency. Default is 0.7. K : float, optional Equal to (kappa - 1)/kappa, where kappa is the adiabatic exponent of air. Default is 0.283, equivalent to kappa = 1.3947. From 90f358547c41fb22b58b5de5155266fc9677773e Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Tue, 19 Dec 2023 14:31:40 +0300 Subject: [PATCH 235/424] Corrected logic flow, and modified ANSI dictionary Made changes to select_pipe function to make sure that the selected inner diameter from ANSI is actually less than the calculated maximum diameter. --- qsdsan/utils/construction.py | 104 ++++++++++++++++++----------------- 1 file changed, 53 insertions(+), 51 deletions(-) diff --git a/qsdsan/utils/construction.py b/qsdsan/utils/construction.py index e838d7ea..01e63c91 100644 --- a/qsdsan/utils/construction.py +++ b/qsdsan/utils/construction.py @@ -468,57 +468,55 @@ def calculate_pipe_material(OD, t, ID, L, density=None): # Based on ANSI (American National Standards Institute) pipe chart # the original code has a bug (no data for 22) and has been fixed here -boundaries = np.concatenate([ - np.arange(1/8, 0.5, 1/8), - np.arange(0.5, 1.5, 1/4), - np.arange(1.5, 5, 0.5), - np.arange(5, 12, 1), - np.arange(12, 36, 2), - np.arange(36, 54, 6) +IDs = np.array([ + 0.307, 0.410, 0.545, 0.674, 0.884, 1.097, 1.442, 1.682, 2.157, + 2.635, 3.260, 3.760, 4.260, 4.760, 5.295, 6.357, 7.357, 8.329, + 9.329, 10.420, 11.420, 12.390, 13.624, 15.602, 17.624, 19.564, 21.500, + 23.500, 25.500, 27.500, 29.376, 31.376, 33.376, 35.376, 41.376, 47.376 ]) -size = boundaries.shape[0] +size = IDs.shape[0] +# Schedule 10 pipes @ https://amerpipe.com/wp-content/uploads/2015/10/APP-chart-v7-web.pdf pipe_dct = { - 1/8 : (0.405, 0.049), # OD (outer diameter), t (wall thickness) - 1/4 : (0.540, 0.065), - 3/8 : (0.675, 0.065), - 1/2 : (0.840, 0.083), - 3/4 : (1.050, 0.083), - 1 : (1.315, 0.109), - 1.25: (1.660, 0.109), - 1.5 : (1.900, 0.109), - 2 : (2.375, 0.109), - 2.5 : (2.875, 0.120), - 3 : (3.500, 0.120), - 3.5 : (4.000, 0.120), - 4 : (4.500, 0.120), - 4.5 : (5.000, 0.120), - 5 : (5.563, 0.134), - 6 : (6.625, 0.134), - 7 : (7.625, 0.134), - 8 : (8.625, 0.148), - 9 : (9.625, 0.148), - 10 : (10.750, 0.165), - 11 : (11.750, 0.165), - 12 : (12.750, 0.180), - 14 : (14.000, 0.188), - 16 : (16.000, 0.199), - 18 : (18.000, 0.188), - 20 : (20.000, 0.218), - 22 : (22.000, 0.250), - 24 : (24.000, 0.250), - 26 : (26.000, 0.250), - 28 : (28.000, 0.250), - 30 : (30.000, 0.312), - 32 : (32.000, 0.312), - 34 : (34.000, 0.312), - 36 : (36.000, 0.312), - 42 : (42.000, 0.312), - 48 : (48.000, 0.312) + 0.307 : (0.405, 0.049), # OD (outer diameter), t (wall thickness) + 0.410 : (0.540, 0.065), + 0.545 : (0.675, 0.065), + 0.674 : (0.840, 0.083), + 0.884 : (1.050, 0.083), + 1.097 : (1.315, 0.109), + 1.442 : (1.660, 0.109), + 1.682 : (1.900, 0.109), + 2.157 : (2.375, 0.109), + 2.635 : (2.875, 0.120), + 3.260 : (3.500, 0.120), + 3.760 : (4.000, 0.120), + 4.260 : (4.500, 0.120), + 4.760 : (5.000, 0.120), + 5.295 : (5.563, 0.134), + 6.357 : (6.625, 0.134), + 7.357 : (7.625, 0.134), + 8.329 : (8.625, 0.148), + 9.329 : (9.625, 0.148), + 10.420: (10.750, 0.165), + 11.420: (11.750, 0.165), + 12.390: (12.750, 0.180), + 13.624: (14.000, 0.188), + 15.602: (16.000, 0.199), + 17.624: (18.000, 0.188), + 19.564: (20.000, 0.218), + 21.500: (22.000, 0.250), + 23.500: (24.000, 0.250), + 25.500: (26.000, 0.250), + 27.500: (28.000, 0.250), + 29.376: (30.000, 0.312), + 31.376: (32.000, 0.312), + 33.376: (34.000, 0.312), + 35.376: (36.000, 0.312), + 41.376: (42.000, 0.312), + 47.376: (48.000, 0.312) } - def select_pipe(Q, v): ''' Select pipe based on Q (flow in ft3/s) and velocity (ft/s). @@ -535,10 +533,14 @@ def select_pipe(Q, v): Outer diameter, thickness, and inner diameter of the pipe (three floats), all in ft. ''' A = Q / v # cross-section area - d = (4*A/np.pi) ** 0.5 # minimum inner diameter, [ft] - d *= 12 # minimum inner diameter, [in] - d_index = np.searchsorted(boundaries, d, side='left') # a[i-1] < v <= a[i] - d_index = d_index-1 if d_index==size else d_index # if beyond the largest size - OD, t = pipe_dct[boundaries[d_index]] - ID = OD - 2*t # inner diameter, [in] + ID = (4*A/np.pi) ** 0.5 # maximum inner diameter, [ft] + ID *= 12 # maximum inner diameter, [in] + + ids = IDs[IDs <= ID] + if ids.size == 0: + ID = IDs[0] # inch + else: + ID = ids[-1] + OD, t = pipe_dct[ID] + return OD, t, ID \ No newline at end of file From fe708188a91d10bdd156a60c21f5460d06af74b5 Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Tue, 19 Dec 2023 14:39:09 +0300 Subject: [PATCH 236/424] Update documentation (select_pipe) Changed documentation of select-pipe --- qsdsan/utils/construction.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qsdsan/utils/construction.py b/qsdsan/utils/construction.py index 01e63c91..6b0508da 100644 --- a/qsdsan/utils/construction.py +++ b/qsdsan/utils/construction.py @@ -526,7 +526,7 @@ def select_pipe(Q, v): Q : float Flow rate of the fluid, [ft3/s] (cfs). v : float - Velocity of the fluid, [ft/s]. + Minumum permissible velocity of the fluid, [ft/s]. Returns ------- From 61de934e0bc3b514f3849c4a8fbc8aa24ce9f0e1 Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Wed, 3 Jan 2024 23:57:59 +0530 Subject: [PATCH 237/424] Added thermo to wastestream Added thermo argument to waste stream to debug an error related to ASM1-ADM1 interface. --- qsdsan/sanunits/_sludge_treatment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py index bb2fed4e..3ba43cd8 100644 --- a/qsdsan/sanunits/_sludge_treatment.py +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -168,7 +168,7 @@ def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, self.h_thickener = h_thickener self.downward_flow_velocity = downward_flow_velocity self.F_BM.update(F_BM) - self._mixed = WasteStream(f'{ID}_mixed') + self._mixed = WasteStream(f'{ID}_mixed', thermo = thermo) self._sludge = self.outs[0].copy(f'{ID}_sludge') self._thickener_factor = None self._thinning_factor = None From 9ffb6063e211b04772b98c89580c897b60aa26a6 Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Wed, 3 Jan 2024 23:59:07 +0530 Subject: [PATCH 238/424] Added thermo to pump to debug error related to using interfaces --- qsdsan/sanunits/_clarifier.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index 792dabc5..53ea520f 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -578,7 +578,7 @@ def _design_pump(self): ID = f'{ID}_{i}' capacity_factor=1 pump = WWTpump( - ID=ID, ins=ins_dct[i], pump_type=type_dct[i], + ID=ID, ins=ins_dct[i], thermo = self.thermo, pump_type=type_dct[i], Q_mgd=Q_mgd[i], add_inputs=inputs_dct[i], capacity_factor=capacity_factor, include_pump_cost=True, @@ -1713,13 +1713,14 @@ def _design_pump(self): ID = f'{ID}_{i}' capacity_factor=1 pump = WWTpump( - ID=ID, ins= ins_dct[i], pump_type=type_dct[i], + ID=ID, ins= ins_dct[i], thermo = self.thermo, pump_type=type_dct[i], Q_mgd=influent_Q_mgd, add_inputs=inputs_dct[i], capacity_factor=capacity_factor, include_pump_cost=True, include_building_cost=False, - include_OM_cost=True, + include_OM_cost=True, ) + setattr(self, f'{i}_pump', pump) pipe_ss, pump_ss = 0., 0. From 9a6977edafe6c901e42abc2352690e0836e131e9 Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Thu, 4 Jan 2024 00:00:00 +0530 Subject: [PATCH 239/424] Changing default value Changing default head from 1 foot to 5 foot. --- qsdsan/sanunits/_pumping.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qsdsan/sanunits/_pumping.py b/qsdsan/sanunits/_pumping.py index 3bf2354d..356a2901 100644 --- a/qsdsan/sanunits/_pumping.py +++ b/qsdsan/sanunits/_pumping.py @@ -713,7 +713,7 @@ def design_sludge(self, Q_mgd=None, N_pump=None, **kwargs): val_dct = dict( L_s=50, # length of suction pipe, [ft] L_d=50, # length of discharge pipe, [ft] - H_ts=1., # H_ds_LIFT (D) - H_ss_LIFT (0) + H_ts=5., # H_ds_LIFT (D) - H_ss_LIFT (0) H_p=0. # no pressure ) val_dct.update(kwargs) From e11e3f647c64f596638ffd15c41fd571d699c109 Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Thu, 4 Jan 2024 00:00:54 +0530 Subject: [PATCH 240/424] Added authorship For creating treatment train SanUnit --- qsdsan/sanunits/_activated_sludge_process.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/qsdsan/sanunits/_activated_sludge_process.py b/qsdsan/sanunits/_activated_sludge_process.py index 21659a81..3f3ae89e 100644 --- a/qsdsan/sanunits/_activated_sludge_process.py +++ b/qsdsan/sanunits/_activated_sludge_process.py @@ -7,6 +7,8 @@ This module is developed by: Yalin Li + + Saumitra Rai This module is under the University of Illinois/NCSA Open Source License. Please refer to https://github.com/QSD-Group/QSDsan/blob/main/LICENSE.txt @@ -742,7 +744,7 @@ class TreatmentTrains(Mixer): ''' Dummy unit with no run function of its own. To be used to calculate the design and cost of treatment trains in ASP. Code largely dereived from code scripts - for [2]_. + for [1]_. Parameters ---------- From 937441167c2b0081a098860cf3a7d01d962eccbe Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Thu, 4 Jan 2024 15:26:56 +0530 Subject: [PATCH 241/424] Added thermo to pumps in thickeners Added thermo to pump in thickener and centrifuge to debug an error related to use of ASM-ADM interface. --- qsdsan/sanunits/_sludge_treatment.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py index 3ba43cd8..72b9dc3d 100644 --- a/qsdsan/sanunits/_sludge_treatment.py +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -418,7 +418,7 @@ def _design_pump(self): capacity_factor=1 # No. of pumps = No. of influents pump = WWTpump( - ID=ID, ins= ins_dct[i], pump_type=type_dct[i], + ID=ID, ins= ins_dct[i], thermo = self.thermo, pump_type=type_dct[i], Q_mgd=influent_Q_mgd, add_inputs=inputs_dct[i], capacity_factor=capacity_factor, include_pump_cost=True, @@ -750,7 +750,7 @@ def _design_pump(self): capacity_factor=1 # No. of pumps = No. of influents pump = WWTpump( - ID=ID, ins= ins_dct[i], pump_type=type_dct[i], + ID=ID, ins= ins_dct[i], thermo = self.thermo, pump_type=type_dct[i], Q_mgd=influent_Q_mgd, add_inputs=inputs_dct[i], capacity_factor=capacity_factor, include_pump_cost=True, From c08375bfa0177d0abe3f31b0a00f0a5fbc53a4ea Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Thu, 11 Jan 2024 21:43:57 +0530 Subject: [PATCH 242/424] Function to determine GHG emission in secondary treatment Added new function that calculates GHG emission associated with secondary treatment processes like ASP. --- qsdsan/sanunits/_sludge_treatment.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py index 72b9dc3d..e89b7ac6 100644 --- a/qsdsan/sanunits/_sludge_treatment.py +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -777,9 +777,18 @@ def _design(self): solids_feed_rate = 44.66*self.solids_feed_rate # 44.66 is factor to convert tonne/day to kg/hr # Cake's total solids and TSS are essentially the same (pg. 24-6 [3]) # If TSS_rmv = 98, then total_mass_dry_solids_removed = (0.98)*(influent TSS mass) + + print(f'TSS_rmv = {TSS_rmv}') + print(f'mixed.get_TSS() = {mixed.get_TSS()}') + print(f'self.ins[0].F_vol = {self.ins[0].F_vol}') + total_mass_dry_solids_removed = (TSS_rmv/100)*((mixed.get_TSS()*self.ins[0].F_vol)/1000) # in kg/hr + print(f'total_mass_dry_solids_removed = {total_mass_dry_solids_removed}') + print(f'solids_feed_rate = {solids_feed_rate}') D['Number of centrifuges'] = np.ceil(total_mass_dry_solids_removed/solids_feed_rate) + m = D['Number of centrifuges'] + print(f'D[Number of centrifuges] = {m}') # HAVE COMMENTED ALL OF THIS SINCE CENTRIFUGE WOULD PROBABLY BE BROUGHT NOT CONSTRUCTED AT THE FACILITY From 704339051cd6f7a3e1081730d0c6fa93e16253b8 Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Thu, 11 Jan 2024 21:44:22 +0530 Subject: [PATCH 243/424] Revert "Function to determine GHG emission in secondary treatment" This reverts commit c08375bfa0177d0abe3f31b0a00f0a5fbc53a4ea. --- qsdsan/sanunits/_sludge_treatment.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py index e89b7ac6..72b9dc3d 100644 --- a/qsdsan/sanunits/_sludge_treatment.py +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -777,18 +777,9 @@ def _design(self): solids_feed_rate = 44.66*self.solids_feed_rate # 44.66 is factor to convert tonne/day to kg/hr # Cake's total solids and TSS are essentially the same (pg. 24-6 [3]) # If TSS_rmv = 98, then total_mass_dry_solids_removed = (0.98)*(influent TSS mass) - - print(f'TSS_rmv = {TSS_rmv}') - print(f'mixed.get_TSS() = {mixed.get_TSS()}') - print(f'self.ins[0].F_vol = {self.ins[0].F_vol}') - total_mass_dry_solids_removed = (TSS_rmv/100)*((mixed.get_TSS()*self.ins[0].F_vol)/1000) # in kg/hr - print(f'total_mass_dry_solids_removed = {total_mass_dry_solids_removed}') - print(f'solids_feed_rate = {solids_feed_rate}') D['Number of centrifuges'] = np.ceil(total_mass_dry_solids_removed/solids_feed_rate) - m = D['Number of centrifuges'] - print(f'D[Number of centrifuges] = {m}') # HAVE COMMENTED ALL OF THIS SINCE CENTRIFUGE WOULD PROBABLY BE BROUGHT NOT CONSTRUCTED AT THE FACILITY From 682f55c079a02fc24fef53df70a85fdb327235e2 Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Thu, 11 Jan 2024 21:45:28 +0530 Subject: [PATCH 244/424] Function to determine GHG emission in secondary treatment Added new function that calculates GHG emission associated with secondary treatment processes like ASP. --- qsdsan/utils/wwt_design.py | 58 +++++++++++++++++++++++++++++++++++--- 1 file changed, 54 insertions(+), 4 deletions(-) diff --git a/qsdsan/utils/wwt_design.py b/qsdsan/utils/wwt_design.py index 04ed0828..ca3bad05 100644 --- a/qsdsan/utils/wwt_design.py +++ b/qsdsan/utils/wwt_design.py @@ -19,7 +19,8 @@ 'get_oxygen_autotrophs', 'get_airflow', 'get_P_blower', - 'get_power_utility') + 'get_power_utility', + 'get_GHG_emissions_sec_treatment') #%% @@ -285,8 +286,7 @@ def get_power_utility(system, active_unit_IDs=None): system : :class:`biosteam.System` The system whose power will be calculated. active_unit_IDs : tuple[str], optional - IDs of all units whose power needs to be accounted for. The default is None, meaning to include - all units in the system. + IDs of all units whose power needs to be accounted for. The default is None. Returns ------- @@ -299,4 +299,54 @@ def get_power_utility(system, active_unit_IDs=None): if y.ID in active_unit_IDs: power_consumption += y.power_utility.power - return power_consumption \ No newline at end of file + return power_consumption + +def get_GHG_emissions_sec_treatment(system, influent=None, sludge = None, + CH4_EF=0.0075, N2O_EF=0.016): + ''' + Parameters + ---------- + system : :class:`biosteam.System` + The system for which emissions during secondary treatment are being calculated. + influent : : iterable[:class:`WasteStream`], optional + Influent wastestreams to the system whose wastewater composition determine the potential for GHG emissions. The default is None. + sludge : : iterable[:class:`WasteStream`], optional + The wastestream which represents the sludge to be disposed. The default is None. + CH4_EF : float, optional. + The emission factor used to calculate methane emissions in secondary treatment. The default is 0.0075 kg CH4/ kg rCOD. [1] + N2O_EF : float, optional + The emission factor used to calculate nitrous oxide emissions in secondary treatment. The default is 0.016 kg N2O-N/ kg N. [1] + + Returns + ------- + CH4_emitted : float + The amount of methane emitted during secondary treatment (kg/day). + N2O_emitted : float + The amount of nitrous oxide emitted during secondary treatment (kg/day). + + References + ---------- + [1] Chapter - 6, IPCC. (2019). In 2019 Refinement to the 2006 IPCC Guidelines for National Greenhouse Gas Inventories. + + ''' + + if influent is None: + influent = [inf for inf in system.feeds if inf.phase == 'l'] + + influent_flow = np.array([inf.F_vol*24 for inf in influent]) # in m3/day + influent_COD = np.array([inf.COD for inf in influent]) # in mg/L + mass_influent_COD = np.sum(influent_flow*influent_COD/1000) # in kg/day + + sludge_flow = np.array([sludge.F_vol*24]) # in m3/day + sludge_COD = np.array([sludge.COD]) # in mg/L + mass_sludge_COD = np.sum(sludge_flow*sludge_COD/1000) # in kg/day + + mass_removed_COD = mass_influent_COD - mass_sludge_COD + CH4_emitted = CH4_EF*mass_removed_COD + + influent_N = np.array([inf.TN for inf in influent]) # in mg/L + mass_influent_N = np.sum(influent_flow*influent_N/1000) # in kg/day + + N2O_emitted = N2O_EF*mass_influent_N + + return CH4_emitted, N2O_emitted \ No newline at end of file From 41aed571809164b70c37dfd8cfd369c8a20468a1 Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Fri, 12 Jan 2024 00:32:47 +0530 Subject: [PATCH 245/424] Function to determine GHG emission during discharge Added new function that calculates GHG emission associated with discharge of wastewater to natural waters. --- qsdsan/utils/wwt_design.py | 53 ++++++++++++++++++++++++++++++++++---- 1 file changed, 48 insertions(+), 5 deletions(-) diff --git a/qsdsan/utils/wwt_design.py b/qsdsan/utils/wwt_design.py index ca3bad05..e4fe2da4 100644 --- a/qsdsan/utils/wwt_design.py +++ b/qsdsan/utils/wwt_design.py @@ -20,7 +20,8 @@ 'get_airflow', 'get_P_blower', 'get_power_utility', - 'get_GHG_emissions_sec_treatment') + 'get_GHG_emissions_sec_treatment', + 'get_GHG_emissions_discharge') #%% @@ -337,9 +338,9 @@ def get_GHG_emissions_sec_treatment(system, influent=None, sludge = None, influent_COD = np.array([inf.COD for inf in influent]) # in mg/L mass_influent_COD = np.sum(influent_flow*influent_COD/1000) # in kg/day - sludge_flow = np.array([sludge.F_vol*24]) # in m3/day - sludge_COD = np.array([sludge.COD]) # in mg/L - mass_sludge_COD = np.sum(sludge_flow*sludge_COD/1000) # in kg/day + sludge_flow = sludge.F_vol*24 # in m3/day + sludge_COD = sludge.COD # in mg/L + mass_sludge_COD = sludge_flow*sludge_COD/1000 # in kg/day mass_removed_COD = mass_influent_COD - mass_sludge_COD CH4_emitted = CH4_EF*mass_removed_COD @@ -349,4 +350,46 @@ def get_GHG_emissions_sec_treatment(system, influent=None, sludge = None, N2O_emitted = N2O_EF*mass_influent_N - return CH4_emitted, N2O_emitted \ No newline at end of file + return CH4_emitted, N2O_emitted + +def get_GHG_emissions_discharge(effluent=None, CH4_EF=0.0075, N2O_EF=0.016): + ''' + Parameters + ---------- + system : :class:`biosteam.System` + The system for which emissions during secondary treatment are being calculated. + influent : : iterable[:class:`WasteStream`], optional + Influent wastestreams to the system whose wastewater composition determine the potential for GHG emissions. The default is None. + sludge : : iterable[:class:`WasteStream`], optional + The wastestream which represents the sludge to be disposed. The default is None. + CH4_EF : float, optional. + The emission factor used to calculate methane emissions in secondary treatment. The default is 0.0075 kg CH4/ kg rCOD. [1] + N2O_EF : float, optional + The emission factor used to calculate nitrous oxide emissions in secondary treatment. The default is 0.016 kg N2O-N/ kg N. [1] + + Returns + ------- + CH4_emitted : float + The amount of methane emitted during secondary treatment (kg/day). + N2O_emitted : float + The amount of nitrous oxide emitted during secondary treatment (kg/day). + + References + ---------- + [1] Chapter - 6, IPCC. (2019). In 2019 Refinement to the 2006 IPCC Guidelines for National Greenhouse Gas Inventories. + + ''' + + effluent_flow = effluent.F_vol*24 # in m3/day + effluent_COD = effluent.COD # in mg/L + mass_effluent_COD = effluent_flow*effluent_COD/1000 # in kg/day + + CH4_emitted = CH4_EF*mass_effluent_COD + + effluent_N = effluent.TN # in mg/L + mass_effluent_N = effluent_flow*effluent_N/1000 # in kg/day + + N2O_emitted = N2O_EF*mass_effluent_N + + return CH4_emitted, N2O_emitted + From 8b03d62b4f916f775b87e1e0afd2371b82be5614 Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Sat, 13 Jan 2024 12:51:10 +0530 Subject: [PATCH 246/424] More flexible GHG emission functions Made the sludge and effluent wastestreams an iterable to make the GHG emission functions more flexible. --- qsdsan/utils/wwt_design.py | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/qsdsan/utils/wwt_design.py b/qsdsan/utils/wwt_design.py index e4fe2da4..80705ab8 100644 --- a/qsdsan/utils/wwt_design.py +++ b/qsdsan/utils/wwt_design.py @@ -312,7 +312,7 @@ def get_GHG_emissions_sec_treatment(system, influent=None, sludge = None, influent : : iterable[:class:`WasteStream`], optional Influent wastestreams to the system whose wastewater composition determine the potential for GHG emissions. The default is None. sludge : : iterable[:class:`WasteStream`], optional - The wastestream which represents the sludge to be disposed. The default is None. + The wastestreams which represents the sludge to be disposed. The default is None. CH4_EF : float, optional. The emission factor used to calculate methane emissions in secondary treatment. The default is 0.0075 kg CH4/ kg rCOD. [1] N2O_EF : float, optional @@ -338,9 +338,9 @@ def get_GHG_emissions_sec_treatment(system, influent=None, sludge = None, influent_COD = np.array([inf.COD for inf in influent]) # in mg/L mass_influent_COD = np.sum(influent_flow*influent_COD/1000) # in kg/day - sludge_flow = sludge.F_vol*24 # in m3/day - sludge_COD = sludge.COD # in mg/L - mass_sludge_COD = sludge_flow*sludge_COD/1000 # in kg/day + sludge_flow = np.array([sludge.F_vol*24 for sludge in sludge]) + sludge_COD = np.array([sludge.COD for sludge in sludge]) # in mg/L + mass_sludge_COD = np.sum(sludge_flow*sludge_COD/1000) # in kg/day mass_removed_COD = mass_influent_COD - mass_sludge_COD CH4_emitted = CH4_EF*mass_removed_COD @@ -356,12 +356,8 @@ def get_GHG_emissions_discharge(effluent=None, CH4_EF=0.0075, N2O_EF=0.016): ''' Parameters ---------- - system : :class:`biosteam.System` - The system for which emissions during secondary treatment are being calculated. - influent : : iterable[:class:`WasteStream`], optional - Influent wastestreams to the system whose wastewater composition determine the potential for GHG emissions. The default is None. - sludge : : iterable[:class:`WasteStream`], optional - The wastestream which represents the sludge to be disposed. The default is None. + effluent : : iterable[:class:`WasteStream`], optional + Effluent wastestreams from the system whose wastewater composition determine the potential for GHG emissions at discharge. The default is None. CH4_EF : float, optional. The emission factor used to calculate methane emissions in secondary treatment. The default is 0.0075 kg CH4/ kg rCOD. [1] N2O_EF : float, optional @@ -370,9 +366,9 @@ def get_GHG_emissions_discharge(effluent=None, CH4_EF=0.0075, N2O_EF=0.016): Returns ------- CH4_emitted : float - The amount of methane emitted during secondary treatment (kg/day). + The amount of methane emitted at discharge (kg/day). N2O_emitted : float - The amount of nitrous oxide emitted during secondary treatment (kg/day). + The amount of nitrous oxide emitted at discharge (kg/day). References ---------- @@ -380,14 +376,14 @@ def get_GHG_emissions_discharge(effluent=None, CH4_EF=0.0075, N2O_EF=0.016): ''' - effluent_flow = effluent.F_vol*24 # in m3/day - effluent_COD = effluent.COD # in mg/L - mass_effluent_COD = effluent_flow*effluent_COD/1000 # in kg/day + effluent_flow = np.array([eff.F_vol*24 for eff in effluent]) # in m3/day + effluent_COD = np.array([eff.COD for eff in effluent]) # in mg/L + mass_effluent_COD = np.sum(effluent_flow*effluent_COD/1000) # in kg/day CH4_emitted = CH4_EF*mass_effluent_COD - effluent_N = effluent.TN # in mg/L - mass_effluent_N = effluent_flow*effluent_N/1000 # in kg/day + effluent_N = np.array([eff.TN for eff in effluent]) # in mg/L + mass_effluent_N = np.sum(effluent_flow*effluent_N/1000) # in kg/day N2O_emitted = N2O_EF*mass_effluent_N From 95e2032b8a60ecf481c52c5c66aa16f0ae9bf335 Mon Sep 17 00:00:00 2001 From: RaiSaumitra Date: Sat, 13 Jan 2024 22:17:52 +0530 Subject: [PATCH 247/424] Function to calculate CO2 emissions due to electricity consumption Added new function that calculates GHG emission associated with consumption of electricity. --- qsdsan/utils/wwt_design.py | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/qsdsan/utils/wwt_design.py b/qsdsan/utils/wwt_design.py index 80705ab8..9f38ee3a 100644 --- a/qsdsan/utils/wwt_design.py +++ b/qsdsan/utils/wwt_design.py @@ -21,7 +21,8 @@ 'get_P_blower', 'get_power_utility', 'get_GHG_emissions_sec_treatment', - 'get_GHG_emissions_discharge') + 'get_GHG_emissions_discharge', + 'get_GHG_emissions_electricity') #%% @@ -388,4 +389,38 @@ def get_GHG_emissions_discharge(effluent=None, CH4_EF=0.0075, N2O_EF=0.016): N2O_emitted = N2O_EF*mass_effluent_N return CH4_emitted, N2O_emitted + +def get_GHG_emissions_electricity(system, power_blower, power_pump, CO2_EF=0.668): + ''' + Parameters + ---------- + system : :class:`biosteam.System` + The system for which tier-2 GHG emissions due to electricity consumption are being calculated. + power_blower : float + Power of blower [kW]. + power_pump : float + Power required for pumping and other utilities at treatment facility [kW]. + CO2_EF : float + The emission factor used to calculate tier-2 CO2 emissions due to electricity consumption. The default is 0.668 kg-CO2-Eq/kWh. [1] + The emission factor is dependent on the region, and is as follows for USA: + + {SERC Reliability Corporation (SERC): 0.612 kg-CO2-Eq/kWh + ReliabilityFirst (RFC): 0.618 kg-CO2-Eq/kWh + Western Electricity Coordinating Council (WECC): 0.428 kg-CO2-Eq/kWh + Texas Reliability Entity (TRE): 0.565 kg-CO2-Eq/kWh + Southwest Power Pool (SPP): 0.724 kg-CO2-Eq/kWh + Midwest Reliability Organization (MRO): 0.668 kg-CO2-Eq/kWh + Florida Reliability Coordinating Council (FRCC): 0.531 kg-CO2-Eq/kWh + Northeast Power Coordinating Council (NPCC): 0.242 kg-CO2-Eq/kWh} + + Returns + ------- + CO2_emitted : float + The amount of eq. CO2 emitted due to electrity consumption (kg-CO2-Eq/day). + ''' + + total_energy_consumed = (power_blower + power_pump)*24 # in kWh/day + CO2_emissions = total_energy_consumed*CO2_EF # in kg-CO2-Eq/day + + return CO2_emissions From 8c8879913a687706799c0ce8ac6f2bdd06b0d493 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Wed, 24 Jan 2024 08:16:15 -0600 Subject: [PATCH 248/424] Added function to quantify GHG emission from sludge disposal The function calculates methane emission from sludge disposal using a first-order decay equation. --- qsdsan/utils/wwt_design.py | 54 +++++++++++++++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/qsdsan/utils/wwt_design.py b/qsdsan/utils/wwt_design.py index 9f38ee3a..a149f4e0 100644 --- a/qsdsan/utils/wwt_design.py +++ b/qsdsan/utils/wwt_design.py @@ -22,7 +22,8 @@ 'get_power_utility', 'get_GHG_emissions_sec_treatment', 'get_GHG_emissions_discharge', - 'get_GHG_emissions_electricity') + 'get_GHG_emissions_electricity', + 'get_GHG_emissions_sludge_disposal') #%% @@ -424,3 +425,54 @@ def get_GHG_emissions_electricity(system, power_blower, power_pump, CO2_EF=0.668 CO2_emissions = total_energy_consumed*CO2_EF # in kg-CO2-Eq/day return CO2_emissions + +# r_VSS_TSS, r_BOD_VSS + +def get_GHG_emissions_sludge_disposal(sludge=None, DOC_f = 0.5, MCF = 0.8, k = 0.185, F=0.5, t=3): + ''' + Parameters + ---------- + sludge : : iterable[:class:`WasteStream`], optional + Effluent sludge from the system for which GHG emissions are being calculated. The default is None. + DOC_f : float, optional + fraction of DOC that can decompose (fraction). The default value is 0.5. + MCF : float, optional + CH4 correction factor for aerobic decomposition in the year of deposition (fraction). The default is 0.8. + k : TYPE, optional + Methane generation rate (k). The default is 0.185. (1/year) + + The decomposition of carbon is assumed to follow first-order kinetics + (with rate constant k), and methane generation is dependent on the amount of remaining decomposable carbon + in the waste. For North America (boreal and temperate climate) the default values for wet and dry climate + are: + k (dry climate) = 0.06 + k (wet climate) = 0.185 + F : float, optional + Fraction of methane in generated landfill gas (volume fraction). The default is 0.5. + t : float, optional + The number of years whose cumulative waste generation is being considered for methane emmissions. (years) + The default is 3. + + Returns + ------- + CH4_emitted : float + The amount of methane emitted during sludge disposal (kg/year). + + ''' + sludge_flow = np.array([slg.F_vol*24 for slg in sludge]) # in m3/day + sludge_COD = np.array([slg.COD for slg in sludge]) # in mg/L + + annual_sludge_mass = 365*np.sum(sludge_flow*sludge_COD/100) # in kg/year + + annual_DDOC = annual_sludge_mass*DOC_f*MCF + + acc_DOC = 0 + for i in range(t + 1): + acc_DOC += annual_DDOC*np.exp(-1*k*t) + + decomposed_DOC = acc_DOC*(1 - np.exp(-1*k)) # in the last one year + CH4_emitted = decomposed_DOC*F*16/12 + + return CH4_emitted + + \ No newline at end of file From 5ae2b181f44bce664a048bcd51a34943946f18c1 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Sun, 28 Jan 2024 23:40:30 -0600 Subject: [PATCH 249/424] updated sludge disposal GHG function Changed function to calculate GHG emissions from sludge disposal to include emissions over project lifetime. Also using a more accurate proxy for W*DOC --- qsdsan/utils/wwt_design.py | 52 +++++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/qsdsan/utils/wwt_design.py b/qsdsan/utils/wwt_design.py index a149f4e0..46fb60ac 100644 --- a/qsdsan/utils/wwt_design.py +++ b/qsdsan/utils/wwt_design.py @@ -14,6 +14,7 @@ import numpy as np + __all__ = ('get_SRT', 'get_oxygen_heterotrophs', 'get_oxygen_autotrophs', @@ -374,7 +375,8 @@ def get_GHG_emissions_discharge(effluent=None, CH4_EF=0.0075, N2O_EF=0.016): References ---------- - [1] Chapter - 6, IPCC. (2019). In 2019 Refinement to the 2006 IPCC Guidelines for National Greenhouse Gas Inventories. + [1] Chapter - 6, IPCC. (2019). In 2019 Refinement to the 2006 IPCC Guidelines + for National Greenhouse Gas Inventories. ''' @@ -428,7 +430,7 @@ def get_GHG_emissions_electricity(system, power_blower, power_pump, CO2_EF=0.668 # r_VSS_TSS, r_BOD_VSS -def get_GHG_emissions_sludge_disposal(sludge=None, DOC_f = 0.5, MCF = 0.8, k = 0.185, F=0.5, t=3): +def get_GHG_emissions_sludge_disposal(sludge=None, DOC_f = 0.5, MCF = 0.8, k = 0.185, F=0.5, pl=30): ''' Parameters ---------- @@ -449,30 +451,44 @@ def get_GHG_emissions_sludge_disposal(sludge=None, DOC_f = 0.5, MCF = 0.8, k = 0 k (wet climate) = 0.185 F : float, optional Fraction of methane in generated landfill gas (volume fraction). The default is 0.5. - t : float, optional - The number of years whose cumulative waste generation is being considered for methane emmissions. (years) - The default is 3. + pl : float, optional + The project lifetime over which methane emissions would be calculated. (years) + The default is 30 years. Returns ------- CH4_emitted : float - The amount of methane emitted during sludge disposal (kg/year). + The average amount of methane emitted during sludge disposal (kg/year). + + References + ---------- + [1] Chapter - 3: Solid Waste Disposal, IPCC. (2019). In 2019 Refinement to + the 2006 IPCC Guidelines for National Greenhouse Gas Inventories. ''' - sludge_flow = np.array([slg.F_vol*24 for slg in sludge]) # in m3/day - sludge_COD = np.array([slg.COD for slg in sludge]) # in mg/L - - annual_sludge_mass = 365*np.sum(sludge_flow*sludge_COD/100) # in kg/year + # sludge_flow = np.array([slg.F_vol*24 for slg in sludge]) # in m3/day + # sludge_COD = np.array([slg.COD for slg in sludge]) # in mg/L + + for slg in sludge: + DOC_mass_flow = slg.composite("C", flow=True, exclude_gas=True, + subgroup=None, particle_size=None, + degradability="b", organic=True, volatile=None, + specification=None, unit="kg/day") - annual_DDOC = annual_sludge_mass*DOC_f*MCF + annual_DOC_mass = 365*np.array(DOC_mass_flow) # in kg/year - acc_DOC = 0 - for i in range(t + 1): - acc_DOC += annual_DDOC*np.exp(-1*k*t) + annual_DDOC = annual_DOC_mass*DOC_f*MCF + + decomposed_DOC = 0 + + # sum of sum of geometric series + for t in range(pl + 1): + # sum of a geometric series where acc_DOC + acc_DOC = annual_DDOC * (1 - np.exp(-1 * k * t)) / (1 - np.exp(-1 * k)) + # all the acc_DOC at the start of the year is the only one contributing + # to decomposition in that one year + decomposed_DOC += acc_DOC*(1 - np.exp(-1*k)) - decomposed_DOC = acc_DOC*(1 - np.exp(-1*k)) # in the last one year CH4_emitted = decomposed_DOC*F*16/12 - return CH4_emitted - - \ No newline at end of file + return CH4_emitted/pl \ No newline at end of file From feb9f991623b42e8145012b26c1d174019a63c0b Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Wed, 31 Jan 2024 11:02:03 -0600 Subject: [PATCH 250/424] Added GHG function Added new functions that return normalized energy consumption, and GHG emissions at WRRF. --- qsdsan/utils/wwt_design.py | 97 +++++++++++++++++++++++++++++++++++--- 1 file changed, 90 insertions(+), 7 deletions(-) diff --git a/qsdsan/utils/wwt_design.py b/qsdsan/utils/wwt_design.py index 46fb60ac..f944e744 100644 --- a/qsdsan/utils/wwt_design.py +++ b/qsdsan/utils/wwt_design.py @@ -21,10 +21,12 @@ 'get_airflow', 'get_P_blower', 'get_power_utility', + 'get_normalized_energy', 'get_GHG_emissions_sec_treatment', 'get_GHG_emissions_discharge', 'get_GHG_emissions_electricity', - 'get_GHG_emissions_sludge_disposal') + 'get_GHG_emissions_sludge_disposal', + 'get_CO2_eq_WRRF') #%% @@ -294,7 +296,7 @@ def get_power_utility(system, active_unit_IDs=None): Returns ------- - Required power [kW]. + Power of blower [kW]. ''' power_consumption = 0 @@ -305,6 +307,33 @@ def get_power_utility(system, active_unit_IDs=None): return power_consumption +def get_normalized_energy(system, aeration_power, pumping_power, miscellaneous_power): + ''' + Parameters + ---------- + system : :class:`biosteam.System` + The system for which normalized energy consumption is being determined. + aeration_power : float, optional + Power of blower [kW]. + pumping_power : float, optional + Power rquired for sludge pumping and other equipments [kW]. + miscellaneous_power : float, optional + Any other power requirement in the system [kW]. + + Returns + ------- + Normalized energy consumption associated with WRRF (kWh/m3). [numpy array] + + ''' + + normalized_aeration_energy = aeration_power/sum([s.F_vol for s in system.feeds]) + normalized_pumping_energy = pumping_power/sum([s.F_vol for s in system.feeds]) + normalized_miscellaneous_energy = miscellaneous_power/sum([s.F_vol for s in system.feeds]) + + normalized_energy_WRRF = np.array([normalized_aeration_energy, normalized_pumping_energy, normalized_miscellaneous_energy]) + + return normalized_energy_WRRF + def get_GHG_emissions_sec_treatment(system, influent=None, sludge = None, CH4_EF=0.0075, N2O_EF=0.016): ''' @@ -404,7 +433,8 @@ def get_GHG_emissions_electricity(system, power_blower, power_pump, CO2_EF=0.668 power_pump : float Power required for pumping and other utilities at treatment facility [kW]. CO2_EF : float - The emission factor used to calculate tier-2 CO2 emissions due to electricity consumption. The default is 0.668 kg-CO2-Eq/kWh. [1] + The emission factor used to calculate tier-2 CO2 emissions due to electricity consumption. + The default is 0.668 kg-CO2-Eq/kWh. [1] The emission factor is dependent on the region, and is as follows for USA: {SERC Reliability Corporation (SERC): 0.612 kg-CO2-Eq/kWh @@ -458,7 +488,7 @@ def get_GHG_emissions_sludge_disposal(sludge=None, DOC_f = 0.5, MCF = 0.8, k = 0 Returns ------- CH4_emitted : float - The average amount of methane emitted during sludge disposal (kg/year). + The average amount of methane emitted during sludge disposal (kg/day). References ---------- @@ -468,19 +498,22 @@ def get_GHG_emissions_sludge_disposal(sludge=None, DOC_f = 0.5, MCF = 0.8, k = 0 ''' # sludge_flow = np.array([slg.F_vol*24 for slg in sludge]) # in m3/day # sludge_COD = np.array([slg.COD for slg in sludge]) # in mg/L + DOC_mass_flow = 0 for slg in sludge: - DOC_mass_flow = slg.composite("C", flow=True, exclude_gas=True, + DOC_mass_flow += slg.composite("C", flow=True, exclude_gas=True, subgroup=None, particle_size=None, degradability="b", organic=True, volatile=None, specification=None, unit="kg/day") - annual_DOC_mass = 365*np.array(DOC_mass_flow) # in kg/year + annual_DOC_mass = 365*DOC_mass_flow # in kg/year annual_DDOC = annual_DOC_mass*DOC_f*MCF decomposed_DOC = 0 + DOC_ARRAY = np.arange(pl + 1) + # sum of sum of geometric series for t in range(pl + 1): # sum of a geometric series where acc_DOC @@ -489,6 +522,56 @@ def get_GHG_emissions_sludge_disposal(sludge=None, DOC_f = 0.5, MCF = 0.8, k = 0 # to decomposition in that one year decomposed_DOC += acc_DOC*(1 - np.exp(-1*k)) + # make annumpy array from 0 to pl + 1 outside the for loop + + # replace t with DOC_ARRAY + CH4_emitted = decomposed_DOC*F*16/12 + + days_in_year = 365 - return CH4_emitted/pl \ No newline at end of file + return CH4_emitted/(pl*days_in_year) + +def get_CO2_eq_WRRF (system, GHG_treatment, GHG_discharge, GHG_electricity, + GHG_sludge_disposal, GHG_AD, CH4_CO2eq=29.8, N2O_CO2eq=273): + ''' + + Parameters + ---------- + system : :class:`biosteam.System` + The system for which normalized GHG emission is being determined. + GHG_treatment : tuple[int], optional + The amount of methane and nitrous oxide emitted during secondary treatment (kg/day). + GHG_discharge : tuple[int], optional + The amount of methane and nitrous oxide emitted during effluent discharge (kg/day). + GHG_electricity : float + The amount of eq. CO2 emitted due to electrity consumption (kg-CO2-Eq/day). + GHG_sludge_disposal : int + The average amount of methane emitted during sludge disposal (kg/day). + GHG_AD : int + The average amount of methane emitted during anaerobic (kg/day). + CH4_CO2eq : TYPE, optional + DESCRIPTION. The default is 29.8 kg CO2eq/kg CH4 [1]. + N2O_CO2eq : TYPE, optional + DESCRIPTION. The default is 273 kg CO2eq/kg CH4 [1]. + + Returns + ------- + Normalized GHG emissions from onsite and offsite operations associated with WRRF (kg CO2 eq./m3). [numpy array] + + References + ---------- + [1] IPCC 2021 – 6th Assessment Report Values. + + ''' + + CO2_eq_treatment = GHG_treatment[0]*CH4_CO2eq + GHG_treatment[1]*N2O_CO2eq + CO2_eq_discharge = GHG_discharge[0]*CH4_CO2eq + GHG_discharge[1]*N2O_CO2eq + CO2_eq_electricity = GHG_electricity + CO2_eq_sludge_disposal = GHG_sludge_disposal*CH4_CO2eq + CO2_eq_AD = GHG_AD*CH4_CO2eq + + CO2_eq_WRRF = np.array([CO2_eq_treatment, CO2_eq_AD, CO2_eq_discharge, CO2_eq_sludge_disposal, CO2_eq_electricity]) + normalized_CO2_eq_WRRF = CO2_eq_WRRF/sum([24*s.F_vol for s in system.feeds]) + + return normalized_CO2_eq_WRRF \ No newline at end of file From b4f8ff373f997a5c5103fdd8fcdce45b404b4ae9 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Wed, 31 Jan 2024 15:32:35 -0600 Subject: [PATCH 251/424] Optimized the code that calculates GHG emission from sludge disposal Optimized the code to a less computationally demanding --- qsdsan/utils/wwt_design.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/qsdsan/utils/wwt_design.py b/qsdsan/utils/wwt_design.py index f944e744..bf11f25c 100644 --- a/qsdsan/utils/wwt_design.py +++ b/qsdsan/utils/wwt_design.py @@ -510,24 +510,24 @@ def get_GHG_emissions_sludge_disposal(sludge=None, DOC_f = 0.5, MCF = 0.8, k = 0 annual_DDOC = annual_DOC_mass*DOC_f*MCF - decomposed_DOC = 0 - - DOC_ARRAY = np.arange(pl + 1) - - # sum of sum of geometric series - for t in range(pl + 1): - # sum of a geometric series where acc_DOC - acc_DOC = annual_DDOC * (1 - np.exp(-1 * k * t)) / (1 - np.exp(-1 * k)) - # all the acc_DOC at the start of the year is the only one contributing - # to decomposition in that one year - decomposed_DOC += acc_DOC*(1 - np.exp(-1*k)) + # decomposed_DOC = 0 + + # # sum of sum of geometric series + # for t in range(pl + 1): + # # sum of a geometric series where acc_DOC + # acc_DOC = annual_DDOC * (1 - np.exp(-1 * k * t)) / (1 - np.exp(-1 * k)) + # # all the acc_DOC at the start of the year is the only one contributing + # # to decomposition in that one year + # decomposed_DOC += acc_DOC*(1 - np.exp(-1*k)) - # make annumpy array from 0 to pl + 1 outside the for loop + # # make annumpy array from 0 to pl + 1 outside the for loop - # replace t with DOC_ARRAY + # # replace t with DOC_ARRAY + t_vary = np.arange(pl + 1) + acc_DOC = annual_DDOC * (1 - np.exp(-1 * k * t_vary)) + decomposed_DOC = np.sum(acc_DOC) CH4_emitted = decomposed_DOC*F*16/12 - days_in_year = 365 return CH4_emitted/(pl*days_in_year) From 491bb3beddb452bf6bb3e9ee80b8b65e7a806edc Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Wed, 31 Jan 2024 16:11:02 -0600 Subject: [PATCH 252/424] Updated secondary treatment GHG Changed variables names to be consistent with IPCC norms. --- qsdsan/utils/wwt_design.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/qsdsan/utils/wwt_design.py b/qsdsan/utils/wwt_design.py index bf11f25c..ee4d6cb1 100644 --- a/qsdsan/utils/wwt_design.py +++ b/qsdsan/utils/wwt_design.py @@ -334,17 +334,17 @@ def get_normalized_energy(system, aeration_power, pumping_power, miscellaneous_p return normalized_energy_WRRF -def get_GHG_emissions_sec_treatment(system, influent=None, sludge = None, +def get_GHG_emissions_sec_treatment(system = None, influent=None, effluent = None, CH4_EF=0.0075, N2O_EF=0.016): ''' Parameters ---------- system : :class:`biosteam.System` - The system for which emissions during secondary treatment are being calculated. + The system for which emissions during secondary treatment are being calculated. The default is None. influent : : iterable[:class:`WasteStream`], optional Influent wastestreams to the system whose wastewater composition determine the potential for GHG emissions. The default is None. - sludge : : iterable[:class:`WasteStream`], optional - The wastestreams which represents the sludge to be disposed. The default is None. + effluent : : iterable[:class:`WasteStream`], optional + The wastestreams which represents the effluent from the treatment process. The default is None. CH4_EF : float, optional. The emission factor used to calculate methane emissions in secondary treatment. The default is 0.0075 kg CH4/ kg rCOD. [1] N2O_EF : float, optional @@ -370,11 +370,11 @@ def get_GHG_emissions_sec_treatment(system, influent=None, sludge = None, influent_COD = np.array([inf.COD for inf in influent]) # in mg/L mass_influent_COD = np.sum(influent_flow*influent_COD/1000) # in kg/day - sludge_flow = np.array([sludge.F_vol*24 for sludge in sludge]) - sludge_COD = np.array([sludge.COD for sludge in sludge]) # in mg/L - mass_sludge_COD = np.sum(sludge_flow*sludge_COD/1000) # in kg/day + effluent_flow = np.array([eff.F_vol*24 for eff in effluent]) + effluent_COD = np.array([eff.COD for eff in effluent]) # in mg/L + mass_effluent_COD = np.sum(effluent_flow*effluent_COD/1000) # in kg/day - mass_removed_COD = mass_influent_COD - mass_sludge_COD + mass_removed_COD = mass_influent_COD - mass_effluent_COD CH4_emitted = CH4_EF*mass_removed_COD influent_N = np.array([inf.TN for inf in influent]) # in mg/L From a2d949d5262684cd1eb884f4f843734c1c8e2987 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Thu, 1 Feb 2024 10:39:23 -0600 Subject: [PATCH 253/424] Updated GHG emission function The function now gives the breakdown of GHG emissions into type of gas (methane, nitrous oxide, carbon-di-oxide) for each of the five sources. --- qsdsan/utils/wwt_design.py | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/qsdsan/utils/wwt_design.py b/qsdsan/utils/wwt_design.py index ee4d6cb1..8f1d1281 100644 --- a/qsdsan/utils/wwt_design.py +++ b/qsdsan/utils/wwt_design.py @@ -565,13 +565,29 @@ def get_CO2_eq_WRRF (system, GHG_treatment, GHG_discharge, GHG_electricity, ''' - CO2_eq_treatment = GHG_treatment[0]*CH4_CO2eq + GHG_treatment[1]*N2O_CO2eq - CO2_eq_discharge = GHG_discharge[0]*CH4_CO2eq + GHG_discharge[1]*N2O_CO2eq - CO2_eq_electricity = GHG_electricity - CO2_eq_sludge_disposal = GHG_sludge_disposal*CH4_CO2eq - CO2_eq_AD = GHG_AD*CH4_CO2eq + # source 1 (on-site) + CH4_CO2_eq_treatment = GHG_treatment[0]*CH4_CO2eq + N2O_CO2_eq_treatment = GHG_treatment[1]*N2O_CO2eq + + # source 2 (on-site) + CH4_CO2_eq_AD = GHG_AD*CH4_CO2eq + + # source 3 (off-site) + CH4_CO2_eq_discharge = GHG_discharge[0]*CH4_CO2eq + N2O_CO2_eq_discharge = GHG_discharge[1]*N2O_CO2eq + + # source 4 (off-site) + CH4_CO2_eq_sludge_disposal = GHG_sludge_disposal*CH4_CO2eq + + # source 5 (off-site) + CO2_eq_electricity = GHG_electricity*1 + + CO2_eq_WRRF = np.array([CH4_CO2_eq_treatment, N2O_CO2_eq_treatment, #1 + CH4_CO2_eq_AD, #2 + CH4_CO2_eq_discharge, N2O_CO2_eq_discharge, #3 + CH4_CO2_eq_sludge_disposal, #4 + CO2_eq_electricity]) #5 - CO2_eq_WRRF = np.array([CO2_eq_treatment, CO2_eq_AD, CO2_eq_discharge, CO2_eq_sludge_disposal, CO2_eq_electricity]) normalized_CO2_eq_WRRF = CO2_eq_WRRF/sum([24*s.F_vol for s in system.feeds]) return normalized_CO2_eq_WRRF \ No newline at end of file From 68da59c67539c7bd418a4cf780da5e753f242abe Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Thu, 1 Feb 2024 11:28:45 -0600 Subject: [PATCH 254/424] Quantifying operational costs Added function which calculates the sludge treatment and disposal costs, and another which compiles all operational costs. --- qsdsan/utils/wwt_design.py | 68 +++++++++++++++++++++++++++++++++++++- 1 file changed, 67 insertions(+), 1 deletion(-) diff --git a/qsdsan/utils/wwt_design.py b/qsdsan/utils/wwt_design.py index 8f1d1281..138f3cc8 100644 --- a/qsdsan/utils/wwt_design.py +++ b/qsdsan/utils/wwt_design.py @@ -21,7 +21,9 @@ 'get_airflow', 'get_P_blower', 'get_power_utility', + 'get_cost_sludge_disposal', 'get_normalized_energy', + 'get_daily_operational_cost', 'get_GHG_emissions_sec_treatment', 'get_GHG_emissions_discharge', 'get_GHG_emissions_electricity', @@ -307,6 +309,35 @@ def get_power_utility(system, active_unit_IDs=None): return power_consumption +def get_cost_sludge_disposal(system, sludge, unit_weight_disposal_cost = 400): + ''' + Parameters + ---------- + sludge : : iterable[:class:`WasteStream`], optional + Effluent sludge from the system for which treatment and disposal costs are being calculated. The default is None. + unit_weight_disposal_cost : float + The sludge treatment and disposal cost per unit weight (USD/ton). + The default is 400 USD/ton [1]. + Feasible range for this value lies between 300-800 USD/ton [1]. + + Returns + ------- + Cost of sludge treatment and disposal (USD/day). + + References + ------- + [1] Seiple, T. E., Skaggs, R. L., Fillmore, L., & Coleman, A. M. (2020). Municipal + wastewater sludge as a renewable, cost-effective feedstock for transportation + biofuels using hydrothermal liquefaction. Journal of Environmental Management, 270, 110852. + https://doi.org/10.1016/j.jenvman.2020.110852 + + ''' + + sludge_prod = np.array([sludge.composite('solids', True, particle_size='x', unit='ton/d') \ + for sludge in sludge]) # in ton/day + cost_sludge_disposal = np.sum(sludge_prod)*unit_weight_disposal_cost #in USD/day + return cost_sludge_disposal + def get_normalized_energy(system, aeration_power, pumping_power, miscellaneous_power): ''' Parameters @@ -330,10 +361,45 @@ def get_normalized_energy(system, aeration_power, pumping_power, miscellaneous_p normalized_pumping_energy = pumping_power/sum([s.F_vol for s in system.feeds]) normalized_miscellaneous_energy = miscellaneous_power/sum([s.F_vol for s in system.feeds]) - normalized_energy_WRRF = np.array([normalized_aeration_energy, normalized_pumping_energy, normalized_miscellaneous_energy]) + normalized_energy_WRRF = np.array([normalized_aeration_energy, normalized_pumping_energy, \ + normalized_miscellaneous_energy]) return normalized_energy_WRRF +def get_daily_operational_cost(system, aeration_power, pumping_power, miscellaneous_power, \ + sludge_disposal_cost, unit_electricity_cost = 0.161): + ''' + Parameters + ---------- + system : :class:`biosteam.System` + The system for which normalized energy consumption is being determined. + aeration_power : float, optional + Power of blower [kW]. + pumping_power : float, optional + Power rquired for sludge pumping and other equipments [kW]. + miscellaneous_power : float, optional + Any other power requirement in the system [kW]. + sludge_disposal_cost : float + Cost of sludge treatment and disposal (USD/day). + unit_electricity_cost : float + Unit cost of electricity. Default value is taken as $0.161/kWh [1]. + + Returns + ------- + Normalized operational cost associated with WRRF (USD/day). [numpy array] + + [1] https://www.bls.gov/regions/midwest/data/averageenergyprices_selectedareas_table.htm + + ''' + aeration_cost = aeration_power*24*unit_electricity_cost # in (kWh/day)*(USD/kWh) = USD/day + pumping_cost = pumping_power*24*unit_electricity_cost # in (kWh/day)*(USD/kWh) = USD/day + sludge_disposal_costs = sludge_disposal_cost + miscellaneous_cost = miscellaneous_power*24*unit_electricity_cost # in (kWh/day)*(USD/kWh) = USD/day + + operational_costs_WRRF = np.array([aeration_cost, pumping_cost, sludge_disposal_costs, miscellaneous_cost]) #5 + + return operational_costs_WRRF + def get_GHG_emissions_sec_treatment(system = None, influent=None, effluent = None, CH4_EF=0.0075, N2O_EF=0.016): ''' From 026cc1194e2a58683117b4a08e0fe9a4e0962762 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Thu, 1 Feb 2024 11:46:24 -0600 Subject: [PATCH 255/424] Updated wwt_design functions System is not required as an input to either of the functions. --- qsdsan/utils/wwt_design.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qsdsan/utils/wwt_design.py b/qsdsan/utils/wwt_design.py index 138f3cc8..0d7aa277 100644 --- a/qsdsan/utils/wwt_design.py +++ b/qsdsan/utils/wwt_design.py @@ -309,7 +309,7 @@ def get_power_utility(system, active_unit_IDs=None): return power_consumption -def get_cost_sludge_disposal(system, sludge, unit_weight_disposal_cost = 400): +def get_cost_sludge_disposal(sludge, unit_weight_disposal_cost = 400): ''' Parameters ---------- @@ -366,7 +366,7 @@ def get_normalized_energy(system, aeration_power, pumping_power, miscellaneous_p return normalized_energy_WRRF -def get_daily_operational_cost(system, aeration_power, pumping_power, miscellaneous_power, \ +def get_daily_operational_cost(aeration_power, pumping_power, miscellaneous_power, \ sludge_disposal_cost, unit_electricity_cost = 0.161): ''' Parameters From d648db5ed7ed4ed6f42cb6ac25803bbe0b856168 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Sun, 4 Feb 2024 11:50:14 -0600 Subject: [PATCH 256/424] Updated estimated range of sludge disposal costs Updated the estimated range of sludge disposal costs based on new suggested reference. --- qsdsan/utils/wwt_design.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/qsdsan/utils/wwt_design.py b/qsdsan/utils/wwt_design.py index 0d7aa277..72701c3c 100644 --- a/qsdsan/utils/wwt_design.py +++ b/qsdsan/utils/wwt_design.py @@ -314,11 +314,12 @@ def get_cost_sludge_disposal(sludge, unit_weight_disposal_cost = 400): Parameters ---------- sludge : : iterable[:class:`WasteStream`], optional - Effluent sludge from the system for which treatment and disposal costs are being calculated. The default is None. + Effluent sludge from the system for which treatment and disposal costs are being calculated. + The default is None. unit_weight_disposal_cost : float The sludge treatment and disposal cost per unit weight (USD/ton). - The default is 400 USD/ton [1]. - Feasible range for this value lies between 300-800 USD/ton [1]. + The default is 400 USD/ton. + Feasible range for this value lies between 110-890 USD/ton [1]. Returns ------- @@ -326,10 +327,10 @@ def get_cost_sludge_disposal(sludge, unit_weight_disposal_cost = 400): References ------- - [1] Seiple, T. E., Skaggs, R. L., Fillmore, L., & Coleman, A. M. (2020). Municipal - wastewater sludge as a renewable, cost-effective feedstock for transportation - biofuels using hydrothermal liquefaction. Journal of Environmental Management, 270, 110852. - https://doi.org/10.1016/j.jenvman.2020.110852 + [1] Feng, J., Li, Y., Strathmann, T. J., & Guest, J. S. (2024b). + Characterizing the opportunity space for sustainable hydrothermal valorization + of Wet Organic Wastes. Environmental Science & Technology. + https://doi.org/10.1021/acs.est.3c07394 ''' @@ -371,8 +372,6 @@ def get_daily_operational_cost(aeration_power, pumping_power, miscellaneous_powe ''' Parameters ---------- - system : :class:`biosteam.System` - The system for which normalized energy consumption is being determined. aeration_power : float, optional Power of blower [kW]. pumping_power : float, optional From 2c5fb7e3d21a1e316ea5660185faf30eb779ec27 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Sun, 4 Feb 2024 13:10:27 -0600 Subject: [PATCH 257/424] Updated GHG emissions from electricity The equivalent GHG emissions are updated assuming medium voltage. --- qsdsan/utils/wwt_design.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/qsdsan/utils/wwt_design.py b/qsdsan/utils/wwt_design.py index 72701c3c..8fe6c10a 100644 --- a/qsdsan/utils/wwt_design.py +++ b/qsdsan/utils/wwt_design.py @@ -487,7 +487,7 @@ def get_GHG_emissions_discharge(effluent=None, CH4_EF=0.0075, N2O_EF=0.016): return CH4_emitted, N2O_emitted -def get_GHG_emissions_electricity(system, power_blower, power_pump, CO2_EF=0.668): +def get_GHG_emissions_electricity(system, power_blower, power_pump, CO2_EF=0.675): ''' Parameters ---------- @@ -499,17 +499,20 @@ def get_GHG_emissions_electricity(system, power_blower, power_pump, CO2_EF=0.668 Power required for pumping and other utilities at treatment facility [kW]. CO2_EF : float The emission factor used to calculate tier-2 CO2 emissions due to electricity consumption. - The default is 0.668 kg-CO2-Eq/kWh. [1] + The default is 0.675 kg-CO2-Eq/kWh. [1] The emission factor is dependent on the region, and is as follows for USA: - {SERC Reliability Corporation (SERC): 0.612 kg-CO2-Eq/kWh - ReliabilityFirst (RFC): 0.618 kg-CO2-Eq/kWh - Western Electricity Coordinating Council (WECC): 0.428 kg-CO2-Eq/kWh - Texas Reliability Entity (TRE): 0.565 kg-CO2-Eq/kWh - Southwest Power Pool (SPP): 0.724 kg-CO2-Eq/kWh - Midwest Reliability Organization (MRO): 0.668 kg-CO2-Eq/kWh + {SERC Reliability Corporation (SERC): 0.618 kg-CO2-Eq/kWh + ReliabilityFirst (RFC): 0.619 kg-CO2-Eq/kWh + Western Electricity Coordinating Council (WECC): 0.436 kg-CO2-Eq/kWh + Texas Reliability Entity (TRE): 0.574 kg-CO2-Eq/kWh + Southwest Power Pool (SPP): 0.733 kg-CO2-Eq/kWh + Midwest Reliability Organization (MRO): 0.675 kg-CO2-Eq/kWh Florida Reliability Coordinating Council (FRCC): 0.531 kg-CO2-Eq/kWh - Northeast Power Coordinating Council (NPCC): 0.242 kg-CO2-Eq/kWh} + Northeast Power Coordinating Council (NPCC): 0.244 kg-CO2-Eq/kWh} + (HICC): 0.814 kg-CO2-Eq/kWh} + (ASCC): 0.599 kg-CO2-Eq/kWh} + Returns ------- From 62e7311f8390616b64ebb1bad2531a7d2a3e649c Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Mon, 5 Feb 2024 14:42:15 -0600 Subject: [PATCH 258/424] Updated sludge management costs Updated sludge management costs. --- qsdsan/utils/wwt_design.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/qsdsan/utils/wwt_design.py b/qsdsan/utils/wwt_design.py index 8fe6c10a..bc8a1f37 100644 --- a/qsdsan/utils/wwt_design.py +++ b/qsdsan/utils/wwt_design.py @@ -318,8 +318,13 @@ def get_cost_sludge_disposal(sludge, unit_weight_disposal_cost = 400): The default is None. unit_weight_disposal_cost : float The sludge treatment and disposal cost per unit weight (USD/ton). - The default is 400 USD/ton. - Feasible range for this value lies between 110-890 USD/ton [1]. + Feasible range for this value lies between 110-880 USD/ton [1]. + + Land application: 330 - 880 USD/ton. [2] + Landfill: 110 - 715 USD/ton. [2] + Incineration: 330 - 550 USD/ton. [2] + + The default is 400 USD/ton, which is the close to average of lanfill. Returns ------- @@ -329,8 +334,12 @@ def get_cost_sludge_disposal(sludge, unit_weight_disposal_cost = 400): ------- [1] Feng, J., Li, Y., Strathmann, T. J., & Guest, J. S. (2024b). Characterizing the opportunity space for sustainable hydrothermal valorization - of Wet Organic Wastes. Environmental Science & Technology. + of Wet Organic Wastes. Environmental Science &; Technology. https://doi.org/10.1021/acs.est.3c07394 + + [2] Peccia, J., & Westerhoff, P. (2015). We should expect more out of our sewage + sludge. Environmental Science & Technology, 49(14), 8271–8276. + https://doi.org/10.1021/acs.est.5b01931 ''' From 0800ffd645519050bffd8953d62c5eab25de02a0 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Mon, 5 Feb 2024 17:17:03 -0600 Subject: [PATCH 259/424] Accounted for methane emission from leftover sludge Also added methane emissions due to leftover sludge at the end of project lifetime. --- qsdsan/utils/wwt_design.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/qsdsan/utils/wwt_design.py b/qsdsan/utils/wwt_design.py index bc8a1f37..c7604dc7 100644 --- a/qsdsan/utils/wwt_design.py +++ b/qsdsan/utils/wwt_design.py @@ -338,7 +338,7 @@ def get_cost_sludge_disposal(sludge, unit_weight_disposal_cost = 400): https://doi.org/10.1021/acs.est.3c07394 [2] Peccia, J., & Westerhoff, P. (2015). We should expect more out of our sewage - sludge. Environmental Science & Technology, 49(14), 8271–8276. + sludge. Environmental Science & Technology, 49(14), 8271–8276. https://doi.org/10.1021/acs.est.5b01931 ''' @@ -602,12 +602,16 @@ def get_GHG_emissions_sludge_disposal(sludge=None, DOC_f = 0.5, MCF = 0.8, k = 0 # # replace t with DOC_ARRAY t_vary = np.arange(pl + 1) - acc_DOC = annual_DDOC * (1 - np.exp(-1 * k * t_vary)) - decomposed_DOC = np.sum(acc_DOC) - CH4_emitted = decomposed_DOC*F*16/12 + decomposed_DOC = annual_DDOC * (1 - np.exp(-1 * k * t_vary)) + total_decomposed_DOC = np.sum(decomposed_DOC) + CH4_emitted_during_pl = total_decomposed_DOC*F*16/12 + + accumulated_DOC_at_pl = annual_DDOC* (1 - np.exp(-1 * k * pl)) / (1 - np.exp(-1 * k)) + CH4_emitted_after_pl = accumulated_DOC_at_pl*F*16/12 + days_in_year = 365 - return CH4_emitted/(pl*days_in_year) + return CH4_emitted_during_pl/(pl*days_in_year), CH4_emitted_after_pl/(pl*days_in_year) def get_CO2_eq_WRRF (system, GHG_treatment, GHG_discharge, GHG_electricity, GHG_sludge_disposal, GHG_AD, CH4_CO2eq=29.8, N2O_CO2eq=273): @@ -654,7 +658,8 @@ def get_CO2_eq_WRRF (system, GHG_treatment, GHG_discharge, GHG_electricity, N2O_CO2_eq_discharge = GHG_discharge[1]*N2O_CO2eq # source 4 (off-site) - CH4_CO2_eq_sludge_disposal = GHG_sludge_disposal*CH4_CO2eq + CH4_CO2_eq_sludge_disposal_pl = GHG_sludge_disposal[0]*CH4_CO2eq + CH4_CO2_eq_sludge_disposal_after_pl = GHG_sludge_disposal[1]*CH4_CO2eq # source 5 (off-site) CO2_eq_electricity = GHG_electricity*1 @@ -662,7 +667,8 @@ def get_CO2_eq_WRRF (system, GHG_treatment, GHG_discharge, GHG_electricity, CO2_eq_WRRF = np.array([CH4_CO2_eq_treatment, N2O_CO2_eq_treatment, #1 CH4_CO2_eq_AD, #2 CH4_CO2_eq_discharge, N2O_CO2_eq_discharge, #3 - CH4_CO2_eq_sludge_disposal, #4 + CH4_CO2_eq_sludge_disposal_pl, #4 + CH4_CO2_eq_sludge_disposal_after_pl, #4 CO2_eq_electricity]) #5 normalized_CO2_eq_WRRF = CO2_eq_WRRF/sum([24*s.F_vol for s in system.feeds]) From 603bff9449d5258ec74ca3b0a1bf44389ae2abe6 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Tue, 13 Feb 2024 16:34:40 -0600 Subject: [PATCH 260/424] Satisfy mass balance Changed the limits of analysis to satisfy mass balance. --- qsdsan/utils/wwt_design.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/qsdsan/utils/wwt_design.py b/qsdsan/utils/wwt_design.py index c7604dc7..0baa3c25 100644 --- a/qsdsan/utils/wwt_design.py +++ b/qsdsan/utils/wwt_design.py @@ -345,7 +345,9 @@ def get_cost_sludge_disposal(sludge, unit_weight_disposal_cost = 400): sludge_prod = np.array([sludge.composite('solids', True, particle_size='x', unit='ton/d') \ for sludge in sludge]) # in ton/day + cost_sludge_disposal = np.sum(sludge_prod)*unit_weight_disposal_cost #in USD/day + return cost_sludge_disposal def get_normalized_energy(system, aeration_power, pumping_power, miscellaneous_power): @@ -601,17 +603,19 @@ def get_GHG_emissions_sludge_disposal(sludge=None, DOC_f = 0.5, MCF = 0.8, k = 0 # # replace t with DOC_ARRAY - t_vary = np.arange(pl + 1) + t_vary = np.arange(pl) decomposed_DOC = annual_DDOC * (1 - np.exp(-1 * k * t_vary)) total_decomposed_DOC = np.sum(decomposed_DOC) CH4_emitted_during_pl = total_decomposed_DOC*F*16/12 - accumulated_DOC_at_pl = annual_DDOC* (1 - np.exp(-1 * k * pl)) / (1 - np.exp(-1 * k)) + accumulated_DOC_at_pl = annual_DDOC* (1 - np.exp(-1 * k * (pl-1))) / (1 - np.exp(-1 * k)) CH4_emitted_after_pl = accumulated_DOC_at_pl*F*16/12 + total_CH4 = annual_DDOC*(pl)*F*16/12 + days_in_year = 365 - return CH4_emitted_during_pl/(pl*days_in_year), CH4_emitted_after_pl/(pl*days_in_year) + return CH4_emitted_during_pl/(pl*days_in_year), CH4_emitted_after_pl/(pl*days_in_year), total_CH4/(pl*days_in_year) def get_CO2_eq_WRRF (system, GHG_treatment, GHG_discharge, GHG_electricity, GHG_sludge_disposal, GHG_AD, CH4_CO2eq=29.8, N2O_CO2eq=273): From 3bb7e52c6b991916cfad451328044d2cd90a5b50 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Fri, 16 Feb 2024 13:09:36 -0600 Subject: [PATCH 261/424] Updated junction from bsm2 Copied changes made by @joyxyz1994 on the bsm2 branch. --- qsdsan/sanunits/_junction.py | 42 +++++++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/qsdsan/sanunits/_junction.py b/qsdsan/sanunits/_junction.py index 143f3280..f8f8c62f 100644 --- a/qsdsan/sanunits/_junction.py +++ b/qsdsan/sanunits/_junction.py @@ -394,6 +394,10 @@ class ADMtoASM(ADMjunction): # Should be constants cod_vfa = np.array([64, 112, 160, 208]) + # whether to conserve the nitrogen split between soluble and particulate components + conserve_particulate_N = False + + def isbalanced(self, lhs, rhs_vals, rhs_i): rhs = sum(rhs_vals*rhs_i) error = rhs - lhs @@ -473,6 +477,7 @@ def _compile_reactions(self): alpha_IC = self.alpha_IC alpha_vfa = self.alpha_vfa f_corr = self.balance_cod_tkn + conserve_particulate_N = self.conserve_particulate_N def adm2asm(adm_vals): S_su, S_aa, S_fa, S_va, S_bu, S_pro, S_ac, S_h2, S_ch4, S_IC, S_IN, S_I, \ @@ -497,20 +502,33 @@ def adm2asm(adm_vals): X_P = xp_cod bio_n -= xp_ndm X_S = bio_cod - X_P - xs_ndm = X_S*X_S_i_N - if xs_ndm <= bio_n: - X_ND = bio_n - xs_ndm - bio_n = 0 - elif xs_ndm <= bio_n + S_IN: - X_ND = 0 - S_IN -= (xs_ndm - bio_n) - bio_n = 0 + if conserve_particulate_N: + xs_ndm = X_S*X_S_i_N + if xs_ndm <= bio_n: + X_ND = bio_n - xs_ndm + bio_n = 0 + elif xs_ndm <= bio_n + S_IN: + X_ND = 0 + S_IN -= (xs_ndm - bio_n) + bio_n = 0 + else: + if isclose(xs_ndm, bio_n + S_IN, rel_tol=rtol, abs_tol=atol): + X_ND = S_IN = bio_n = 0 + else: + raise RuntimeError('Not enough nitrogen (S_IN + biomass) to map ' + 'all biomass COD into X_P and X_S') else: - if isclose(xs_ndm, bio_n + S_IN, rel_tol=rtol, abs_tol=atol): - X_ND = S_IN = bio_n = 0 + xs_ndm = X_S*X_c_i_N # requires X_S.i_N = 0 + if xs_ndm <= bio_n + S_IN: + X_ND = xs_ndm + S_IN += bio_n - X_ND + bio_n = 0 else: - raise RuntimeError('Not enough nitrogen (S_IN + biomass) to map ' - 'all biomass COD into X_P and X_S') + if isclose(xs_ndm, bio_n + S_IN, rel_tol=rtol, abs_tol=atol): + X_ND = S_IN = bio_n = 0 + else: + raise RuntimeError('Not enough nitrogen (S_IN + biomass) to map ' + 'all biomass COD into X_P and X_S') # Step 1b: convert particulate substrates into X_S + X_ND xsub_cod = X_c + X_ch + X_pr + X_li From 49aa0b4cfe619716f9a6c48136e9db52eef59fa6 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Fri, 16 Feb 2024 13:09:45 -0600 Subject: [PATCH 262/424] Null Null --- qsdsan/sanunits/_sludge_treatment.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py index 72b9dc3d..dfe27fc7 100644 --- a/qsdsan/sanunits/_sludge_treatment.py +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -1203,7 +1203,4 @@ def yt(t, QC_ins, dQC_ins): self._cached_t = t _update_state() _update_dstate() - self._AE = yt - - - \ No newline at end of file + self._AE = yt \ No newline at end of file From 8dd776c75e3ea162e0a28f7bb7652758148f08e1 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Fri, 16 Feb 2024 13:10:19 -0600 Subject: [PATCH 263/424] Updated cost estimates due to unit Changed cost estimates based on unit correction. --- qsdsan/utils/wwt_design.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/qsdsan/utils/wwt_design.py b/qsdsan/utils/wwt_design.py index 0baa3c25..6fcb2f94 100644 --- a/qsdsan/utils/wwt_design.py +++ b/qsdsan/utils/wwt_design.py @@ -30,6 +30,7 @@ 'get_GHG_emissions_sludge_disposal', 'get_CO2_eq_WRRF') + #%% def get_SRT(system, biomass_IDs, wastage=None, active_unit_IDs=None): @@ -320,11 +321,11 @@ def get_cost_sludge_disposal(sludge, unit_weight_disposal_cost = 400): The sludge treatment and disposal cost per unit weight (USD/ton). Feasible range for this value lies between 110-880 USD/ton [1]. - Land application: 330 - 880 USD/ton. [2] - Landfill: 110 - 715 USD/ton. [2] - Incineration: 330 - 550 USD/ton. [2] + Land application: 300 - 800 USD/US ton. [2] + Landfill: 100 - 650 USD/US ton. [2] + Incineration: 300 - 500 USD/US ton. [2] - The default is 400 USD/ton, which is the close to average of lanfill. + The default is 375 USD/US ton, which is the close to average of lanfill. Returns ------- From ace59abbf11634e11a34d7efcbb592189bb5e9b8 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Mon, 19 Feb 2024 14:14:40 -0600 Subject: [PATCH 264/424] Facilitating uncertainty analysis 1. Added functions to facilitate uncertainty analysis on total operational cost and GHG emissions. 2. Corrected EFs for discharge based on IPCC report. 3. Made other minor changes. --- qsdsan/utils/wwt_design.py | 346 +++++++++++++++++++++++++++++++++++-- 1 file changed, 327 insertions(+), 19 deletions(-) diff --git a/qsdsan/utils/wwt_design.py b/qsdsan/utils/wwt_design.py index 6fcb2f94..a2372bd8 100644 --- a/qsdsan/utils/wwt_design.py +++ b/qsdsan/utils/wwt_design.py @@ -24,11 +24,13 @@ 'get_cost_sludge_disposal', 'get_normalized_energy', 'get_daily_operational_cost', + 'get_total_operational_cost', 'get_GHG_emissions_sec_treatment', 'get_GHG_emissions_discharge', 'get_GHG_emissions_electricity', 'get_GHG_emissions_sludge_disposal', - 'get_CO2_eq_WRRF') + 'get_CO2_eq_WRRF', + 'get_total_CO2_eq') #%% @@ -235,7 +237,6 @@ def get_airflow(oxygen_heterotrophs, oxygen_autotrophs, oxygen_transfer_efficien return Q_air - def get_P_blower(q_air, T=20, p_atm=101.325, P_inlet_loss=1, P_diffuser_loss=7, h_submergance=5.18, efficiency=0.7, K=0.283): @@ -299,7 +300,7 @@ def get_power_utility(system, active_unit_IDs=None): Returns ------- - Power of blower [kW]. + Cumulative power of sludge pumps [kW]. ''' power_consumption = 0 @@ -310,7 +311,7 @@ def get_power_utility(system, active_unit_IDs=None): return power_consumption -def get_cost_sludge_disposal(sludge, unit_weight_disposal_cost = 400): +def get_cost_sludge_disposal(sludge, unit_weight_disposal_cost = 375): ''' Parameters ---------- @@ -319,7 +320,7 @@ def get_cost_sludge_disposal(sludge, unit_weight_disposal_cost = 400): The default is None. unit_weight_disposal_cost : float The sludge treatment and disposal cost per unit weight (USD/ton). - Feasible range for this value lies between 110-880 USD/ton [1]. + Feasible range for this value lies between 100-800 USD/ton [1]. Land application: 300 - 800 USD/US ton. [2] Landfill: 100 - 650 USD/US ton. [2] @@ -410,7 +411,112 @@ def get_daily_operational_cost(aeration_power, pumping_power, miscellaneous_powe operational_costs_WRRF = np.array([aeration_cost, pumping_cost, sludge_disposal_costs, miscellaneous_cost]) #5 return operational_costs_WRRF + +def get_total_operational_cost(q_air, # aeration (blower) power + sludge, # sludge disposal costs + system, active_unit_IDs=None, # sludge pumping power + T=20, p_atm=101.325, P_inlet_loss=1, P_diffuser_loss=7, + h_submergance=5.18, efficiency=0.7, K=0.283, # aeration (blower) power + miscellaneous_power = 0, + unit_weight_disposal_cost = 375, # sludge disposal costs + unit_electricity_cost = 0.161): + ''' + Parameters + ---------- + + q_air : float + Air volumetric flow rate [m3/min]. + T : float + Air temperature [degree Celsius]. + p_atm : float + Atmostpheric pressure [kPa] + P_inlet_loss : float + Head loss at inlet [kPa]. The default is 1 kPa. + P_diffuser_loss : float + Head loss due to piping and diffuser [kPa]. The default is 7 kPa. + h_submergance : float + Diffuser submergance depth in m. The default is 17 feet (5.18 m) + efficiency : float + Blower efficiency. Default is 0.7. + K : float, optional + Equal to (kappa - 1)/kappa, where kappa is the adiabatic exponent of air. + Default is 0.283, equivalent to kappa = 1.3947. + + ------------------------------------------------------------------------------ + + system : :class:`biosteam.System` + The system whose power will be calculated. + active_unit_IDs : tuple[str], optional + IDs of all units whose power needs to be accounted for. The default is None. + + ------------------------------------------------------------------------------ + + sludge : : iterable[:class:`WasteStream`], optional + Effluent sludge from the system for which treatment and disposal costs are being calculated. + The default is None. + unit_weight_disposal_cost : float + The sludge treatment and disposal cost per unit weight (USD/ton). + Feasible range for this value lies between 100-800 USD/ton [1]. + + Land application: 300 - 800 USD/US ton. [2] + Landfill: 100 - 650 USD/US ton. [2] + Incineration: 300 - 500 USD/US ton. [2] + + The default is 375 USD/US ton, which is the close to average of lanfill. + + ------------------------------------------------------------------------------ + + miscellaneous_power : float, optional + Any other power requirement in the system [kW]. + + unit_electricity_cost : float + Unit cost of electricity. Default value is taken as $0.161/kWh [1]. + + Returns + ------- + Normalized operational cost associated with WRRF (USD/day). [int] + + ''' + T += 273.15 + air_molar_vol = 22.4e-3 * (T/273.15)/(p_atm/101.325) # m3/mol + R = 8.31446261815324 # J/mol/K, universal gas constant + + p_in = p_atm - P_inlet_loss + p_out = p_atm + 9.81*h_submergance + P_diffuser_loss + + # Q_air = q_air*(24*60) # m3/min to m3/day + # P_blower = 1.4161e-5*(T + 273.15)*Q_air*((p_out/p_in)**0.283 - 1)/efficiency + # return P_blower + + Q_air = q_air/60 # m3/min to m3/s + WP = (Q_air / air_molar_vol) * R * T / K * ((p_out/p_in)**K - 1) / efficiency # in W + + aeration_power = WP/1000 # in kW + + power_consumption = 0 + + for y in system.flowsheet.unit: + if y.ID in active_unit_IDs: + power_consumption += y.power_utility.power + + pumping_power = power_consumption + + aeration_cost = aeration_power*24*unit_electricity_cost # in (kWh/day)*(USD/kWh) = USD/day + pumping_cost = pumping_power*24*unit_electricity_cost # in (kWh/day)*(USD/kWh) = USD/day + + sludge_prod = np.array([sludge.composite('solids', True, particle_size='x', unit='ton/d') \ + for sludge in sludge]) # in ton/day + sludge_disposal_costs = np.sum(sludge_prod)*unit_weight_disposal_cost #in USD/day + + miscellaneous_cost = miscellaneous_power*24*unit_electricity_cost # in (kWh/day)*(USD/kWh) = USD/day + + operational_costs_WRRF = np.array([aeration_cost, pumping_cost, sludge_disposal_costs, miscellaneous_cost]) + + total_operational_cost = np.sum(operational_costs_WRRF) #5 + + return total_operational_cost + def get_GHG_emissions_sec_treatment(system = None, influent=None, effluent = None, CH4_EF=0.0075, N2O_EF=0.016): ''' @@ -461,7 +567,7 @@ def get_GHG_emissions_sec_treatment(system = None, influent=None, effluent = Non return CH4_emitted, N2O_emitted -def get_GHG_emissions_discharge(effluent=None, CH4_EF=0.0075, N2O_EF=0.016): +def get_GHG_emissions_discharge(effluent=None, CH4_EF=0.009, N2O_EF=0.005): ''' Parameters ---------- @@ -538,9 +644,7 @@ def get_GHG_emissions_electricity(system, power_blower, power_pump, CO2_EF=0.675 return CO2_emissions -# r_VSS_TSS, r_BOD_VSS - -def get_GHG_emissions_sludge_disposal(sludge=None, DOC_f = 0.5, MCF = 0.8, k = 0.185, F=0.5, pl=30): +def get_GHG_emissions_sludge_disposal(sludge=None, DOC_f = 0.38, MCF = 0.8, k = 0.06, F=0.5, pl=30): ''' Parameters ---------- @@ -612,14 +716,12 @@ def get_GHG_emissions_sludge_disposal(sludge=None, DOC_f = 0.5, MCF = 0.8, k = 0 accumulated_DOC_at_pl = annual_DDOC* (1 - np.exp(-1 * k * (pl-1))) / (1 - np.exp(-1 * k)) CH4_emitted_after_pl = accumulated_DOC_at_pl*F*16/12 - total_CH4 = annual_DDOC*(pl)*F*16/12 - days_in_year = 365 - return CH4_emitted_during_pl/(pl*days_in_year), CH4_emitted_after_pl/(pl*days_in_year), total_CH4/(pl*days_in_year) + return CH4_emitted_during_pl/(pl*days_in_year), CH4_emitted_after_pl/(pl*days_in_year) def get_CO2_eq_WRRF (system, GHG_treatment, GHG_discharge, GHG_electricity, - GHG_sludge_disposal, GHG_AD, CH4_CO2eq=29.8, N2O_CO2eq=273): + GHG_sludge_disposal, CH4_CO2eq=29.8, N2O_CO2eq=273): ''' Parameters @@ -634,8 +736,6 @@ def get_CO2_eq_WRRF (system, GHG_treatment, GHG_discharge, GHG_electricity, The amount of eq. CO2 emitted due to electrity consumption (kg-CO2-Eq/day). GHG_sludge_disposal : int The average amount of methane emitted during sludge disposal (kg/day). - GHG_AD : int - The average amount of methane emitted during anaerobic (kg/day). CH4_CO2eq : TYPE, optional DESCRIPTION. The default is 29.8 kg CO2eq/kg CH4 [1]. N2O_CO2eq : TYPE, optional @@ -655,8 +755,6 @@ def get_CO2_eq_WRRF (system, GHG_treatment, GHG_discharge, GHG_electricity, CH4_CO2_eq_treatment = GHG_treatment[0]*CH4_CO2eq N2O_CO2_eq_treatment = GHG_treatment[1]*N2O_CO2eq - # source 2 (on-site) - CH4_CO2_eq_AD = GHG_AD*CH4_CO2eq # source 3 (off-site) CH4_CO2_eq_discharge = GHG_discharge[0]*CH4_CO2eq @@ -670,7 +768,6 @@ def get_CO2_eq_WRRF (system, GHG_treatment, GHG_discharge, GHG_electricity, CO2_eq_electricity = GHG_electricity*1 CO2_eq_WRRF = np.array([CH4_CO2_eq_treatment, N2O_CO2_eq_treatment, #1 - CH4_CO2_eq_AD, #2 CH4_CO2_eq_discharge, N2O_CO2_eq_discharge, #3 CH4_CO2_eq_sludge_disposal_pl, #4 CH4_CO2_eq_sludge_disposal_after_pl, #4 @@ -678,4 +775,215 @@ def get_CO2_eq_WRRF (system, GHG_treatment, GHG_discharge, GHG_electricity, normalized_CO2_eq_WRRF = CO2_eq_WRRF/sum([24*s.F_vol for s in system.feeds]) - return normalized_CO2_eq_WRRF \ No newline at end of file + return normalized_CO2_eq_WRRF + +def get_total_CO2_eq(system, q_air, influent_sc =None, effluent_sc = None, effluent_sys =None, active_unit_IDs=None, sludge=None, + CH4_EF_sc =0.0075, N2O_EF_sc =0.016, CH4_EF_discharge=0.009, N2O_EF_discharge=0.005, + T=20, p_atm=101.325, K=0.283, F=0.5,CH4_CO2eq=29.8, N2O_CO2eq=273, + + # uncertain parameters + P_inlet_loss=1, P_diffuser_loss=7, h_submergance=5.18, efficiency=0.7, + + CO2_EF=0.675, DOC_f = 0.38, MCF = 0.8, k = 0.06, pl=30 + ): + + ''' + + Parameters + ---------- + system : :class:`biosteam.System` + The system for which normalized GHG emission is being determined. + + ----Secondary treatment---- + + influent_sc : : iterable[:class:`WasteStream`], optional + Influent wastestreams to secondary treatment whose wastewater composition determine the potential for GHG emissions. The default is None. + effluent_sc : : iterable[:class:`WasteStream`], optional + The wastestreams which represents the effluent from the secondary treatment process. The default is None. + CH4_EF_sc : float, optional. + The emission factor used to calculate methane emissions in secondary treatment. The default is 0.0075 kg CH4/ kg rCOD. [1] + N2O_EF_sc : float, optional + The emission factor used to calculate nitrous oxide emissions in secondary treatment. The default is 0.016 kg N2O-N/ kg N. [1] + + ----Discharge---- + + effluent_sys : : iterable[:class:`WasteStream`], optional + Effluent wastestreams from the system whose wastewater composition determine the potential for GHG emissions at discharge. The default is None. + CH4_EF_discharge : float, optional. + The emission factor used to calculate methane emissions in secondary treatment. The default is 0.0075 kg CH4/ kg rCOD. [1] + N2O_EF_discharge : float, optional + The emission factor used to calculate nitrous oxide emissions in secondary treatment. The default is 0.016 kg N2O-N/ kg N. [1] + + + ----Electricity--- + + --blower power- + q_air : float + Air volumetric flow rate [m3/min]. + T : float + Air temperature [degree Celsius]. + p_atm : float + Atmostpheric pressure [kPa] + P_inlet_loss : float + Head loss at inlet [kPa]. + P_diffuser_loss : float + Head loss due to piping and diffuser [kPa]. + h_submergance : float + Diffuser submergance depth in m. The default is 17 feet (5.18 m) + efficiency : float + Blower efficiency. Default is 0.7. + K : float, optional + Equal to (kappa - 1)/kappa, where kappa is the adiabatic exponent of air. + Default is 0.283, equivalent to kappa = 1.3947. + + --pump power-- + active_unit_IDs : tuple[str], optional + IDs of all units whose power needs to be accounted for. The default is None. + + + ----Sludge disposal--- + + sludge : : iterable[:class:`WasteStream`], optional + Effluent sludge from the system for which GHG emissions are being calculated. The default is None. + DOC_f : float, optional + fraction of DOC that can decompose (fraction). The default value is 0.5. + MCF : float, optional + CH4 correction factor for aerobic decomposition in the year of deposition (fraction). The default is 0.8. + k : TYPE, optional + Methane generation rate (k). The default is 0.185. (1/year) + + The decomposition of carbon is assumed to follow first-order kinetics + (with rate constant k), and methane generation is dependent on the amount of remaining decomposable carbon + in the waste. For North America (boreal and temperate climate) the default values for wet and dry climate + are: + k (dry climate) = 0.06 + k (wet climate) = 0.185 + F : float, optional + Fraction of methane in generated landfill gas (volume fraction). The default is 0.5. + pl : float, optional + The project lifetime over which methane emissions would be calculated. (years) + The default is 30 years. + + + --------- Eq CO2 EFs --------- + + CH4_CO2eq : TYPE, optional + DESCRIPTION. The default is 29.8 kg CO2eq/kg CH4 [1]. + N2O_CO2eq : TYPE, optional + DESCRIPTION. The default is 273 kg CO2eq/kg CH4 [1]. + + Returns + ------- + Total Normalized GHG emissions from onsite and offsite operations associated with WRRF (kg CO2 eq./m3) [int]. + + + ''' + + # source 1 (on-site) + if influent_sc is None: + influent_sc = [inf for inf in system.feeds if inf.phase == 'l'] + + influent_sc_flow = np.array([inf.F_vol*24 for inf in influent_sc]) # in m3/day + influent_sc_COD = np.array([inf.COD for inf in influent_sc]) # in mg/L + mass_influent_COD_sc = np.sum(influent_sc_flow*influent_sc_COD/1000) # in kg/day + + effluent_sc_flow = np.array([eff.F_vol*24 for eff in effluent_sc]) + effluent_sc_COD = np.array([eff.COD for eff in effluent_sc]) # in mg/L + mass_effluent_COD_sc = np.sum(effluent_sc_flow*effluent_sc_COD/1000) # in kg/day + + mass_removed_COD_sc = mass_influent_COD_sc - mass_effluent_COD_sc + CH4_emitted_sc = CH4_EF_sc*mass_removed_COD_sc + + influent_N_sc = np.array([inf.TN for inf in influent_sc]) # in mg/L + mass_influent_N = np.sum(influent_sc_flow*influent_N_sc/1000) # in kg/day + + N2O_emitted_sc = N2O_EF_sc*mass_influent_N + + # source 1 (on-site) + CH4_CO2_eq_treatment = CH4_emitted_sc*CH4_CO2eq + N2O_CO2_eq_treatment = N2O_emitted_sc*N2O_CO2eq + + # source 3 (off-site) + effluent_flow = np.array([eff.F_vol*24 for eff in effluent_sys]) # in m3/day + effluent_COD = np.array([eff.COD for eff in effluent_sys]) # in mg/L + mass_effluent_COD = np.sum(effluent_flow*effluent_COD/1000) # in kg/day + + CH4_emitted_discharge = CH4_EF_discharge*mass_effluent_COD + + effluent_N = np.array([eff.TN for eff in effluent_sys]) # in mg/L + mass_effluent_N = np.sum(effluent_flow*effluent_N/1000) # in kg/day + + N2O_emitted_discharge = N2O_EF_discharge*mass_effluent_N + + # source 3 (off-site) + CH4_CO2_eq_discharge = CH4_emitted_discharge*CH4_CO2eq + N2O_CO2_eq_discharge = N2O_emitted_discharge*N2O_CO2eq + + + # source 5 (off-site) + T += 273.15 + air_molar_vol = 22.4e-3 * (T/273.15)/(p_atm/101.325) # m3/mol + R = 8.31446261815324 # J/mol/K, universal gas constant + + p_in = p_atm - P_inlet_loss + p_out = p_atm + 9.81*h_submergance + P_diffuser_loss + + # Q_air = q_air*(24*60) # m3/min to m3/day + # P_blower = 1.4161e-5*(T + 273.15)*Q_air*((p_out/p_in)**0.283 - 1)/efficiency + # return P_blower + + Q_air = q_air/60 # m3/min to m3/s + WP = (Q_air / air_molar_vol) * R * T / K * ((p_out/p_in)**K - 1) / efficiency # in W + + blower_power = WP/1000 + + pumping_power = 0 + + for y in system.flowsheet.unit: + if y.ID in active_unit_IDs: + pumping_power += y.power_utility.power # in kW + + total_energy_consumed = (blower_power + pumping_power)*24 # in kWh/day + + GHG_electricity = total_energy_consumed*CO2_EF # in kg-CO2-Eq/day + + # source 5 (off-site) + CO2_eq_electricity = GHG_electricity*1 + + # source 4 (off-site) + + DOC_mass_flow = 0 + + for slg in sludge: + DOC_mass_flow += slg.composite("C", flow=True, exclude_gas=True, + subgroup=None, particle_size=None, + degradability="b", organic=True, volatile=None, + specification=None, unit="kg/day") + + annual_DOC_mass = 365*DOC_mass_flow # in kg/year + + annual_DDOC = annual_DOC_mass*DOC_f*MCF + + t_vary = np.arange(pl) + decomposed_DOC = annual_DDOC * (1 - np.exp(-1 * k * t_vary)) + total_decomposed_DOC = np.sum(decomposed_DOC) + CH4_emitted_during_pl = total_decomposed_DOC*F*16/12 + + accumulated_DOC_at_pl = annual_DDOC* (1 - np.exp(-1 * k * (pl-1))) / (1 - np.exp(-1 * k)) + CH4_emitted_after_pl = accumulated_DOC_at_pl*F*16/12 + + days_in_year = 365 + + GHG_sludge_disposal = (CH4_emitted_during_pl + CH4_emitted_after_pl)/(pl*days_in_year) + + CH4_CO2_eq_sludge_disposal_pl = GHG_sludge_disposal*CH4_CO2eq + + + CO2_eq_WRRF = np.array([CH4_CO2_eq_treatment, N2O_CO2_eq_treatment, #1 + CH4_CO2_eq_discharge, N2O_CO2_eq_discharge, #3 + CH4_CO2_eq_sludge_disposal_pl, #4 + CO2_eq_electricity]) #5 + + normalized_total_CO2_eq_WRRF = np.sum(CO2_eq_WRRF)/sum([24*s.F_vol for s in system.feeds]) + + return normalized_total_CO2_eq_WRRF \ No newline at end of file From 18259f922e8d3292862fb782b972873b2d2a5f78 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Tue, 20 Feb 2024 08:37:56 -0600 Subject: [PATCH 265/424] Corrected typos in documentation Edited out typos in documentation. --- qsdsan/utils/wwt_design.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/qsdsan/utils/wwt_design.py b/qsdsan/utils/wwt_design.py index a2372bd8..37b9bdb3 100644 --- a/qsdsan/utils/wwt_design.py +++ b/qsdsan/utils/wwt_design.py @@ -573,10 +573,10 @@ def get_GHG_emissions_discharge(effluent=None, CH4_EF=0.009, N2O_EF=0.005): ---------- effluent : : iterable[:class:`WasteStream`], optional Effluent wastestreams from the system whose wastewater composition determine the potential for GHG emissions at discharge. The default is None. - CH4_EF : float, optional. - The emission factor used to calculate methane emissions in secondary treatment. The default is 0.0075 kg CH4/ kg rCOD. [1] - N2O_EF : float, optional - The emission factor used to calculate nitrous oxide emissions in secondary treatment. The default is 0.016 kg N2O-N/ kg N. [1] + CH4_EF_discharge : float, optional. + The emission factor used to calculate methane emissions in discharge. The default is 0.009 kg CH4/ kg effluent COD. [1] + N2O_EF_discharge : float, optional + The emission factor used to calculate nitrous oxide emissions in discharge. The default is 0.005 kg N2O-N/ kg effluent N. [1] Returns ------- @@ -778,8 +778,10 @@ def get_CO2_eq_WRRF (system, GHG_treatment, GHG_discharge, GHG_electricity, return normalized_CO2_eq_WRRF def get_total_CO2_eq(system, q_air, influent_sc =None, effluent_sc = None, effluent_sys =None, active_unit_IDs=None, sludge=None, - CH4_EF_sc =0.0075, N2O_EF_sc =0.016, CH4_EF_discharge=0.009, N2O_EF_discharge=0.005, - T=20, p_atm=101.325, K=0.283, F=0.5,CH4_CO2eq=29.8, N2O_CO2eq=273, + p_atm=101.325, K=0.283, CH4_CO2eq=29.8, N2O_CO2eq=273, + + CH4_EF_sc =0.0075, N2O_EF_sc =0.016, CH4_EF_discharge=0.009, N2O_EF_discharge=0.005, + T=20, F=0.5, # uncertain parameters P_inlet_loss=1, P_diffuser_loss=7, h_submergance=5.18, efficiency=0.7, @@ -810,9 +812,9 @@ def get_total_CO2_eq(system, q_air, influent_sc =None, effluent_sc = None, efflu effluent_sys : : iterable[:class:`WasteStream`], optional Effluent wastestreams from the system whose wastewater composition determine the potential for GHG emissions at discharge. The default is None. CH4_EF_discharge : float, optional. - The emission factor used to calculate methane emissions in secondary treatment. The default is 0.0075 kg CH4/ kg rCOD. [1] + The emission factor used to calculate methane emissions in discharge. The default is 0.009 kg CH4/ kg effluent COD. [1] N2O_EF_discharge : float, optional - The emission factor used to calculate nitrous oxide emissions in secondary treatment. The default is 0.016 kg N2O-N/ kg N. [1] + The emission factor used to calculate nitrous oxide emissions in discharge. The default is 0.005 kg N2O-N/ kg effluent N. [1] ----Electricity--- From 098018ef694e5fd25f3141dcb1bf9c40e19b97aa Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Wed, 21 Feb 2024 18:13:19 -0600 Subject: [PATCH 266/424] Adding file with initial conditions --- qsdsan/data/initial_conditions.xlsx | Bin 0 -> 20141 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 qsdsan/data/initial_conditions.xlsx diff --git a/qsdsan/data/initial_conditions.xlsx b/qsdsan/data/initial_conditions.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..b99956d889ee893deb90f07de0112cd0cafb1965 GIT binary patch literal 20141 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 HkH7vGa#@E` literal 0 HcmV?d00001 From 5ce9fa5370eb3c915e522160767c4298d8746e0b Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Wed, 21 Feb 2024 18:18:26 -0600 Subject: [PATCH 267/424] Revert "Adding file with initial conditions" This reverts commit 098018ef694e5fd25f3141dcb1bf9c40e19b97aa. --- qsdsan/data/initial_conditions.xlsx | Bin 20141 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 qsdsan/data/initial_conditions.xlsx diff --git a/qsdsan/data/initial_conditions.xlsx b/qsdsan/data/initial_conditions.xlsx deleted file mode 100644 index b99956d889ee893deb90f07de0112cd0cafb1965..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20141 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 HkH7vGa#@E` From 029ceaaa60a03781cc2521a763e14a8a1031bbe1 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Tue, 19 Mar 2024 10:04:29 -0500 Subject: [PATCH 268/424] ASM2d-ADM1 Adding ASM2d-ADM1 junction. ADM1 is not modified. --- qsdsan/sanunits/_junction.py | 395 ++++++++++++++++++++++++++++++++++- 1 file changed, 394 insertions(+), 1 deletion(-) diff --git a/qsdsan/sanunits/_junction.py b/qsdsan/sanunits/_junction.py index f8f8c62f..3a5c49c1 100644 --- a/qsdsan/sanunits/_junction.py +++ b/qsdsan/sanunits/_junction.py @@ -885,8 +885,401 @@ def asm2adm(asm_vals): self._reactions = asm2adm +#%% + +class ASM2dtoADM1(ADMjunction): + ''' + Interface unit to convert activated sludge model No. (ASM2d) components + to original anaerobic digestion model (ADM1) components. + + Parameters + ---------- + upstream : stream or str + Influent stream with ASM components. + downstream : stream or str + Effluent stream with ADM components. + adm1_model : obj + The anaerobic digestion process model (:class:`qsdsan.processes.ADM1`). -# %% + xs_to_li : float + Split of slowly biodegradable substrate COD to lipid, + after all N is mapped into protein. + bio_to_li : float + Split of biomass COD to lipid, after all biomass N is + mapped into protein. + frac_deg : float + Biodegradable fraction of biomass COD. + rtol : float + Relative tolerance for COD and TKN balance. + atol : float + Absolute tolerance for COD and TKN balance. + + References + ---------- + [1] Nopens, I.; Batstone, D. J.; Copp, J. B.; Jeppsson, U.; Volcke, E.; + Alex, J.; Vanrolleghem, P. A. An ASM/ADM Model Interface for Dynamic + Plant-Wide Simulation. Water Res. 2009, 43, 1913–1923. + + See Also + -------- + :class:`qsdsan.sanunits.ADMjunction` + + :class:`qsdsan.sanunits.ADMtoASM` + + `math.isclose ` + ''' + # User defined values + xs_to_li = 0.7 + bio_to_li = 0.4 + frac_deg = 0.68 + + + def isbalanced(self, lhs, rhs_vals, rhs_i): + rhs = sum(rhs_vals*rhs_i) + error = rhs - lhs + tol = max(self.rtol*lhs, self.rtol*rhs, self.atol) + return abs(error) <= tol, error, tol, rhs + + def balance_cod_tkn(self, asm_vals, adm_vals): + cmps_asm = self.ins[0].components + cmps_adm = self.outs[0].components + asm_i_COD = cmps_asm.i_COD + adm_i_COD = cmps_adm.i_COD + non_tkn_idx = cmps_asm.indices(('S_NO', 'S_N2')) + asm_i_N = cmps_asm.i_N + adm_i_N = cmps_adm.i_N + asm_cod = sum(asm_vals*asm_i_COD) + asm_tkn = sum(asm_vals*asm_i_N) - sum(asm_vals[non_tkn_idx]) + cod_bl, cod_err, cod_tol, adm_cod = self.isbalanced(asm_cod, adm_vals, adm_i_COD) + tkn_bl, tkn_err, tkn_tol, adm_tkn = self.isbalanced(asm_tkn, adm_vals, adm_i_N) + if cod_bl: + if tkn_bl: return adm_vals + else: + if tkn_err > 0: dtkn = -(tkn_err - tkn_tol)/adm_tkn + else: dtkn = -(tkn_err + tkn_tol)/adm_tkn + _adm_vals = adm_vals * (1 + (adm_i_N>0)*dtkn) + _cod_bl, _cod_err, _cod_tol, _adm_cod = self.isbalanced(asm_cod, _adm_vals, adm_i_COD) + if _cod_bl: return _adm_vals + else: + warn('cannot balance COD and TKN at the same ' + f'time with rtol={self.rtol} and atol={self.atol}.\n ' + f'influent (ASM) COD is {asm_cod}\n ' + f'effluent (ADM) COD is {adm_cod} or {_adm_cod}\n ' + f'influent TKN is {asm_tkn}\n ' + f'effluent TKN is {adm_tkn} or {adm_tkn*(1+dtkn)}. ') + return adm_vals + else: + if cod_err > 0: dcod = -(cod_err - cod_tol)/adm_cod + else: dcod = -(cod_err + cod_tol)/adm_cod + _adm_vals = adm_vals * (1 + (adm_i_COD>0)*dcod) + _tkn_bl, _tkn_err, _tkn_tol, _adm_tkn = self.isbalanced(asm_tkn, _adm_vals, adm_i_N) + if _tkn_bl: return _adm_vals + else: + warn('cannot balance COD and TKN at the same ' + f'time with rtol={self.rtol} and atol={self.atol}.\n ' + f'influent (ASM) COD is {asm_cod}\n ' + f'effluent (ADM) COD is {adm_cod} or {adm_cod*(1+dcod)}\n ' + f'influent TKN is {asm_tkn}\n ' + f'effluent TKN is {adm_tkn} or {_adm_tkn}. ') + return adm_vals + + def _compile_reactions(self): + # Retrieve constants + ins = self.ins[0] + outs = self.outs[0] + rtol = self.rtol + atol = self.atol + + cmps_asm = ins.components + + S_NO3_i_COD = cmps_asm.S_NO3.i_COD + X_H_i_N = cmps_asm.X_H.i_N + X_AUT_i_N = cmps_asm.X_AUT.i_N + X_PAO_i_N = cmps_asm.X_PAO.i_N + S_F_i_N = cmps_asm.S_F.i_N + X_S_i_N = cmps_asm.X_S.i_N + asm_S_I_i_N = cmps_asm.S_I.i_N + asm_X_I_i_N = cmps_asm.X_I.i_N + + if cmps_asm.X_S.i_N > 0: + warn(f'X_S in ASM has positive nitrogen content: {cmps_asm.X_S.i_N} gN/gCOD. ' + 'These nitrogen will be ignored by the interface model ' + 'and could lead to imbalance of TKN after conversion.') + if cmps_asm.S_S.i_N > 0: + warn(f'S_S in ASM has positive nitrogen content: {cmps_asm.S_S.i_N} gN/gCOD. ' + 'These nitrogen will be ignored by the interface model ' + 'and could lead to imbalance of TKN after conversion.') + + cmps_adm = outs.components + S_aa_i_N = cmps_adm.S_aa.i_N + X_pr_i_N = cmps_adm.X_pr.i_N + adm_S_I_i_N = cmps_adm.S_I.i_N + adm_X_I_i_N = cmps_adm.X_I.i_N + + + adm_ions_idx = cmps_adm.indices(['S_IN', 'S_IC', 'S_cat', 'S_an']) + + frac_deg = self.frac_deg + alpha_IN = self.alpha_IN + alpha_IC = self.alpha_IC + proton_charge = 10**(-self.pKa[0]+self.pH) - 10**(-self.pH) # self.pKa[0] is pKw + f_corr = self.balance_cod_tkn + + def asm2adm(asm_vals): + + S_O2, S_N2, S_NH4, S_NO3, S_PO4, S_F, S_A, S_I, S_ALK, X_I, X_S, X_H, \ + X_PAO, X_PP, X_PHA, X_AUT, X_MeOH, X_MeP, H2O = asm_vals + + # Step 0: charged component snapshot + _sa = S_A + _snh4 = S_NH4 + _sno3 = S_NO3 + _spo4 = S_PO4 + _salk = S_ALK + _xpp = X_PP + + # Step 1: remove any remaining COD demand + O2_coddm = S_O2 + NO3_coddm = -S_NO3*S_NO3_i_COD + + cod_spl = (S_A + S_F) + (X_S + X_PHA) + (X_H + X_AUT + X_PAO) + + + bioN = X_H*X_H_i_N + X_AUT*X_AUT_i_N + X_PAO*X_PAO_i_N + # To be used in Step 2 + S_F_N = S_F*S_F_i_N #S_ND (in asm1) equals the N content in S_F + # To be used in Step 3 + X_S_N = X_S*X_S_i_N #X_ND (in asm1) equals the N content in X_S + + + if cod_spl <= O2_coddm: + S_O2 = O2_coddm - cod_spl + S_F = S_A = X_S = X_H = X_AUT = 0 + elif cod_spl <= O2_coddm + NO3_coddm: + S_O2 = 0 + S_NO3 = -(O2_coddm + NO3_coddm - cod_spl)/S_NO3_i_COD + S_A = S_F = X_S = X_H = X_AUT = 0 + else: + S_A -= O2_coddm + NO3_coddm + if S_A < 0: + S_F += S_A + S_A = 0 + if S_F < 0: + X_S += S_F + S_F = 0 + if X_S < 0: + X_PHA += X_S + X_S = 0 + if X_PHA < 0: + X_H += X_PHA + X_PHA = 0 + if X_H < 0: + X_AUT += X_H + X_H = 0 + if X_AUT < 0: + X_PAO += X_AUT + X_AUT = 0 + + S_O2 = S_NO3 = 0 + + # Step 2: convert any readily biodegradable + # COD and TKN into amino acids and sugars + + # S_S (in asm1) equals to the sum of S_F and S_A (pg. 82 IWA ASM models handbook) + S_S_asm1 = S_F + S_A + + # First we calculate the amount of amino acid required in ADM1 + # if all available soluble organic N can be mapped to amino acid + req_scod = S_F_N / S_aa_i_N + + # if available S_S is not enough to fulfill that amino acid requirement + if S_S_asm1 < req_scod: + # then all available S_S is mapped to amino acids + S_aa = S_S_asm1 + # and no S_S would be available for conversion to sugars + S_su = 0 + # This needs to be followed by a corresponding loss in soluble organic N + S_F_N -= S_aa * S_aa_i_N + # if available S_S is more than enough to fulfill that amino acid requirement + else: + # All soluble organic N will be mapped to amino acid + S_aa = req_scod + # The line above implies that a certain portion of S_S would also be consumed to form amino acid + # The S_S which is left would form sugar + # In simpler terms; S_S = S_S - S_aa; S_su = S_S + S_su = S_S_asm1 - S_aa + # All soluble organic N would thus be consumed in amino acid formation + S_F_N = 0 + + + # Step 3: convert slowly biodegradable COD and TKN + # into proteins, lipids, and carbohydrates + + # First we calculate the amount of protein required in ADM1 + # if all available particulate organic N can be mapped to protein + req_xcod = X_S_N / X_pr_i_N + + # if available X_S is not enough to fulfill that protein requirement + if X_S < req_xcod: + # then all available X_S is mapped to amino acids + X_pr = X_S + # and no X_S would be available for conversion to lipid or carbohydrates + + X_li = self.xs_to_li * X_PHA + X_ch = (1 - self.xs_to_li)*X_PHA + + # This needs to be followed by a corresponding loss in particulate organic N + X_S_N -= X_pr * X_pr_i_N + + # if available X_S is more than enough to fulfill that protein requirement + else: + # All particulate organic N will be mapped to amino acid + X_pr = req_xcod + # The line above implies that a certain portion of X_S would also be consumed to form protein + # The X_S which is left would form lipid and carbohydrates in a percentage define by the user + X_li = self.xs_to_li * (X_S + X_PHA - X_pr) + X_ch = (X_S + X_PHA - X_pr) - X_li + # All particulate organic N would thus be consumed in amino acid formation + X_S_N = 0 + + # Step 4: convert active biomass into protein, lipids, + # carbohydrates and potentially particulate TKN + + # First the amount of biomass N available for protein, lipid etc is determined + # For this calculation, from total biomass N available the amount + # of particulate inert N expected in ADM1 is subtracted + + available_bioN = bioN - (X_H + X_AUT + X_PAO) * (1-frac_deg) * adm_X_I_i_N + + if available_bioN < 0: + raise RuntimeError('Not enough N in X_H, X_AUT and X_PAO to fully convert ' + 'the non-biodegradable portion into X_I in ADM1.') + + # Then the amount of biomass N required for biomass conversion to protein is determined + req_bioN = (X_H + X_AUT + X_PAO) * frac_deg * X_pr_i_N + # req_bioP = (X_H + X_AUT) * frac_deg * X_pr_i_P + + # If available biomass N and particulate organic N is greater than + # required biomass N for conversion to protein + if available_bioN + X_S_N >= req_bioN: + # then all biodegradable biomass N (corrsponding to protein demand) is converted to protein + X_pr += (X_H + X_AUT + X_PAO) * frac_deg + # the remaining biomass N is transfered as organic N + X_S_N += available_bioN - req_bioN + else: + # all available N and particulate organic N is converted to protein + bio2pr = (available_bioN + X_S_N)/X_pr_i_N + X_pr += bio2pr + # Biodegradable biomass available after conversion to protein is calculated + bio_to_split = (X_H + X_AUT + X_PAO) * frac_deg - bio2pr + # Part of the remaining biomass is mapped to lipid based on user defined value + bio_split_to_li = bio_to_split * self.bio_to_li + X_li += bio_split_to_li + # The other portion of the remanining biomass is mapped to carbohydrates + X_ch += (bio_to_split - bio_split_to_li) + # Since all organic N has been mapped to protein, none is left + X_S_N = 0 + + # Step 5: map particulate inerts + + # 5 (a) + # First determine the amount of particulate inert N available from ASM2d + xi_nsp_asm2d = X_I * asm_X_I_i_N + + # Then determine the amount of particulate inert N that could be produced + # in ADM1 given the ASM1 X_I + xi_ndm = X_I * adm_X_I_i_N + + # if particulate inert N available in ASM1 is greater than ADM1 demand + if xi_nsp_asm2d + X_S_N >= xi_ndm: + deficit = xi_ndm - xi_nsp_asm2d + # COD balance + X_I += (X_H + X_AUT + X_PAO) * (1-frac_deg) + # N balance + X_S_N -= deficit + elif isclose(xi_nsp_asm2d+X_S_N, xi_ndm, rel_tol=rtol, abs_tol=atol): + # COD balance + X_I += (X_H + X_AUT + X_PAO) * (1-frac_deg) + # N balance + X_S_N = 0 + else: + raise RuntimeError('Not enough N in X_I, X_S to fully ' + 'convert X_I in ASM2d into X_I in ADM1.') + + # 5(b) + + # Then determine the amount of soluble inert N that could be produced + # in ADM1 given the ASM1 X_I + req_sn = S_I * adm_S_I_i_N + supply_inert_n_asm2d = S_I * asm_S_I_i_N + + # N balance + if req_sn <= S_F_N + supply_inert_n_asm2d: + S_F_N -= (req_sn - supply_inert_n_asm2d) + supply_inert_n_asm2d = 0 + # N balance + elif req_sn <= S_F_N + X_S_N + supply_inert_n_asm2d: + X_S_N -= (req_sn - S_F_N - supply_inert_n_asm2d) + S_F_N = supply_inert_n_asm2d = 0 + # N balance + elif req_sn <= S_F_N + X_S_N + S_NH4 + supply_inert_n_asm2d: + S_NH4 -= (req_sn - S_F_N - X_S_N - supply_inert_n_asm2d) + S_F_N = X_S_N = supply_inert_n_asm2d = 0 + else: + warn('Additional soluble inert COD is mapped to S_su.') + SI_cod = (S_F_N + X_S_N + S_NH4 + supply_inert_n_asm2d)/adm_S_I_i_N + S_su += S_I - SI_cod + S_I = SI_cod + S_F_N = X_S_N = S_NH4 = supply_inert_n_asm2d = 0 + + # Step 6: Step map any remaining TKN/P + S_IN = S_F_N + X_S_N + S_NH4 + supply_inert_n_asm2d + + # Step 8: check COD and TKN balance + # has TKN: S_aa, S_IN, S_I, X_pr, X_I + S_IC = S_cat = S_an = 0 + + adm_vals = np.array([ + S_su, S_aa, + 0, 0, 0, 0, 0, # S_fa, S_va, S_bu, S_pro, S_ac, + 0, 0, # S_h2, S_ch4, + S_IC, S_IN, S_I, + 0, # X_c, + X_ch, X_pr, X_li, + 0, 0, 0, 0, 0, 0, 0, # X_su, X_aa, X_fa, X_c4, X_pro, X_ac, X_h2, + X_I, S_cat, S_an]) + + adm_vals = f_corr(asm_vals, adm_vals) + + # Step 7: charge balance + asm_charge_tot = _snh/14 - _sno/14 - _salk/12 + + _sa = S_A + _snh4 = S_NH4 + _sno3 = S_NO3 + _spo4 = S_PO4 + _salk = S_ALK + _xpp = X_PP + + #!!! charge balance should technically include VFAs, + # but VFAs concentrations are assumed zero per previous steps?? + S_IN = adm_vals[adm_ions_idx[0]] + S_IC = (asm_charge_tot - S_IN*alpha_IN)/alpha_IC + net_Scat = asm_charge_tot + proton_charge + if net_Scat > 0: + S_cat = net_Scat + S_an = 0 + else: + S_cat = 0 + S_an = -net_Scat + + adm_vals[adm_ions_idx[1:]] = [S_IC, S_cat, S_an] + + return adm_vals + + self._reactions = asm2adm + +#%% class mADM1toASM2d(ADMjunction): ''' From 3579c8b4b6357b6e7c15e5570c0201238211fc6c Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Tue, 19 Mar 2024 10:13:06 -0500 Subject: [PATCH 269/424] Charge balance in junction Charge balance in ASM2d-ADM1 interface. --- qsdsan/sanunits/_junction.py | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/qsdsan/sanunits/_junction.py b/qsdsan/sanunits/_junction.py index 3a5c49c1..1f2404f3 100644 --- a/qsdsan/sanunits/_junction.py +++ b/qsdsan/sanunits/_junction.py @@ -1252,19 +1252,13 @@ def asm2adm(asm_vals): adm_vals = f_corr(asm_vals, adm_vals) # Step 7: charge balance - asm_charge_tot = _snh/14 - _sno/14 - _salk/12 - - _sa = S_A - _snh4 = S_NH4 - _sno3 = S_NO3 - _spo4 = S_PO4 - _salk = S_ALK - _xpp = X_PP - + asm_charge_tot = - _sa/64 + _snh4/14 - _sno3/14 - 1.5*_spo4/31 - _salk - _xpp/31 #Based on page 84 of IWA ASM handbook #!!! charge balance should technically include VFAs, # but VFAs concentrations are assumed zero per previous steps?? S_IN = adm_vals[adm_ions_idx[0]] - S_IC = (asm_charge_tot - S_IN*alpha_IN)/alpha_IC + + S_IC = (asm_charge_tot -S_IN*alpha_IN)/alpha_IC + net_Scat = asm_charge_tot + proton_charge if net_Scat > 0: S_cat = net_Scat @@ -1273,7 +1267,7 @@ def asm2adm(asm_vals): S_cat = 0 S_an = -net_Scat - adm_vals[adm_ions_idx[1:]] = [S_IC, S_cat, S_an] + adm_vals[adm_ions_idx[2:]] = [S_IC, S_cat, S_an] return adm_vals From f5594937aeddefd9105b2c1c9e377dd500d9a9c7 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Tue, 19 Mar 2024 10:27:35 -0500 Subject: [PATCH 270/424] ASM2d-ADM1 Finalizing ASM2d-ADM1 --- qsdsan/sanunits/_junction.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/qsdsan/sanunits/_junction.py b/qsdsan/sanunits/_junction.py index 1f2404f3..516cf420 100644 --- a/qsdsan/sanunits/_junction.py +++ b/qsdsan/sanunits/_junction.py @@ -900,7 +900,6 @@ class ASM2dtoADM1(ADMjunction): Effluent stream with ADM components. adm1_model : obj The anaerobic digestion process model (:class:`qsdsan.processes.ADM1`). - xs_to_li : float Split of slowly biodegradable substrate COD to lipid, after all N is mapped into protein. @@ -945,7 +944,7 @@ def balance_cod_tkn(self, asm_vals, adm_vals): cmps_adm = self.outs[0].components asm_i_COD = cmps_asm.i_COD adm_i_COD = cmps_adm.i_COD - non_tkn_idx = cmps_asm.indices(('S_NO', 'S_N2')) + non_tkn_idx = cmps_asm.indices(('S_N2', 'S_NO3')) asm_i_N = cmps_asm.i_N adm_i_N = cmps_adm.i_N asm_cod = sum(asm_vals*asm_i_COD) @@ -1002,11 +1001,11 @@ def _compile_reactions(self): asm_X_I_i_N = cmps_asm.X_I.i_N if cmps_asm.X_S.i_N > 0: - warn(f'X_S in ASM has positive nitrogen content: {cmps_asm.X_S.i_N} gN/gCOD. ' + warn(f'X_S in ASM2d has positive nitrogen content: {cmps_asm.X_S.i_N} gN/gCOD. ' 'These nitrogen will be ignored by the interface model ' 'and could lead to imbalance of TKN after conversion.') - if cmps_asm.S_S.i_N > 0: - warn(f'S_S in ASM has positive nitrogen content: {cmps_asm.S_S.i_N} gN/gCOD. ' + if cmps_asm.S_A.i_N > 0: + warn(f'S_A in ASM2d has positive nitrogen content: {cmps_asm.S_S.i_N} gN/gCOD. ' 'These nitrogen will be ignored by the interface model ' 'and could lead to imbalance of TKN after conversion.') From 6e8caaf96547ad49d62f001598dc1fc1b53a2f46 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Tue, 19 Mar 2024 23:46:38 -0500 Subject: [PATCH 271/424] ADM1-ASM2d junction Added ADM1-ASM2d junction. --- qsdsan/sanunits/_junction.py | 305 +++++++++++++++++++++++++++++++++-- 1 file changed, 295 insertions(+), 10 deletions(-) diff --git a/qsdsan/sanunits/_junction.py b/qsdsan/sanunits/_junction.py index 516cf420..36402f53 100644 --- a/qsdsan/sanunits/_junction.py +++ b/qsdsan/sanunits/_junction.py @@ -21,8 +21,8 @@ __all__ = ( 'Junction', - 'ADMjunction', 'ADMtoASM', 'ASMtoADM', - ) + 'ADMjunction', 'ADMtoASM', 'ASMtoADM', 'ASM2dtoADM1', 'ADM1toASM2d' + ) #%% class Junction(SanUnit): @@ -931,7 +931,7 @@ class ASM2dtoADM1(ADMjunction): xs_to_li = 0.7 bio_to_li = 0.4 frac_deg = 0.68 - + asm_X_I_i_N = 0.06 def isbalanced(self, lhs, rhs_vals, rhs_i): rhs = sum(rhs_vals*rhs_i) @@ -998,12 +998,12 @@ def _compile_reactions(self): S_F_i_N = cmps_asm.S_F.i_N X_S_i_N = cmps_asm.X_S.i_N asm_S_I_i_N = cmps_asm.S_I.i_N - asm_X_I_i_N = cmps_asm.X_I.i_N - if cmps_asm.X_S.i_N > 0: - warn(f'X_S in ASM2d has positive nitrogen content: {cmps_asm.X_S.i_N} gN/gCOD. ' - 'These nitrogen will be ignored by the interface model ' - 'and could lead to imbalance of TKN after conversion.') + if self.asm_X_I_i_N == None: + asm_X_I_i_N = cmps_asm.X_I.i_N + else: + asm_X_I_i_N = self.asm_X_I_i_N + if cmps_asm.S_A.i_N > 0: warn(f'S_A in ASM2d has positive nitrogen content: {cmps_asm.S_S.i_N} gN/gCOD. ' 'These nitrogen will be ignored by the interface model ' @@ -1246,7 +1246,7 @@ def asm2adm(asm_vals): 0, # X_c, X_ch, X_pr, X_li, 0, 0, 0, 0, 0, 0, 0, # X_su, X_aa, X_fa, X_c4, X_pro, X_ac, X_h2, - X_I, S_cat, S_an]) + X_I, S_cat, S_an, H2O]) adm_vals = f_corr(asm_vals, adm_vals) @@ -1266,12 +1266,297 @@ def asm2adm(asm_vals): S_cat = 0 S_an = -net_Scat - adm_vals[adm_ions_idx[2:]] = [S_IC, S_cat, S_an] + adm_vals[adm_ions_idx[1:]] = [S_IC, S_cat, S_an] return adm_vals self._reactions = asm2adm +#%% +class ADM1toASM2d(ADMjunction): + ''' + Interface unit to convert anaerobic digestion model no. 1 (ADM1) components + to activated sludge model no. 2 (ASM2d) components. + + Parameters + ---------- + upstream : stream or str + Influent stream with ADM components. + downstream : stream or str + Effluent stream with ASM components. + adm1_model : obj + The anaerobic digestion process model (:class:`qsdsan.processes.ADM1`). + bio_to_xs : float + Split of the total biomass COD to slowly biodegradable substrate (X_S), + the rest is assumed to be mapped into X_P. + rtol : float + Relative tolerance for COD and TKN balance. + atol : float + Absolute tolerance for COD and TKN balance. + + References + ---------- + [1] Nopens, I.; Batstone, D. J.; Copp, J. B.; Jeppsson, U.; Volcke, E.; + Alex, J.; Vanrolleghem, P. A. An ASM/ADM Model Interface for Dynamic + Plant-Wide Simulation. Water Res. 2009, 43, 1913–1923. + + See Also + -------- + :class:`qsdsan.sanunits.ADMjunction` + + :class:`qsdsan.sanunits.ASMtoADM` + + `math.isclose ` + ''' + # User defined values + bio_to_xs = 0.9 + + # Should be constants + cod_vfa = np.array([64, 112, 160, 208]) + + # whether to conserve the nitrogen split between soluble and particulate components + conserve_particulate_N = False + + + def isbalanced(self, lhs, rhs_vals, rhs_i): + rhs = sum(rhs_vals*rhs_i) + error = rhs - lhs + tol = max(self.rtol*lhs, self.rtol*rhs, self.atol) + return abs(error) <= tol, error, tol, rhs + + def balance_cod_tkn(self, adm_vals, asm_vals): + cmps_adm = self.ins[0].components + cmps_asm = self.outs[0].components + asm_i_COD = cmps_asm.i_COD + adm_i_COD = cmps_adm.i_COD + gas_idx = cmps_adm.indices(('S_h2', 'S_ch4')) + asm_i_N = cmps_asm.i_N + adm_i_N = cmps_adm.i_N + adm_cod = sum(adm_vals*adm_i_COD) - sum(adm_vals[gas_idx]) + adm_tkn = sum(adm_vals*adm_i_N) + cod_bl, cod_err, cod_tol, asm_cod = self.isbalanced(adm_cod, asm_vals, asm_i_COD) + tkn_bl, tkn_err, tkn_tol, asm_tkn = self.isbalanced(adm_tkn, asm_vals, asm_i_N) + if cod_bl: + if tkn_bl: return asm_vals + else: + if tkn_err > 0: dtkn = -(tkn_err - tkn_tol)/asm_tkn + else: dtkn = -(tkn_err + tkn_tol)/asm_tkn + _asm_vals = asm_vals * (1 + (asm_i_N>0)*dtkn) + _cod_bl, _cod_err, _cod_tol, _asm_cod = self.isbalanced(adm_cod, _asm_vals, asm_i_COD) + if _cod_bl: return _asm_vals + else: + warn('cannot balance COD and TKN at the same ' + f'time with rtol={self.rtol} and atol={self.atol}. ' + f'influent (ADM) COD is {adm_cod}, ' + f'effluent (ASM) COD is {asm_cod} or {_asm_cod}. ' + f'influent TKN is {adm_tkn}, ' + f'effluent TKN is {asm_tkn} or {asm_tkn*(1+dtkn)}. ') + return asm_vals + else: + if cod_err > 0: dcod = -(cod_err - cod_tol)/asm_cod + else: dcod = -(cod_err + cod_tol)/asm_cod + _asm_vals = asm_vals * (1 + (asm_i_COD>0)*dcod) + _tkn_bl, _tkn_err, _tkn_tol, _asm_tkn = self.isbalanced(adm_tkn, _asm_vals, asm_i_N) + if _tkn_bl: return _asm_vals + else: + warn('cannot balance COD and TKN at the same ' + f'time with rtol={self.rtol} and atol={self.atol}. ' + f'influent (ADM) COD is {adm_cod}, ' + f'effluent (ASM) COD is {asm_cod} or {asm_cod*(1+dcod)}. ' + f'influent TKN is {adm_tkn}, ' + f'effluent TKN is {asm_tkn} or {_asm_tkn}. ') + return asm_vals + + def _compile_reactions(self): + # Retrieve constants + ins = self.ins[0] + outs = self.outs[0] + rtol = self.rtol + atol = self.atol + + cmps_adm = ins.components + X_c_i_N = cmps_adm.X_c.i_N + X_pr_i_N = cmps_adm.X_pr.i_N + S_aa_i_N = cmps_adm.S_aa.i_N + adm_X_I_i_N = cmps_adm.X_I.i_N + adm_S_I_i_N = cmps_adm.S_I.i_N + adm_i_N = cmps_adm.i_N + adm_bio_N_indices = cmps_adm.indices(('X_su', 'X_aa', 'X_fa', + 'X_c4', 'X_pro', 'X_ac', 'X_h2')) + + cmps_asm = outs.components + + X_S_i_N = cmps_asm.X_S.i_N + S_F_i_N = cmps_asm.S_F.i_N + + asm_X_I_i_N = cmps_asm.X_I.i_N + asm_S_I_i_N = cmps_asm.S_I.i_N + + asm_ions_idx = cmps_asm.indices(('S_A', 'S_NH4', 'S_NO3', 'S_PO4','S_ALK', 'X_PP')) + + alpha_IN = self.alpha_IN + alpha_IC = self.alpha_IC + alpha_vfa = self.alpha_vfa + f_corr = self.balance_cod_tkn + conserve_particulate_N = self.conserve_particulate_N + + def adm2asm(adm_vals): + + S_su, S_aa, S_fa, S_va, S_bu, S_pro, S_ac, S_h2, S_ch4, S_IC, S_IN, S_I, X_c, \ + X_ch, X_pr, X_li, X_su, X_aa, X_fa, X_c4, X_pro, X_ac, X_h2, X_I, S_cat, S_an, H2O = adm_vals + + # Step 0: snapshot of charged components + _ions = np.array([S_IN, S_IC, S_ac, S_pro, S_bu, S_va]) + + # Step 1a: convert biomass into X_S and X_I + + bio_cod = X_su + X_aa + X_fa + X_c4 + X_pro + X_ac + X_h2 + bio_n = sum((adm_vals*adm_i_N)[adm_bio_N_indices]) + + #!!! In default ASM2d stoichiometry, biomass decay (cell lysis) + #!!! yields 90% particulate substrate + 10% X_I + #!!! so: convert both biomass and X_I in adm to X_S and X_I in asm + xi_n = X_I*adm_X_I_i_N + xs_cod = bio_cod * self.bio_to_xs + xs_ndm = xs_cod * X_S_i_N + + xi_cod = bio_cod * (1-self.bio_to_xs) + X_I + xi_ndm = xi_cod * asm_X_I_i_N + + if xs_ndm > bio_n: + warn('Not enough biomass N to map the specified proportion of ' + 'biomass COD into X_S. Rest of the biomass COD goes to S_A') + X_S = bio_n / X_S_i_N + xs_cod -= X_S + bio_n = 0 + else: + X_S = xs_cod + xs_cod = 0 + bio_n -= xs_ndm + + if xi_ndm > bio_n + xi_n + S_IN: + warn('Not enough N in biomass and X_I to map the specified proportion of ' + 'biomass COD into X_I. Rest of the biomass COD goes to S_A') + X_I = (bio_n + xi_n + S_IN) / asm_X_I_i_N + xi_cod -= X_I + bio_n = xi_n = S_IN = 0 + else: + X_I = xi_cod + xi_cod = 0 + xi_n -= xi_ndm + if xi_n < 0: + bio_n += xi_n + xi_n = 0 + if bio_n < 0: + S_IN += bio_n + bio_n = 0 + + xsub_cod = X_c + X_ch + X_pr + X_li + xsub_n = X_c*X_c_i_N + X_pr*X_pr_i_N + + xs_ndm = xsub_cod * X_S_i_N + + if xs_ndm > xsub_n + bio_n: + X_S_temp = (xsub_n + bio_n)/X_S_i_N + X_S += X_S_temp + xsub_cod -= X_S_temp + xsub_n = bio_n = 0 + else: + X_S += xsub_cod + xsub_cod = 0 + xsub_n -= xs_ndm + if xsub_n < 0: + bio_n += xsub_n + xsub_n = 0 + + ssub_cod = S_su + S_aa + S_fa + ssub_n = S_aa * S_aa_i_N + sf_ndm = ssub_cod * S_F_i_N + + if sf_ndm > ssub_n + xsub_n + bio_n: + S_F = (ssub_n + xsub_n + bio_n) / S_F_i_N + ssub_cod -= S_F + ssub_n = xsub_n = bio_n = 0 + else: + S_F = ssub_cod + ssub_cod = 0 + ssub_n -= sf_ndm + if ssub_n < 0: + xsub_n += ssub_n + ssub_n = 0 + if xsub_n < 0: + bio_n += xsub_n + xsub_n = 0 + + S_A = S_ac + S_pro + S_bu + S_va + + si_cod = S_I + si_n = S_I * adm_S_I_i_N + si_ndm = si_cod * asm_S_I_i_N + if si_ndm > si_n + xi_n + S_IN: + warn('Not enough N in S_I and X_I to map all S_I from ADM1 to ASM2d. ' + 'Rest of the S_I COD goes to S_A') + S_I = (si_n + xi_n + S_IN) / asm_S_I_i_N + si_cod -= S_I + si_n = xi_n = S_IN = 0 + else: + S_I = si_cod + si_cod = 0 + si_n -= si_ndm + if si_n < 0: + xi_n += si_n + si_n = 0 + if xi_n < 0: + S_IN += xi_n + xi_n = 0 + + + S_NH4 = S_IN + si_n + ssub_n + xsub_n + xi_n + bio_n + S_A += si_cod + ssub_cod + xsub_cod + xi_cod + xs_cod + S_ALK = S_IC + + # Step 6: check COD and TKN balance + asm_vals = np.array(([ + 0, 0, # S_O2, S_N2, + S_NH4, + 0, + 0, + S_F, S_A, S_I, S_ALK, + X_I, X_S, + 0, # X_H, + 0, 0, 0, + 0, # X_AUT, + 0, 0, H2O])) + + if S_h2 > 0 or S_ch4 > 0: + warn('Ignored dissolved H2 or CH4.') + + asm_vals = f_corr(adm_vals, asm_vals) + + # Step 5: charge balance for alkalinity + + # asm_ions_idx = cmps_asm.indices(('S_A', 'S_NH4', 'S_NO3', 'S_PO4','S_ALK', 'X_PP')) + + _sa = asm_vals[asm_ions_idx[0]] + _snh4 = asm_vals[asm_ions_idx[1]] + _sno3 = asm_vals[asm_ions_idx[2]] + _spo4 = asm_vals[asm_ions_idx[3]] + _xpp = asm_vals[asm_ions_idx[5]] + + S_ALK = (sum(_ions * np.append([alpha_IN, alpha_IC], alpha_vfa)) - \ + (- _sa/64 + _snh4/14 - _sno3/14 - 1.5*_spo4/31 - _xpp/31))*(-12) + + asm_vals[asm_ions_idx[4]] = S_ALK + + return asm_vals + + self._reactions = adm2asm + + @property + def alpha_vfa(self): + return 1.0/self.cod_vfa*(-1.0/(1.0 + 10**(self.pKa[3:]-self.pH))) + + #%% class mADM1toASM2d(ADMjunction): From 2d832491e456ff12c53ce1286e7ddf3ddfcbbd7c Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Thu, 21 Mar 2024 11:46:10 -0500 Subject: [PATCH 272/424] Update junction no significant changes --- qsdsan/sanunits/_junction.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/qsdsan/sanunits/_junction.py b/qsdsan/sanunits/_junction.py index 36402f53..b8f22324 100644 --- a/qsdsan/sanunits/_junction.py +++ b/qsdsan/sanunits/_junction.py @@ -951,6 +951,7 @@ def balance_cod_tkn(self, asm_vals, adm_vals): asm_tkn = sum(asm_vals*asm_i_N) - sum(asm_vals[non_tkn_idx]) cod_bl, cod_err, cod_tol, adm_cod = self.isbalanced(asm_cod, adm_vals, adm_i_COD) tkn_bl, tkn_err, tkn_tol, adm_tkn = self.isbalanced(asm_tkn, adm_vals, adm_i_N) + if cod_bl: if tkn_bl: return adm_vals else: @@ -1273,6 +1274,7 @@ def asm2adm(asm_vals): self._reactions = asm2adm #%% + class ADM1toASM2d(ADMjunction): ''' Interface unit to convert anaerobic digestion model no. 1 (ADM1) components @@ -1555,8 +1557,7 @@ def adm2asm(adm_vals): @property def alpha_vfa(self): return 1.0/self.cod_vfa*(-1.0/(1.0 + 10**(self.pKa[3:]-self.pH))) - - + #%% class mADM1toASM2d(ADMjunction): @@ -2156,10 +2157,12 @@ def _compile_reactions(self): warn(f'S_A in ASM has positive nitrogen content: {cmps_asm.S_S.i_N} gN/gCOD. ' 'These nitrogen will be ignored by the interface model ' 'and could lead to imbalance of TKN after conversion.') + if cmps_asm.S_A.i_P > 0: warn(f'S_A in ASM has positive phosphorous content: {cmps_asm.S_S.i_P} gN/gCOD. ' 'These phosphorous will be ignored by the interface model ' 'and could lead to imbalance of TP after conversion.') + if cmps_asm.S_I.i_P > 0: warn(f'S_I in ASM has positive phosphorous content: {cmps_asm.S_I.i_P} gN/gCOD. ' 'These phosphorous will be ignored by the interface model ' From 2760d7fdd73255c0799df128612d512700ac41e8 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Thu, 21 Mar 2024 14:32:10 -0500 Subject: [PATCH 273/424] ASM2d-mADM1 interface Step 5(b) deployed to match S_I was incorrect. Corrections, and corresponding check have been made. --- qsdsan/sanunits/_junction.py | 47 +++++++++++++++--------------------- 1 file changed, 20 insertions(+), 27 deletions(-) diff --git a/qsdsan/sanunits/_junction.py b/qsdsan/sanunits/_junction.py index b8f22324..a3bf1406 100644 --- a/qsdsan/sanunits/_junction.py +++ b/qsdsan/sanunits/_junction.py @@ -2485,13 +2485,8 @@ def asm2d2madm1(asm_vals): S_PO4 -= (req_sp - S_F_P - X_S_P) S_F_P = X_S_P = 0 else: - warn('Additional soluble inert COD is mapped to S_su.') - # Can these be executed in this case? I think so - SI_cod = (S_F_P + X_S_P + S_PO4)/adm_S_I_i_P - S_su += S_I - SI_cod - S_I = SI_cod - S_F_P = X_S_P = S_PO4 = 0 - # Should I redo N balance here? + S_PO4 -= (req_sp - S_F_P - X_S_P) + S_F_P = X_S_P = 0 # N balance elif req_sn <= S_ND_asm1 + X_ND_asm1 + supply_inert_n_asm2d: X_ND_asm1 -= (req_sn - S_ND_asm1 - supply_inert_n_asm2d) @@ -2506,13 +2501,8 @@ def asm2d2madm1(asm_vals): S_PO4 -= (req_sp - S_F_P - X_S_P) S_F_P = X_S_P = 0 else: - warn('Additional soluble inert COD is mapped to S_su.') - # Can these be executed in this case? I think so - SI_cod = (S_F_P + X_S_P + S_PO4)/adm_S_I_i_P - S_su += S_I - SI_cod - S_I = SI_cod - S_F_P = X_S_P = S_PO4 = 0 - # Should I redo N balance here? + S_PO4 -= (req_sp - S_F_P - X_S_P) + S_F_P = X_S_P = 0 # N balance elif req_sn <= S_ND_asm1 + X_ND_asm1 + S_NH4 + supply_inert_n_asm2d: S_NH4 -= (req_sn - S_ND_asm1 - X_ND_asm1 - supply_inert_n_asm2d) @@ -2527,20 +2517,13 @@ def asm2d2madm1(asm_vals): S_PO4 -= (req_sp - S_F_P - X_S_P) S_F_P = X_S_P = 0 else: - warn('Additional soluble inert COD is mapped to S_su.') - # Can these be executed in this case? I think so - SI_cod = (S_F_P + X_S_P + S_PO4)/adm_S_I_i_P - S_su += S_I - SI_cod - S_I = SI_cod - S_F_P = X_S_P = S_PO4 = 0 - # Should I redo N balance here? + S_PO4 -= (req_sp - S_F_P - X_S_P) + S_F_P = X_S_P = 0 elif req_sp <= S_F_P or req_sp <= S_F_P + X_S_P or req_sp <= S_F_P + X_S_P + S_PO4: - warn('Additional soluble inert COD is mapped to S_su.') - SI_cod = (S_ND_asm1 + X_ND_asm1 + S_NH4 + supply_inert_n_asm2d)/adm_S_I_i_N - S_su += S_I - SI_cod - S_I = SI_cod - S_ND_asm1 = X_ND_asm1 = S_NH4 = supply_inert_n_asm2d = 0 - req_sp = S_I * adm_S_I_i_P + + S_NH4 -= (req_sn - S_ND_asm1 - X_ND_asm1 - supply_inert_n_asm2d) + S_ND_asm1 = X_ND_asm1 = supply_inert_n_asm2d = 0 + if req_sp <= S_F_P: S_F_P -= req_sp elif req_sp <= S_F_P + X_S_P: @@ -2570,7 +2553,17 @@ def asm2d2madm1(asm_vals): req_sn = S_I * adm_S_I_i_N S_NH4 -= (req_sn - S_ND_asm1 - X_ND_asm1 - supply_inert_n_asm2d) S_ND_asm1 = X_ND_asm1 = supply_inert_n_asm2d = 0 + + if S_PO4 < 0: + raise RuntimeError('Not enough P in S_F_P, X_S_P and S_PO4 to fully ' + 'convert S_I in ASM2d into S_I in ADM1. Consider ' + 'increasing the value of P content in S_I (ASM2d)') + if S_NH4 < 0: + raise RuntimeError('Not enough N in S_I, S_ND_asm1, X_ND_asm1, and S_NH4 to fully ' + 'convert S_I in ASM2d into S_I in ADM1. Consider ' + 'increasing the value of N content in S_I (ASM2d)') + # Step 6: Step map any remaining TKN/P S_IN = S_ND_asm1 + X_ND_asm1 + S_NH4 + supply_inert_n_asm2d S_IP = S_F_P + X_S_P + S_PO4 From 759cf6a4a12ebb261f807461b5eb4e6ed152e522 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Thu, 21 Mar 2024 14:44:15 -0500 Subject: [PATCH 274/424] Checks for N/P in PAO ASM2d-mADM1 interface: check for direct mapping of PAO --- qsdsan/sanunits/_junction.py | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/qsdsan/sanunits/_junction.py b/qsdsan/sanunits/_junction.py index a3bf1406..f2dcf9a8 100644 --- a/qsdsan/sanunits/_junction.py +++ b/qsdsan/sanunits/_junction.py @@ -2029,6 +2029,11 @@ class ASM2dtomADM1(ADMjunction): bio_to_li = 0.4 frac_deg = 0.68 + # Since we are matching PAOs directly from ASM2d to mADM1, it is important + # for PAOs to have identical N/P content across models + + adm_X_PAO_i_N = 0.07 + adm_X_PAO_i_P = 0.02 def isbalanced(self, lhs, rhs_vals, rhs_i): rhs = sum(rhs_vals*rhs_i) @@ -2187,14 +2192,24 @@ def _compile_reactions(self): # Check for X_PAO (measured as COD so i_COD = 1 in both ASM2d and ADM1) asm_X_PAO_i_N = cmps_asm.X_PAO.i_N - adm_X_PAO_i_N = cmps_adm.X_PAO.i_N + + if self.adm_X_PAO_i_N == None: + adm_X_PAO_i_N = cmps_adm.X_PAO.i_N + else: + adm_X_PAO_i_N = self.adm_X_PAO_i_N + if asm_X_PAO_i_N != adm_X_PAO_i_N: raise RuntimeError('X_PAO cannot be directly mapped as N content' f'in asm2d_X_PAO_i_N = {asm_X_PAO_i_N} is not equal to' f'adm_X_PAO_i_N = {adm_X_PAO_i_N}') asm_X_PAO_i_P = cmps_asm.X_PAO.i_P - adm_X_PAO_i_P = cmps_adm.X_PAO.i_P + + if self.adm_X_PAO_i_P == None: + adm_X_PAO_i_P = cmps_adm.X_PAO.i_P + else: + adm_X_PAO_i_P = self.adm_X_PAO_i_P + if asm_X_PAO_i_P != adm_X_PAO_i_P: raise RuntimeError('X_PAO cannot be directly mapped as P content' f'in asm2d_X_PAO_i_P = {asm_X_PAO_i_P} is not equal to' @@ -2602,12 +2617,16 @@ def asm2d2madm1(asm_vals): asm_charge_tot = - _sa/64 + _snh4/14 - _sno3/14 - 1.5*_spo4/31 - _salk - _xpp/31 #Based on page 84 of IWA ASM handbook #!!! charge balance should technically include VFAs, # but VFAs concentrations are assumed zero per previous steps?? + S_IN = adm_vals[adm_ions_idx[0]] S_IP = adm_vals[adm_ions_idx[1]] #!!! charge balance from ADM1 should technically include S_K, and S_Mg, #but since both are zero, it is acceptable + S_IC = (asm_charge_tot -S_IN*alpha_IN -S_IP*alpha_IP)/alpha_IC + net_Scat = asm_charge_tot + proton_charge + if net_Scat > 0: S_cat = net_Scat S_an = 0 From 49bfcb9e0dbe8b28dfb50fa15d6034c171c2e70f Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Thu, 21 Mar 2024 15:10:00 -0500 Subject: [PATCH 275/424] Understanding charge balance Added comments to enhance my understanding of charge balance. --- qsdsan/sanunits/_junction.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/qsdsan/sanunits/_junction.py b/qsdsan/sanunits/_junction.py index f2dcf9a8..2fcc8160 100644 --- a/qsdsan/sanunits/_junction.py +++ b/qsdsan/sanunits/_junction.py @@ -2615,16 +2615,17 @@ def asm2d2madm1(asm_vals): # Step 7: charge balance asm_charge_tot = - _sa/64 + _snh4/14 - _sno3/14 - 1.5*_spo4/31 - _salk - _xpp/31 #Based on page 84 of IWA ASM handbook - #!!! charge balance should technically include VFAs, - # but VFAs concentrations are assumed zero per previous steps?? + + #!!! charge balance should technically include VFAs, S_K, S_Mg, + # but since their concentrations are assumed zero it is acceptable. S_IN = adm_vals[adm_ions_idx[0]] S_IP = adm_vals[adm_ions_idx[1]] - #!!! charge balance from ADM1 should technically include S_K, and S_Mg, - #but since both are zero, it is acceptable S_IC = (asm_charge_tot -S_IN*alpha_IN -S_IP*alpha_IP)/alpha_IC + # proton_charge = (OH)^-1 - (H)^+1 + # net_Scat = Scat - San net_Scat = asm_charge_tot + proton_charge if net_Scat > 0: From 38c567ccf9ead46d32b934a0b33765b3c36604c2 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Thu, 21 Mar 2024 15:30:57 -0500 Subject: [PATCH 276/424] Changes to validate ASM2d-mADM1 Minor changes to prevent problems while validating the ASM2d-mADM1 interface. --- qsdsan/sanunits/_junction.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/qsdsan/sanunits/_junction.py b/qsdsan/sanunits/_junction.py index 2fcc8160..c1ae7f12 100644 --- a/qsdsan/sanunits/_junction.py +++ b/qsdsan/sanunits/_junction.py @@ -2034,6 +2034,7 @@ class ASM2dtomADM1(ADMjunction): adm_X_PAO_i_N = 0.07 adm_X_PAO_i_P = 0.02 + asm_X_I_i_N = 0.06 def isbalanced(self, lhs, rhs_vals, rhs_i): rhs = sum(rhs_vals*rhs_i) @@ -2046,7 +2047,7 @@ def balance_cod_tkn_tp(self, asm_vals, adm_vals): cmps_adm = self.outs[0].components asm_i_COD = cmps_asm.i_COD adm_i_COD = cmps_adm.i_COD - non_tkn_idx = cmps_asm.indices(('S_NO', 'S_N2')) + non_tkn_idx = cmps_asm.indices(('S_N2', 'S_NO3')) asm_i_N = cmps_asm.i_N adm_i_N = cmps_adm.i_N asm_i_P = cmps_asm.i_P @@ -2148,7 +2149,14 @@ def _compile_reactions(self): X_AUT_i_N = cmps_asm.X_AUT.i_N S_F_i_N = cmps_asm.S_F.i_N X_S_i_N = cmps_asm.X_S.i_N - asm_X_I_i_N = cmps_asm.X_I.i_N + + + # Due to issue with mapping of X_I across ASM2d and ADM1, making this user dependent is important + if self.asm_X_I_i_N == None: + asm_X_I_i_N = cmps_asm.X_I.i_N + else: + asm_X_I_i_N = self.asm_X_I_i_N + asm_S_I_i_N = cmps_asm.S_I.i_N # For P balance From 010d5d27ffedcdfd79af3596b77a73d6bfd043e4 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Thu, 21 Mar 2024 15:44:47 -0500 Subject: [PATCH 277/424] ASM2dtomADM1 Added 'ASM2dtomADM1' to the file. --- qsdsan/sanunits/_junction.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/qsdsan/sanunits/_junction.py b/qsdsan/sanunits/_junction.py index c1ae7f12..3afd775e 100644 --- a/qsdsan/sanunits/_junction.py +++ b/qsdsan/sanunits/_junction.py @@ -21,7 +21,12 @@ __all__ = ( 'Junction', - 'ADMjunction', 'ADMtoASM', 'ASMtoADM', 'ASM2dtoADM1', 'ADM1toASM2d' + 'ADMjunction', + 'ADMtoASM', + 'ASMtoADM', + 'ASM2dtoADM1', + 'ADM1toASM2d', + 'ASM2dtomADM1', ) #%% @@ -2150,7 +2155,6 @@ def _compile_reactions(self): S_F_i_N = cmps_asm.S_F.i_N X_S_i_N = cmps_asm.X_S.i_N - # Due to issue with mapping of X_I across ASM2d and ADM1, making this user dependent is important if self.asm_X_I_i_N == None: asm_X_I_i_N = cmps_asm.X_I.i_N From 7531b130e70b8be66f10f86756371096bce5739d Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Thu, 21 Mar 2024 16:32:25 -0500 Subject: [PATCH 278/424] Added mADM1 junction class New class for enabling mADM1 interfaces --- qsdsan/sanunits/_junction.py | 124 ++++++++++++++++++++++++++++++++++- 1 file changed, 123 insertions(+), 1 deletion(-) diff --git a/qsdsan/sanunits/_junction.py b/qsdsan/sanunits/_junction.py index 3afd775e..89edb387 100644 --- a/qsdsan/sanunits/_junction.py +++ b/qsdsan/sanunits/_junction.py @@ -22,6 +22,7 @@ __all__ = ( 'Junction', 'ADMjunction', + 'mADMjunction', 'ADMtoASM', 'ASMtoADM', 'ASM2dtoADM1', @@ -292,7 +293,7 @@ def adm1_model(self): def adm1_model(self, model): if not isinstance(model, pc.ADM1): raise ValueError('`adm1_model` must be an `AMD1` object, ' - f'the given object is {type(model).__name__}.') + f'the given object is {type(model).__name__}.') self._adm1_model = model @property @@ -334,6 +335,127 @@ def alpha_IN(self): pKa_IN = self.pKa[1] return 10**(pKa_IN-pH)/(1+10**(pKa_IN-pH))/14 + def _compile_AE(self): + _state = self._state + _dstate = self._dstate + _cached_state = self._cached_state + _update_state = self._update_state + _update_dstate = self._update_dstate + rxn = self.reactions + + def yt(t, QC_ins, dQC_ins): + before_vals = QC_ins[0,:-1] + _state[:-1] = rxn(before_vals) + _state[-1] = QC_ins[0,-1] + if t > self._cached_t: + _dstate[:] = (_state - _cached_state)/(t-self._cached_t) + _cached_state[:] = _state + self._cached_t = t + _update_state() + _update_dstate() + + self._AE = yt +#%% + +class mADMjunction(Junction): + ''' + An abstract superclass holding common properties of ADM interface classes. + Users should use its subclasses (e.g., ``ASMtoADM``, ``ADMtoASM``) instead. + + See Also + -------- + :class:`qsdsan.sanunits.Junction` + + :class:`qsdsan.sanunits.ADMtoASM` + + :class:`qsdsan.sanunits.ASMtoADM` + ''' + _parse_reactions = Junction._no_parse_reactions + rtol = 1e-2 + atol = 1e-6 + + def __init__(self, ID='', upstream=None, downstream=(), thermo=None, + init_with='WasteStream', F_BM_default=None, isdynamic=False, + adm1_model=None): + self.adm1_model = adm1_model # otherwise there won't be adm1_model when `_compile_reactions` is called + if thermo is None: + warn('No `thermo` object is provided and is prone to raise error. ' + 'If you are not sure how to get the `thermo` object, ' + 'use `thermo = qsdsan.set_thermo` after setting thermo with the `Components` object.') + super().__init__(ID=ID, upstream=upstream, downstream=downstream, + thermo=thermo, init_with=init_with, + F_BM_default=F_BM_default, isdynamic=isdynamic) + + + @property + def T(self): + '''[float] Temperature of the upstream/downstream [K].''' + return self.ins[0].T + @T.setter + def T(self, T): + self.ins[0].T = self.outs[0].T = T + + @property + def pH(self): + '''[float] pH of the upstream/downstream.''' + return self.ins[0].pH + + @property + def adm1_model(self): + '''[qsdsan.Process] ADM process model.''' + return self._adm1_model + @adm1_model.setter + def adm1_model(self, model): + if not isinstance(model, pc.ADM1_p_extension): + raise ValueError('`adm1_model` must be an `AMD1` object, ' + f'the given object is {type(model).__name__}.') + self._adm1_model = model + + @property + def T_base(self): + '''[float] Base temperature in the ADM1 model.''' + return self.adm1_model.rate_function.params['T_base'] + + @property + def pKa_base(self): + '''[float] pKa of the acid-base pairs at the base temperature in the ADM1 model.''' + Ka_base = self.adm1_model.rate_function.params['Ka_base'] + return -np.log10(Ka_base) + + @property + def Ka_dH(self): + '''[float] Heat of reaction for Ka.''' + return self.adm1_model.rate_function.params['Ka_dH'] + + @property + def pKa(self): + ''' + [numpy.array] pKa array of the following acid-base pairs: + ('H+', 'OH-'), ('NH4+', 'NH3'), ('CO2', 'HCO3-'), + ('HAc', 'Ac-'), ('HPr', 'Pr-'), ('HBu', 'Bu-'), ('HVa', 'Va-') + ''' + return self.pKa_base-np.log10(np.exp(pc.T_correction_factor(self.T_base, self.T, self.Ka_dH))) + + @property + def alpha_IC(self): + '''[float] Charge per g of C.''' + pH = self.pH + pKa_IC = self.pKa[3] + return -1/(1+10**(pKa_IC-pH))/12 + + @property + def alpha_IN(self): + '''[float] Charge per g of N.''' + pH = self.pH + pKa_IN = self.pKa[1] + return 10**(pKa_IN-pH)/(1+10**(pKa_IN-pH))/14 + + @property + def alpha_IP(self): + '''[float] Charge per g of P.''' + pH = self.pH + pKa_IP = self.pKa[2] + return 10**(pKa_IP-pH)/(1+10**(pKa_IP-pH))/31 def _compile_AE(self): _state = self._state From 656bf805e653c519834da145d02cd3376f2a4bf3 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Thu, 21 Mar 2024 16:38:10 -0500 Subject: [PATCH 279/424] mADM1 junction Added H3PO4, and HPO4 2- acid/base pair to the mADM1 junction --- qsdsan/sanunits/_junction.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/qsdsan/sanunits/_junction.py b/qsdsan/sanunits/_junction.py index 89edb387..90213306 100644 --- a/qsdsan/sanunits/_junction.py +++ b/qsdsan/sanunits/_junction.py @@ -431,17 +431,10 @@ def Ka_dH(self): def pKa(self): ''' [numpy.array] pKa array of the following acid-base pairs: - ('H+', 'OH-'), ('NH4+', 'NH3'), ('CO2', 'HCO3-'), + ('H+', 'OH-'), ('NH4+', 'NH3'), ('H3PO4', 'H2PO4 2-'), ('CO2', 'HCO3-'), ('HAc', 'Ac-'), ('HPr', 'Pr-'), ('HBu', 'Bu-'), ('HVa', 'Va-') ''' return self.pKa_base-np.log10(np.exp(pc.T_correction_factor(self.T_base, self.T, self.Ka_dH))) - - @property - def alpha_IC(self): - '''[float] Charge per g of C.''' - pH = self.pH - pKa_IC = self.pKa[3] - return -1/(1+10**(pKa_IC-pH))/12 @property def alpha_IN(self): @@ -456,6 +449,13 @@ def alpha_IP(self): pH = self.pH pKa_IP = self.pKa[2] return 10**(pKa_IP-pH)/(1+10**(pKa_IP-pH))/31 + + @property + def alpha_IC(self): + '''[float] Charge per g of C.''' + pH = self.pH + pKa_IC = self.pKa[3] + return -1/(1+10**(pKa_IC-pH))/12 def _compile_AE(self): _state = self._state @@ -2106,7 +2106,7 @@ def alpha_vfa(self): # %% # While using this interface X_I.i_N in ASM2d should be 0.06, instead of 0.02. -class ASM2dtomADM1(ADMjunction): +class ASM2dtomADM1(mADMjunction): ''' Interface unit to convert activated sludge model (ASM) components to anaerobic digestion model (ADM) components. From 47a78bb1923bd5363645fca53f917b7d824fe4a5 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Fri, 22 Mar 2024 16:44:29 -0500 Subject: [PATCH 280/424] TN balance check: ASM2d-mADM1 Partially unresolved: When the ASM2d-mADM1 checks for TKN balance, it is not using updated value of ASM(X_I.i_N) even after I update while initializing the system. For now, I have made a modification in the interface itself. --- qsdsan/sanunits/_junction.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/qsdsan/sanunits/_junction.py b/qsdsan/sanunits/_junction.py index 90213306..0274d6f3 100644 --- a/qsdsan/sanunits/_junction.py +++ b/qsdsan/sanunits/_junction.py @@ -378,6 +378,7 @@ def __init__(self, ID='', upstream=None, downstream=(), thermo=None, init_with='WasteStream', F_BM_default=None, isdynamic=False, adm1_model=None): self.adm1_model = adm1_model # otherwise there won't be adm1_model when `_compile_reactions` is called + if thermo is None: warn('No `thermo` object is provided and is prone to raise error. ' 'If you are not sure how to get the `thermo` object, ' @@ -2180,7 +2181,12 @@ def balance_cod_tkn_tp(self, asm_vals, adm_vals): asm_i_P = cmps_asm.i_P adm_i_P = cmps_adm.i_P asm_cod = sum(asm_vals*asm_i_COD) - asm_tkn = sum(asm_vals*asm_i_N) - sum(asm_vals[non_tkn_idx]) + + # to ensure correct mechanism to check TN balance in case of mADM1 interfaces + X_I_asm = cmps_asm.indices(('X_I',)) + X_I_adm = cmps_adm.indices(('X_I',)) + asm_tkn = sum(asm_vals*asm_i_N) - sum(asm_vals[non_tkn_idx]) - asm_vals[X_I_asm]*asm_i_N[X_I_asm] + asm_vals[X_I_asm]*adm_i_N[X_I_adm] + asm_tp = sum(asm_vals*asm_i_P) cod_bl, cod_err, cod_tol, adm_cod = self.isbalanced(asm_cod, adm_vals, adm_i_COD) tkn_bl, tkn_err, tkn_tol, adm_tkn = self.isbalanced(asm_tkn, adm_vals, adm_i_N) @@ -2227,7 +2233,7 @@ def balance_cod_tkn_tp(self, asm_vals, adm_vals): f'effluent TKN is {adm_tkn} or {adm_tkn*(1+dtkn)}. ' 'To balance TKN please ensure ASM2d(X_I.i_N) = ADM1(X_I.i_N)') return adm_vals - elif cod_bl and tkn_bl: + elif cod_bl and tkn_bl: if tp_bl: return adm_vals else: From 896c7edfa8f35bd5b37aa9b88c723485a2c1ee16 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Fri, 22 Mar 2024 16:45:22 -0500 Subject: [PATCH 281/424] Update in mADM1 process MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Partially unresolved: I need enthalpy of formation for ‘H3PO4/H2PO4-’ which I couldn’t find reliably. Filled in a placeholder value of 5000 J/mol for now. --- qsdsan/processes/_adm1_p_extension.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/qsdsan/processes/_adm1_p_extension.py b/qsdsan/processes/_adm1_p_extension.py index 217fc744..2d27e649 100644 --- a/qsdsan/processes/_adm1_p_extension.py +++ b/qsdsan/processes/_adm1_p_extension.py @@ -562,7 +562,9 @@ class ADM1_p_extension(CompiledProcesses): Ka_dH : iterable[float], optional Heat of reaction of each acid-base pair at base temperature [J/mol], following the order of `ADM1._acid_base_pairs`. The default is - [55900, 51965, 7646, 0, 0, 0, 0]. + [55900, 51965, 5000, 7646, 0, 0, 0, 0]. + ('H+', 'OH-'), ('NH4+', 'NH3'), ('H3PO4', 'H2PO4 2-'), ('CO2', 'HCO3-'), + ('HAc', 'Ac-'), ('HPr', 'Pr-'), ('HBu', 'Bu-'), ('HVa', 'Va-') kLa : float, optional Liquid-gas mass transfer coefficient [d^(-1)]. The default is 200. K_H_base : iterable[float], optional @@ -629,7 +631,7 @@ def __new__(cls, components=None, path=None, N_xc=2.686e-3, N_I=4.286e-3, N_aa=7 KI_h2_fa=5e-6, KI_h2_c4=1e-5, KI_h2_pro=3.5e-6, KI_nh3=1.8e-3, KS_IN=1e-4, KS_IP=2e-5, pH_limits_aa=(4,5.5), pH_limits_ac=(6,7), pH_limits_h2=(5,6), T_base=298.15, pKa_base=[14, 9.25, 2.12, 6.35, 4.76, 4.88, 4.82, 4.86], - Ka_dH=[55900, 51965, 7646, 0, 0, 0, 0], + Ka_dH=[55900, 51965, 5000, 7646, 0, 0, 0, 0], kLa=200, K_H_base=[7.8e-4, 1.4e-3, 3.5e-2], K_H_dH=[-4180, -14240, -19410], **kwargs): From db5bad815b28010e88341a10a091770edceadbcc Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Tue, 26 Mar 2024 10:00:58 -0500 Subject: [PATCH 282/424] Updated mADM1-ASM2d junction Added name for contribution, and removed an irrelevant comment. --- qsdsan/sanunits/_junction.py | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/qsdsan/sanunits/_junction.py b/qsdsan/sanunits/_junction.py index 0274d6f3..084c314c 100644 --- a/qsdsan/sanunits/_junction.py +++ b/qsdsan/sanunits/_junction.py @@ -7,6 +7,8 @@ Joy Zhang Yalin Li + + Saumitra Rai This module is under the University of Illinois/NCSA Open Source License. Please refer to https://github.com/QSD-Group/QSDsan/blob/main/LICENSE.txt @@ -1909,20 +1911,6 @@ def madm12asm2d(adm_vals): bio_n = sum((adm_vals*adm_i_N)[adm_bio_N_indices]) bio_p = sum((adm_vals*adm_i_P)[adm_bio_P_indices]) - # There is no X_P (particulate products arising due to biomass decay) - # or equivalent component in ASM2d - # xp_cod = bio_cod * (1-self.bio_to_xs) - # xp_ndm = xp_cod*X_P_i_N - # if xp_ndm > bio_n: - # warn('Not enough biomass N to map the specified proportion of ' - # 'biomass COD into X_P. Mapped as much COD as possible, the rest ' - # 'goes to X_S.') - # X_P = bio_n/asm_X_P_i_N - # bio_n = 0 - # else: - # X_P = xp_cod - # bio_n -= xp_ndm - # X_S = bio_cod - X_P # COD balance @@ -1967,6 +1955,10 @@ def madm12asm2d(adm_vals): raise RuntimeError('Not enough phosphorous (S_IP + biomass) to map ' 'all biomass COD into X_S') + + + + # Step 1b: convert particulate substrates into X_S # First we calculate the COD, N, and P content in particulate substrate From 8e5a7b0ce40a4b6fa06d8c8286e1470f72bc2d6e Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Tue, 26 Mar 2024 11:07:33 -0500 Subject: [PATCH 283/424] Step 1 (mADM1-ASM2d) Updated part of Step 1 of mADM1-ASM2d junction. Mapping of biomass to X_S. --- qsdsan/sanunits/_junction.py | 86 +++++++++++++++++++----------------- 1 file changed, 45 insertions(+), 41 deletions(-) diff --git a/qsdsan/sanunits/_junction.py b/qsdsan/sanunits/_junction.py index 084c314c..7f3c21e8 100644 --- a/qsdsan/sanunits/_junction.py +++ b/qsdsan/sanunits/_junction.py @@ -1907,57 +1907,61 @@ def madm12asm2d(adm_vals): _ions = np.array([S_IN, S_IC, S_IP, X_PP, S_Mg, S_K, S_ac, S_pro, S_bu, S_va]) # Step 1a: convert biomass into X_S+X_ND + + # What is available bio_cod = X_su + X_aa + X_fa + X_c4 + X_pro + X_ac + X_h2 bio_n = sum((adm_vals*adm_i_N)[adm_bio_N_indices]) bio_p = sum((adm_vals*adm_i_P)[adm_bio_P_indices]) - # X_S = bio_cod - X_P - # COD balance - X_S = bio_cod + #!!! In default ASM2d stoichiometry, biomass decay (cell lysis) + #!!! yields 90% particulate substrate + 10% X_I + #!!! so: convert both biomass and X_I in adm to X_S and X_I in asm - # N balance - X_S_N = X_S*X_S_i_N - if X_S_N <= bio_n: - # In ASM1-ADM1 interface if there was excess bio_n compared to - # X_S_N, it was transferred to X_ND. But since X_ND does not - # exist in ASM2d, S_IN is used for N balance - # Here, S_IN is also used based on the fact that in case of - # deficit bio_N, S_IN is used to compensate for excess X_S_N - # in Nopens at al. 2009 - S_IN += (bio_n - X_S_N) - bio_n = 0 - elif X_S_N <= bio_n + S_IN: - S_IN -= (X_S_N - bio_n) - bio_n = 0 - else: - if isclose(X_S_N, bio_n + S_IN, rel_tol=rtol, abs_tol=atol): - S_IN = bio_n = 0 - else: - raise RuntimeError('Not enough nitrogen (S_IN + biomass) to map ' - 'all biomass COD into X_S') + # What is available + xi_n = X_I*adm_X_I_i_N + xi_p = X_I*adm_X_I_i_P + + # What would be formed by X_S + xs_cod = bio_cod * 0.9 + xs_ndm = xs_cod * X_S_i_N + xs_pdm = xs_cod * X_S_i_P - # P balance - X_S_P = X_S*X_S_i_P - if X_S_P <= bio_p: - # Here, S_IP is based on the fact that in case of - # deficit bio_N, S_IN is used to compensate for excess X_S_N - # in Nopens at al. 2009 - S_IP += (bio_p - X_S_P) - bio_p = 0 - elif X_S_P <= bio_p + S_IP: - S_IP -= (X_S_P - bio_p) + # What would be formed by X_I (ASM2d) + xi_cod = bio_cod * 0.1 + X_I + xi_ndm = xi_cod * asm_X_I_i_N + xi_pdm = xi_cod * asm_X_I_i_P + + if xs_ndm <= bio_n and xs_pdm <= bio_p: + X_S = xs_cod + xs_cod = 0 + bio_n -= xs_ndm + bio_p -= xs_pdm + elif xs_ndm > bio_n and xs_pdm <= bio_p: + warn('Not enough biomass N to map the specified proportion of ' + 'biomass COD into X_S. Rest of the biomass COD goes to S_A in last step') + X_S = bio_n / X_S_i_N + xs_cod -= X_S + bio_n = 0 + bio_p -= X_S*X_S_i_P # Here bio_p would certainly remain positive + elif xs_ndm <= bio_n and xs_pdm > bio_p: + warn('Not enough biomass P to map the specified proportion of ' + 'biomass COD into X_S. Rest of the biomass COD goes to S_A in last step') + X_S = bio_p / X_S_i_P + xs_cod -= X_S bio_p = 0 + bio_n -= X_S*X_S_i_N # Here bio_n would certainly remain positive else: - if isclose(X_S_P, bio_p + S_IP, rel_tol=rtol, abs_tol=atol): - S_IP = bio_p = 0 + if bio_p / X_S_i_P < bio_n / X_S_i_N: + X_S = bio_p / X_S_i_P + xs_cod -= X_S + bio_p = 0 + bio_n -= X_S*X_S_i_N # mathematically, bio_n can become negative at this point else: - raise RuntimeError('Not enough phosphorous (S_IP + biomass) to map ' - 'all biomass COD into X_S') - - - - + X_S = bio_n / X_S_i_N + xs_cod -= X_S + bio_n = 0 + bio_p -= X_S*X_S_i_P # mathematically, bio_p can become negative at this point # Step 1b: convert particulate substrates into X_S From b01ea4b35103bea814f95ae8057d6c97bec888a0 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Tue, 26 Mar 2024 11:40:14 -0500 Subject: [PATCH 284/424] Step 1: mADM1-ASM2d Updated part of Step 1 of mADM1-ASM2d junction. Mapping of particulate inert and biomass to X_I. --- qsdsan/sanunits/_junction.py | 101 +++++++++++++++++++++++++++-------- 1 file changed, 80 insertions(+), 21 deletions(-) diff --git a/qsdsan/sanunits/_junction.py b/qsdsan/sanunits/_junction.py index 7f3c21e8..bfd98a54 100644 --- a/qsdsan/sanunits/_junction.py +++ b/qsdsan/sanunits/_junction.py @@ -1931,37 +1931,96 @@ def madm12asm2d(adm_vals): xi_cod = bio_cod * 0.1 + X_I xi_ndm = xi_cod * asm_X_I_i_N xi_pdm = xi_cod * asm_X_I_i_P - + + # MAPPING OF X_S + + # Case I: Both bio_N and bio_P are sufficient if xs_ndm <= bio_n and xs_pdm <= bio_p: X_S = xs_cod xs_cod = 0 bio_n -= xs_ndm bio_p -= xs_pdm - elif xs_ndm > bio_n and xs_pdm <= bio_p: - warn('Not enough biomass N to map the specified proportion of ' - 'biomass COD into X_S. Rest of the biomass COD goes to S_A in last step') - X_S = bio_n / X_S_i_N - xs_cod -= X_S - bio_n = 0 - bio_p -= X_S*X_S_i_P # Here bio_p would certainly remain positive - elif xs_ndm <= bio_n and xs_pdm > bio_p: - warn('Not enough biomass P to map the specified proportion of ' - 'biomass COD into X_S. Rest of the biomass COD goes to S_A in last step') - X_S = bio_p / X_S_i_P - xs_cod -= X_S - bio_p = 0 - bio_n -= X_S*X_S_i_N # Here bio_n would certainly remain positive else: - if bio_p / X_S_i_P < bio_n / X_S_i_N: + # Case II, III, and, IV: At least one of the two biological N/P is not sufficient + if bio_p / X_S_i_P > bio_n / X_S_i_N: + warn('Not enough biomass N to map the specified proportion of ' + 'biomass COD into X_S. Rest of the biomass COD goes to S_A in last step') + X_S = bio_n / X_S_i_N + xs_cod -= X_S + bio_n = 0 + bio_p -= X_S*X_S_i_P #mathematically, bio_p can become negative at this point + if bio_p < 0: + S_IP += bio_p + bio_p = 0 + else: + warn('Not enough biomass P to map the specified proportion of ' + 'biomass COD into X_S. Rest of the biomass COD goes to S_A in last step') X_S = bio_p / X_S_i_P xs_cod -= X_S bio_p = 0 - bio_n -= X_S*X_S_i_N # mathematically, bio_n can become negative at this point + bio_n -= X_S*X_S_i_N #mathematically, bio_n can become negative at this point + if bio_n < 0: + S_IN += bio_n + bio_n = 0 + + + # MAPPING OF X_I + + if xi_ndm < bio_n + xi_n + S_IN and xi_pdm < bio_p + xi_p + S_IP: + + X_I = xi_cod + xi_cod = 0 + + xi_n -= xi_ndm + if xi_n < 0: + bio_n += xi_n + xi_n = 0 + if bio_n < 0: + S_IN += bio_n + bio_n = 0 + + xi_p -= xi_pdm + if xi_p < 0: + bio_p += xi_p + xi_p = 0 + if bio_p < 0: + S_IP += bio_p + bio_p = 0 + + else: + if (bio_p + xi_p + S_IP) / asm_X_I_i_P > (bio_n + xi_n + S_IN) / asm_X_I_i_N: + + warn('Not enough N in biomass and X_I to map the specified proportion of' + 'biomass COD into X_I. Rest of the biomass COD goes to S_A') + X_I = (bio_n + xi_n + S_IN) / asm_X_I_i_N + xi_cod -= X_I + + bio_n = xi_n = S_IN = 0 + + xi_p -= X_I*asm_X_I_i_P + if xi_p < 0: + bio_p += xi_p + xi_p = 0 + if bio_p < 0: + S_IP += bio_p + bio_p = 0 + else: - X_S = bio_n / X_S_i_N - xs_cod -= X_S - bio_n = 0 - bio_p -= X_S*X_S_i_P # mathematically, bio_p can become negative at this point + + warn('Not enough P in biomass and X_I to map the specified proportion of' + 'biomass COD into X_I. Rest of the biomass COD goes to S_A') + X_I = (bio_p + xi_p + S_IP) / asm_X_I_i_P + xi_cod -= X_I + + bio_p = xi_p = S_IP = 0 + + xi_n -= X_I*asm_X_I_i_N + if xi_n < 0: + bio_n += xi_n + xi_n = 0 + if bio_n < 0: + S_IN += bio_n + bio_n = 0 # Step 1b: convert particulate substrates into X_S From 49d8745579b2c88e007ae5c099c7e36372fedbb7 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Tue, 26 Mar 2024 13:19:35 -0500 Subject: [PATCH 285/424] Step 1 (b) mADM1-ASM2d Mapped particulate substrates into X_S. --- qsdsan/sanunits/_junction.py | 60 +++++++++++++++++++----------------- 1 file changed, 32 insertions(+), 28 deletions(-) diff --git a/qsdsan/sanunits/_junction.py b/qsdsan/sanunits/_junction.py index bfd98a54..9695dc7a 100644 --- a/qsdsan/sanunits/_junction.py +++ b/qsdsan/sanunits/_junction.py @@ -1906,7 +1906,7 @@ def madm12asm2d(adm_vals): # Not sure about charge on X_PP, S_Mg, S_K (PHA and PAO would have zero charge) _ions = np.array([S_IN, S_IC, S_IP, X_PP, S_Mg, S_K, S_ac, S_pro, S_bu, S_va]) - # Step 1a: convert biomass into X_S+X_ND + # Step 1a: convert biomass and inert particulates into X_S and X_I # What is available bio_cod = X_su + X_aa + X_fa + X_c4 + X_pro + X_ac + X_h2 @@ -2023,37 +2023,41 @@ def madm12asm2d(adm_vals): bio_n = 0 # Step 1b: convert particulate substrates into X_S - - # First we calculate the COD, N, and P content in particulate substrate - xsub_cod = X_ch + X_pr + X_li + + xsub_cod = X_ch + X_pr + X_li xsub_n = X_pr*X_pr_i_N xsub_p = X_pr*X_pr_i_P - # Then we determine the amount of X_S required for COD, N, and P balance - X_S_cod = xsub_cod - X_S_n = xsub_n/X_S_i_N - X_S_p = xsub_p/X_S_i_P - - # Identify the limiting component and accordingly form X_S - X_S_add = min(X_S_cod, X_S_n, X_S_p) - X_S += X_S_add - - # Calculate the imbalance in the non-limiting components - if X_S_add == X_S_cod: - def_N = (xsub_n - X_S_add*X_S_i_N) - def_P = (xsub_p - X_S_add*X_S_i_P) - S_IN += def_N - S_IP += def_P - elif X_S_add == X_S_n: - def_COD = (xsub_cod - X_S_add) - def_P = (xsub_p - X_S_add*X_S_i_P) - S_A = def_COD # suggested by Jeremy - S_IP += def_P + xs_ndm = xsub_cod * X_S_i_N + xs_pdm = xsub_cod * X_S_i_P + + if xs_ndm <= xsub_n + bio_n and xs_pdm <= xsub_p + bio_p: + X_S += xsub_cod + xsub_cod = 0 + xsub_n -= xs_ndm + xsub_p -= xs_pdm else: - def_COD = (xsub_cod - X_S_add) - def_N = (xsub_n - X_S_add*X_S_i_N) - S_A = def_COD # suggested by Jeremy - S_IN += def_N + if (xsub_n + bio_n)/X_S_i_N < (xsub_p + bio_p)/X_S_i_P: + X_S_temp = (xsub_n + bio_n)/X_S_i_N + X_S += X_S_temp + xsub_cod -= X_S_temp + xsub_n = bio_n = 0 + + xsub_p -= X_S_temp*X_S_i_P + if xsub_p < 0: + bio_p += xsub_p + xsub_p = 0 + + else: + X_S_temp = (xsub_p + bio_p)/X_S_i_P + X_S += X_S_temp + xsub_cod -= X_S_temp + xsub_p = bio_p = 0 + + xsub_n -= X_S_temp*X_S_i_N + if xsub_n < 0: + bio_n += xsub_n + xsub_n = 0 # Step 2: map all X_I from ADM to ASM excess_XIn = X_I * (adm_X_I_i_N - asm_X_I_i_N) From 601df978d88c1a9eef5d3b6b081e19145e2e2c6d Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Tue, 26 Mar 2024 13:22:51 -0500 Subject: [PATCH 286/424] mADM1-ASM2d interface Deleting some incorrect steps --- qsdsan/sanunits/_junction.py | 41 ------------------------------------ 1 file changed, 41 deletions(-) diff --git a/qsdsan/sanunits/_junction.py b/qsdsan/sanunits/_junction.py index 9695dc7a..37108720 100644 --- a/qsdsan/sanunits/_junction.py +++ b/qsdsan/sanunits/_junction.py @@ -2059,49 +2059,8 @@ def madm12asm2d(adm_vals): bio_n += xsub_n xsub_n = 0 - # Step 2: map all X_I from ADM to ASM - excess_XIn = X_I * (adm_X_I_i_N - asm_X_I_i_N) - S_IN += excess_XIn - if S_IN < 0: - if isclose(S_IN, 0, rel_tol=rtol, abs_tol=atol): S_IN = 0 - # This seems to be a typo. Shouldn't there be an else here? - raise RuntimeError('Not enough nitrogen (X_I + S_IN) to map ' - 'all ADM X_I into ASM X_I') - - excess_XIp = X_I * (adm_X_I_i_P - asm_X_I_i_P) - S_IP += excess_XIp - if S_IP < 0: - if isclose(S_IP, 0, rel_tol=rtol, abs_tol=atol): S_IP = 0 - # This seems to be a typo. Shouldn't there be an else here? - raise RuntimeError('Not enough phosphorous (X_I + S_IP) to map ' - 'all ADM X_I into ASM X_I') - # Step 3: map ADM S_I into ASM S_I and S_NH4 - excess_SIn = S_I * (adm_S_I_i_N - asm_S_I_i_N) - if excess_SIn > 0: - S_NH4 = excess_SIn - else: - S_NH4 = 0 - S_IN += excess_SIn - if S_IN < 0: - if isclose(S_IN, 0, rel_tol=rtol, abs_tol=atol): S_IN = 0 - # This seems to be a typo. Shouldn't there be an else here? - raise RuntimeError('Not enough nitrogen (S_I + S_IN) to map ' - 'all ADM S_I into ASM S_I') - S_NH4 += S_IN - excess_SIp = S_I * (adm_S_I_i_P - asm_S_I_i_P) - if excess_SIp > 0: - S_PO4 = excess_SIp - else: - S_PO4 = 0 - S_IP += excess_SIp - if S_IP < 0: - if isclose(S_IP, 0, rel_tol=rtol, abs_tol=atol): S_IP = 0 - # This seems to be a typo. Shouldn't there be an else here? - raise RuntimeError('Not enough nitrogen (S_I + S_IP) to map ' - 'all ADM S_I into ASM S_I') - S_PO4 += S_IP # Step 4: map all soluble substrates into S_A and S_F ssub_cod = S_su + S_aa + S_fa + S_va + S_bu + S_pro + S_ac From cc417d619cc5e5e91f685a05487825c0eb40f5f4 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Tue, 26 Mar 2024 14:17:58 -0500 Subject: [PATCH 287/424] Mapped soluble substrates: mADM1-ASM2d Mapped soluble substrates while conserving COD, TKN, and TP. --- qsdsan/sanunits/_junction.py | 55 +++++++++++++++++++++++------------- 1 file changed, 35 insertions(+), 20 deletions(-) diff --git a/qsdsan/sanunits/_junction.py b/qsdsan/sanunits/_junction.py index 37108720..e84ee4fd 100644 --- a/qsdsan/sanunits/_junction.py +++ b/qsdsan/sanunits/_junction.py @@ -1849,6 +1849,7 @@ def _compile_reactions(self): X_pr_i_P = cmps_adm.X_pr.i_P adm_X_I_i_P = cmps_adm.X_I.i_P adm_S_I_i_P = cmps_adm.S_I.i_P + S_aa_i_P = cmps_adm.S_aa.i_P adm_i_P = cmps_adm.i_P adm_bio_P_indices = cmps_adm.indices(('X_su', 'X_aa', 'X_fa', 'X_c4', 'X_pro', 'X_ac', 'X_h2')) @@ -2059,31 +2060,45 @@ def madm12asm2d(adm_vals): bio_n += xsub_n xsub_n = 0 - - - - # Step 4: map all soluble substrates into S_A and S_F - ssub_cod = S_su + S_aa + S_fa + S_va + S_bu + S_pro + S_ac + # P balance not required as S_su, S_aa, S_fa do not have P + ssub_cod = S_su + S_aa + S_fa ssub_n = S_aa * S_aa_i_N + ssub_p = S_aa * S_aa_i_P # which would be 0 - # P balance not required as all the 7 soluble substrates have no P - # N balance - S_F = ssub_n/S_F_i_N - # COD balance - S_A += ssub_cod - S_F - - # Technically both S_A_i_N and S_A_i_P would be 0 - if S_A_i_N > 0 or S_A_i_P > 0: - # excess N formed subtracted from S_NH4 - S_NH4 -= (ssub_cod - S_F)*S_A_i_N - # excess P (due to S_A) formed subtracted from S_PO4 - S_PO4 -= (ssub_cod - S_F)*S_A_i_P + sf_ndm = ssub_cod * S_F_i_N + sf_pdm = ssub_cod * S_F_i_P + + if sf_ndm > ssub_n + xsub_n + bio_n and sf_pdm > ssub_p + xsub_p + bio_p: - if S_F_i_P > 0: - # excess P (due to S_F) formed subtracted from S_PO4 - S_PO4 -= S_F*S_F_i_P + S_F = ssub_cod + ssub_cod = 0 + ssub_n -= sf_ndm + if ssub_n < 0: + xsub_n += ssub_n + ssub_n = 0 + if xsub_n < 0: + bio_n += xsub_n + xsub_n = 0 + else: + if (ssub_n + xsub_n + bio_n) / S_F_i_N < (ssub_p + xsub_p + bio_p) / S_F_i_P: + + S_F = (ssub_n + xsub_n + bio_n) / S_F_i_N + ssub_cod -= S_F + ssub_n = xsub_n = bio_n = 0 + + ssub_p -= sf_pdm + + if ssub_p < 0: + xsub_p += ssub_p + ssub_p = 0 + if xsub_p < 0: + bio_p += xsub_p + xsub_p = 0 + + # N and P balance not required as S_su, S_aa, S_fa do not have N and P + S_A = S_ac + S_pro + S_bu + S_va # Step 6: check COD and TKN balance asm_vals = np.array(([ From ced062b9e08695b1da1e92d73e72516f23436375 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Tue, 26 Mar 2024 22:42:57 -0500 Subject: [PATCH 288/424] mADM1-ASM2d: Mapping of soluble inert Mapping of soluble inert --- qsdsan/sanunits/_junction.py | 63 ++++++++++++++++++++++++++++++++++-- 1 file changed, 60 insertions(+), 3 deletions(-) diff --git a/qsdsan/sanunits/_junction.py b/qsdsan/sanunits/_junction.py index e84ee4fd..03a81f33 100644 --- a/qsdsan/sanunits/_junction.py +++ b/qsdsan/sanunits/_junction.py @@ -1899,6 +1899,7 @@ def _compile_reactions(self): # To convert components from mADM1 to ASM2d (madm1-2-asm2d) def madm12asm2d(adm_vals): + S_su, S_aa, S_fa, S_va, S_bu, S_pro, S_ac, S_h2, S_ch4, S_IC, S_IN, S_IP, S_I, \ X_ch, X_pr, X_li, X_su, X_aa, X_fa, X_c4, X_pro, X_ac, X_h2, X_I, \ X_PHA, X_PP, X_PAO, S_K, S_Mg, X_MeOH, X_MeP, S_cat, S_an, H2O = adm_vals @@ -2099,14 +2100,68 @@ def madm12asm2d(adm_vals): # N and P balance not required as S_su, S_aa, S_fa do not have N and P S_A = S_ac + S_pro + S_bu + S_va - + + si_cod = S_I + si_n = S_I * adm_S_I_i_N + si_p = S_I * adm_S_I_i_P + + si_ndm = si_cod * asm_S_I_i_N + si_pdm = si_cod * asm_S_I_i_P + + if si_ndm < si_n + xi_n + S_IN and si_pdm < si_p + xi_p + S_IP: + S_I = si_cod + si_cod = 0 + si_n -= si_ndm + if si_n < 0: + xi_n += si_n + si_n = 0 + if xi_n < 0: + S_IN += xi_n + xi_n = 0 + si_p -= si_pdm + if si_p < 0: + xi_p += si_p + si_p = 0 + if xi_p < 0: + S_IP += xi_p + xi_p = 0 + else: + if (si_n + xi_n + S_IN) / asm_S_I_i_N < (si_p + xi_p + S_IP) / asm_S_I_i_P: + S_I = (si_n + xi_n + S_IN) / asm_S_I_i_N + si_cod -= S_I + si_n = xi_n = S_IN = 0 + si_p -= S_I * asm_S_I_i_P + if si_p < 0: + xi_p += si_p + si_p = 0 + if xi_p < 0: + S_IP += xi_p + xi_p = 0 + else: + S_I = (si_p + xi_p + S_IP) / asm_S_I_i_P + si_cod -= S_I + si_p = xi_p = S_IP = 0 + si_n -= S_I * asm_S_I_i_N + if si_n < 0: + xi_n += si_n + si_n = 0 + if xi_n < 0: + S_IN += xi_n + xi_n = 0 + + S_NH4 = S_IN + si_n + ssub_n + xsub_n + xi_n + bio_n + S_PO4 = S_IP + si_p + ssub_p + xsub_p + xi_p + bio_p + S_A += si_cod + ssub_cod + xsub_cod + xi_cod + xs_cod + + S_ALK = S_IC + # Step 6: check COD and TKN balance asm_vals = np.array(([ 0, 0, # S_O2, S_N2, S_NH4, - 0, + 0, # S_NO3 S_PO4, S_F, S_A, S_I, - 0, # S_ALK, + S_ALK, X_I, X_S, 0, # X_H, X_PAO, X_PP, X_PHA, # directly mapped @@ -2119,11 +2174,13 @@ def madm12asm2d(adm_vals): asm_vals = f_corr(adm_vals, asm_vals) # Step 5: charge balance for alkalinity + S_NH4 = asm_vals[asm_ions_idx[0]] S_A = asm_vals[asm_ions_idx[1]] S_NO3 = asm_vals[asm_ions_idx[2]] S_PO4 = asm_vals[asm_ions_idx[3]] X_PP = asm_vals[asm_ions_idx[4]] + # Need to include S_K, S_Mg in the charge balance (Maybe ask Joy/Jeremy) S_ALK = (sum(_ions * np.append([alpha_IN, alpha_IC, alpha_IP], alpha_vfa)) - (S_NH4/14 - S_A/64 - S_NO3/14 -1.5*S_PO4/31 - X_PP/31))*(-12) asm_vals[asm_ions_idx[5]] = S_ALK From 94b79368eac065bdab4a2bf06fa09f6fb43e6259 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Tue, 26 Mar 2024 23:18:48 -0500 Subject: [PATCH 289/424] Charge balance: mADM1-ASM2d Tried to complete charge balance. --- qsdsan/sanunits/_junction.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/qsdsan/sanunits/_junction.py b/qsdsan/sanunits/_junction.py index 03a81f33..10fbd70e 100644 --- a/qsdsan/sanunits/_junction.py +++ b/qsdsan/sanunits/_junction.py @@ -1906,6 +1906,8 @@ def madm12asm2d(adm_vals): # Step 0: snapshot of charged components # Not sure about charge on X_PP, S_Mg, S_K (PHA and PAO would have zero charge) + # Step 0: snapshot of charged components + # _ions = np.array([S_IN, S_IC, S_ac, S_pro, S_bu, S_va]) _ions = np.array([S_IN, S_IC, S_IP, X_PP, S_Mg, S_K, S_ac, S_pro, S_bu, S_va]) # Step 1a: convert biomass and inert particulates into X_S and X_I @@ -2153,15 +2155,13 @@ def madm12asm2d(adm_vals): S_PO4 = S_IP + si_p + ssub_p + xsub_p + xi_p + bio_p S_A += si_cod + ssub_cod + xsub_cod + xi_cod + xs_cod - S_ALK = S_IC - # Step 6: check COD and TKN balance asm_vals = np.array(([ 0, 0, # S_O2, S_N2, S_NH4, 0, # S_NO3 S_PO4, S_F, S_A, S_I, - S_ALK, + 0, # S_ALK(for now) X_I, X_S, 0, # X_H, X_PAO, X_PP, X_PHA, # directly mapped @@ -2175,14 +2175,22 @@ def madm12asm2d(adm_vals): # Step 5: charge balance for alkalinity + # asm_ions_idx = cmps_asm.indices(('S_NH4', 'S_A', 'S_NO3', 'S_PO4', 'X_PP', 'S_ALK')) + S_NH4 = asm_vals[asm_ions_idx[0]] S_A = asm_vals[asm_ions_idx[1]] S_NO3 = asm_vals[asm_ions_idx[2]] S_PO4 = asm_vals[asm_ions_idx[3]] X_PP = asm_vals[asm_ions_idx[4]] - # Need to include S_K, S_Mg in the charge balance (Maybe ask Joy/Jeremy) - S_ALK = (sum(_ions * np.append([alpha_IN, alpha_IC, alpha_IP], alpha_vfa)) - (S_NH4/14 - S_A/64 - S_NO3/14 -1.5*S_PO4/31 - X_PP/31))*(-12) + # Need to include S_K, S_Mg in the charge balance + + # _ions = np.array([S_IN, S_IC, S_ac, S_pro, S_bu, S_va]) + # S_ALK = (sum(_ions * np.append([alpha_IN, alpha_IC], alpha_vfa)) - S_NH/14)*(-12) + + _ions = np.array([S_IN, S_IC, S_IP, X_PP, S_Mg, S_K, S_ac, S_pro, S_bu, S_va]) + + S_ALK = (sum(_ions * np.append([alpha_IN, alpha_IC, alpha_IP], -1/31, 2, 1, alpha_vfa)) - (S_NH4/14 - S_A/64 - S_NO3/14 -1.5*S_PO4/31 - X_PP/31))*(-12) asm_vals[asm_ions_idx[5]] = S_ALK return asm_vals From 6e3e99bc25a095f38d741231b4d41fee1bf3a34c Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Tue, 26 Mar 2024 23:46:59 -0500 Subject: [PATCH 290/424] Minor debugging mADM1-ASM2d interface. --- qsdsan/sanunits/_junction.py | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/qsdsan/sanunits/_junction.py b/qsdsan/sanunits/_junction.py index 10fbd70e..b745eb37 100644 --- a/qsdsan/sanunits/_junction.py +++ b/qsdsan/sanunits/_junction.py @@ -30,6 +30,7 @@ 'ASM2dtoADM1', 'ADM1toASM2d', 'ASM2dtomADM1', + 'mADM1toASM2d', ) #%% @@ -1690,7 +1691,7 @@ def alpha_vfa(self): #%% -class mADM1toASM2d(ADMjunction): +class mADM1toASM2d(mADMjunction): ''' Interface unit to convert modified anaerobic digestion model no. 1 (ADM1) components to activated sludge model no. 2d (ASM2d) components. @@ -1726,8 +1727,15 @@ class mADM1toASM2d(ADMjunction): `math.isclose ` ''' + # User defined values - # bio_to_xs = 0.7 (Not using this split since no X_P exists in ASM2d) + bio_to_xs = 0.9 + + # Since we are matching PAOs directly from ASM2d to mADM1, it is important + # for PAOs to have identical N/P content across models + + adm_X_PAO_i_N = 0.07 + adm_X_PAO_i_P = 0.02 # Should be constants cod_vfa = np.array([64, 112, 160, 208]) @@ -1874,8 +1882,15 @@ def _compile_reactions(self): # Checks for direct mapping of X_PAO, X_PP, X_PHA # Check for X_PAO (measured as COD so i_COD = 1 in both ASM2d and ADM1) + asm_X_PAO_i_N = cmps_asm.X_PAO.i_N adm_X_PAO_i_N = cmps_adm.X_PAO.i_N + + if self.adm_X_PAO_i_N == None: + adm_X_PAO_i_N = cmps_adm.X_PAO.i_N + else: + adm_X_PAO_i_N = self.adm_X_PAO_i_N + if asm_X_PAO_i_N != adm_X_PAO_i_N: raise RuntimeError('X_PAO cannot be directly mapped as N content' f'in asm2d_X_PAO_i_N = {asm_X_PAO_i_N} is not equal to' @@ -1883,6 +1898,12 @@ def _compile_reactions(self): asm_X_PAO_i_P = cmps_asm.X_PAO.i_P adm_X_PAO_i_P = cmps_adm.X_PAO.i_P + + if self.adm_X_PAO_i_P == None: + adm_X_PAO_i_P = cmps_adm.X_PAO.i_P + else: + adm_X_PAO_i_P = self.adm_X_PAO_i_P + if asm_X_PAO_i_P != adm_X_PAO_i_P: raise RuntimeError('X_PAO cannot be directly mapped as P content' f'in asm2d_X_PAO_i_P = {asm_X_PAO_i_P} is not equal to' @@ -1927,12 +1948,12 @@ def madm12asm2d(adm_vals): xi_p = X_I*adm_X_I_i_P # What would be formed by X_S - xs_cod = bio_cod * 0.9 + xs_cod = bio_cod * self.bio_to_xs xs_ndm = xs_cod * X_S_i_N xs_pdm = xs_cod * X_S_i_P # What would be formed by X_I (ASM2d) - xi_cod = bio_cod * 0.1 + X_I + xi_cod = bio_cod * (1 - self.bio_to_xs) + X_I xi_ndm = xi_cod * asm_X_I_i_N xi_pdm = xi_cod * asm_X_I_i_P From 0a1540a82b43bd53096f5e8b531004c7a002e2a7 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Wed, 27 Mar 2024 00:35:31 -0500 Subject: [PATCH 291/424] Important debugging changes mADM1-ASM2d --- qsdsan/sanunits/_junction.py | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/qsdsan/sanunits/_junction.py b/qsdsan/sanunits/_junction.py index b745eb37..842d41f6 100644 --- a/qsdsan/sanunits/_junction.py +++ b/qsdsan/sanunits/_junction.py @@ -1736,6 +1736,7 @@ class mADM1toASM2d(mADMjunction): adm_X_PAO_i_N = 0.07 adm_X_PAO_i_P = 0.02 + asm_X_I_i_N = 0.06 # Should be constants cod_vfa = np.array([64, 112, 160, 208]) @@ -1785,6 +1786,7 @@ def balance_cod_tkn(self, adm_vals, asm_vals): f'effluent (ASM) COD is {asm_cod} or {asm_cod*(1+dcod)}. ') return asm_vals elif cod_bl and tp_bl: + print('sahi pakde hein') if tkn_bl: return asm_vals else: @@ -1868,7 +1870,13 @@ def _compile_reactions(self): X_S_i_N = cmps_asm.X_S.i_N S_F_i_N = cmps_asm.S_F.i_N S_A_i_N = cmps_asm.S_A.i_N - asm_X_I_i_N = cmps_asm.X_I.i_N + + # Due to issue with mapping of X_I across ASM2d and ADM1, making this user dependent is important + if self.asm_X_I_i_N == None: + asm_X_I_i_N = cmps_asm.X_I.i_N + else: + asm_X_I_i_N = self.asm_X_I_i_N + asm_S_I_i_N = cmps_asm.S_I.i_N asm_ions_idx = cmps_asm.indices(('S_NH4', 'S_A', 'S_NO3', 'S_PO4', 'X_PP', 'S_ALK')) @@ -2120,6 +2128,21 @@ def madm12asm2d(adm_vals): if xsub_p < 0: bio_p += xsub_p xsub_p = 0 + + else: + + S_F = (ssub_p + xsub_p + bio_p) / S_F_i_P + ssub_cod -= S_F + ssub_p = xsub_p = bio_p = 0 + + ssub_n -= sf_ndm + + if ssub_n < 0: + xsub_n += ssub_n + ssub_n = 0 + if xsub_n < 0: + bio_n += xsub_n + xsub_n = 0 # N and P balance not required as S_su, S_aa, S_fa do not have N and P S_A = S_ac + S_pro + S_bu + S_va @@ -2209,9 +2232,10 @@ def madm12asm2d(adm_vals): # _ions = np.array([S_IN, S_IC, S_ac, S_pro, S_bu, S_va]) # S_ALK = (sum(_ions * np.append([alpha_IN, alpha_IC], alpha_vfa)) - S_NH/14)*(-12) - _ions = np.array([S_IN, S_IC, S_IP, X_PP, S_Mg, S_K, S_ac, S_pro, S_bu, S_va]) + # _ions = np.array([S_IN, S_IC, S_IP, X_PP, S_Mg, S_K, S_ac, S_pro, S_bu, S_va]) - S_ALK = (sum(_ions * np.append([alpha_IN, alpha_IC, alpha_IP], -1/31, 2, 1, alpha_vfa)) - (S_NH4/14 - S_A/64 - S_NO3/14 -1.5*S_PO4/31 - X_PP/31))*(-12) + S_ALK = (sum(_ions * np.append([alpha_IN, alpha_IC, alpha_IP, -1/31, 2, 1], alpha_vfa)) - (S_NH4/14 - S_A/64 - S_NO3/14 -1.5*S_PO4/31 - X_PP/31))*(-12) + asm_vals[asm_ions_idx[5]] = S_ALK return asm_vals @@ -2220,8 +2244,7 @@ def madm12asm2d(adm_vals): @property def alpha_vfa(self): - # This may need change based on P-extension of ADM1 (ask Joy?) - return 1.0/self.cod_vfa*(-1.0/(1.0 + 10**(self.pKa[3:]-self.pH))) + return 1.0/self.cod_vfa*(-1.0/(1.0 + 10**(self.pKa[4:]-self.pH))) # %% From 984a1b9ca86b0305197bb0024aee1b208fe23428 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Wed, 27 Mar 2024 07:26:43 -0500 Subject: [PATCH 292/424] mADM1-ASM2d interface Corrected N balance --- qsdsan/sanunits/_junction.py | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/qsdsan/sanunits/_junction.py b/qsdsan/sanunits/_junction.py index 842d41f6..80f616e8 100644 --- a/qsdsan/sanunits/_junction.py +++ b/qsdsan/sanunits/_junction.py @@ -1753,8 +1753,9 @@ def balance_cod_tkn(self, adm_vals, asm_vals): asm_i_COD = cmps_asm.i_COD adm_i_COD = cmps_adm.i_COD gas_idx = cmps_adm.indices(('S_h2', 'S_ch4')) + asm_i_N = cmps_asm.i_N - adm_i_N = cmps_adm.i_N + adm_i_N = cmps_adm.i_N asm_i_P = cmps_asm.i_P adm_i_P = cmps_adm.i_P adm_cod = sum(adm_vals*adm_i_COD) - sum(adm_vals[gas_idx]) @@ -1788,6 +1789,7 @@ def balance_cod_tkn(self, adm_vals, asm_vals): elif cod_bl and tp_bl: print('sahi pakde hein') if tkn_bl: + print('bilkul sahi pakde hein') return asm_vals else: if tkn_err > 0: dtkn = -(tkn_err - tkn_tol)/asm_tkn @@ -1870,13 +1872,8 @@ def _compile_reactions(self): X_S_i_N = cmps_asm.X_S.i_N S_F_i_N = cmps_asm.S_F.i_N S_A_i_N = cmps_asm.S_A.i_N - - # Due to issue with mapping of X_I across ASM2d and ADM1, making this user dependent is important - if self.asm_X_I_i_N == None: - asm_X_I_i_N = cmps_asm.X_I.i_N - else: - asm_X_I_i_N = self.asm_X_I_i_N - + + asm_X_I_i_N = cmps_asm.X_I.i_N asm_S_I_i_N = cmps_asm.S_I.i_N asm_ions_idx = cmps_asm.indices(('S_NH4', 'S_A', 'S_NO3', 'S_PO4', 'X_PP', 'S_ALK')) @@ -1932,6 +1929,8 @@ def madm12asm2d(adm_vals): S_su, S_aa, S_fa, S_va, S_bu, S_pro, S_ac, S_h2, S_ch4, S_IC, S_IN, S_IP, S_I, \ X_ch, X_pr, X_li, X_su, X_aa, X_fa, X_c4, X_pro, X_ac, X_h2, X_I, \ X_PHA, X_PP, X_PAO, S_K, S_Mg, X_MeOH, X_MeP, S_cat, S_an, H2O = adm_vals + + # print(f'adm_vals = {adm_vals}') # Step 0: snapshot of charged components # Not sure about charge on X_PP, S_Mg, S_K (PHA and PAO would have zero charge) @@ -2097,11 +2096,10 @@ def madm12asm2d(adm_vals): ssub_n = S_aa * S_aa_i_N ssub_p = S_aa * S_aa_i_P # which would be 0 - sf_ndm = ssub_cod * S_F_i_N sf_pdm = ssub_cod * S_F_i_P - if sf_ndm > ssub_n + xsub_n + bio_n and sf_pdm > ssub_p + xsub_p + bio_p: + if sf_ndm <= ssub_n + xsub_n + bio_n and sf_pdm <= ssub_p + xsub_p + bio_p: S_F = ssub_cod ssub_cod = 0 @@ -2113,6 +2111,15 @@ def madm12asm2d(adm_vals): if xsub_n < 0: bio_n += xsub_n xsub_n = 0 + + ssub_p -= sf_pdm + if ssub_p < 0: + xsub_p += ssub_p + ssub_p = 0 + if xsub_p < 0: + bio_p += xsub_p + xsub_p = 0 + else: if (ssub_n + xsub_n + bio_n) / S_F_i_N < (ssub_p + xsub_p + bio_p) / S_F_i_P: @@ -2120,7 +2127,7 @@ def madm12asm2d(adm_vals): ssub_cod -= S_F ssub_n = xsub_n = bio_n = 0 - ssub_p -= sf_pdm + ssub_p -= S_F*S_F_i_P if ssub_p < 0: xsub_p += ssub_p @@ -2135,7 +2142,7 @@ def madm12asm2d(adm_vals): ssub_cod -= S_F ssub_p = xsub_p = bio_p = 0 - ssub_n -= sf_ndm + ssub_n -= S_F*S_F_i_N if ssub_n < 0: xsub_n += ssub_n From f11a41a573cb0fea4042147cf7fae26d3fd3b73c Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Wed, 27 Mar 2024 07:52:29 -0500 Subject: [PATCH 293/424] N balance: mADM1-ASM2d Corrected N balance. --- qsdsan/sanunits/_junction.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/qsdsan/sanunits/_junction.py b/qsdsan/sanunits/_junction.py index 80f616e8..a2ef5f6d 100644 --- a/qsdsan/sanunits/_junction.py +++ b/qsdsan/sanunits/_junction.py @@ -1736,7 +1736,6 @@ class mADM1toASM2d(mADMjunction): adm_X_PAO_i_N = 0.07 adm_X_PAO_i_P = 0.02 - asm_X_I_i_N = 0.06 # Should be constants cod_vfa = np.array([64, 112, 160, 208]) @@ -1759,7 +1758,13 @@ def balance_cod_tkn(self, adm_vals, asm_vals): asm_i_P = cmps_asm.i_P adm_i_P = cmps_adm.i_P adm_cod = sum(adm_vals*adm_i_COD) - sum(adm_vals[gas_idx]) - adm_tkn = sum(adm_vals*adm_i_N) + + # to ensure correct mechanism to check TN balance in case of mADM1 interfaces + X_PAO_asm_idx = cmps_asm.indices(('X_PAO',)) + X_PAO_adm_idx = cmps_adm.indices(('X_PAO',)) + adm_tkn = sum(adm_vals*adm_i_N) - adm_vals[X_PAO_adm_idx]*adm_i_N[X_PAO_adm_idx] - adm_vals[X_PAO_adm_idx]*asm_i_N[X_PAO_asm_idx] + + # adm_tkn = sum(adm_vals*adm_i_N) adm_tp = sum(adm_vals*adm_i_P) cod_bl, cod_err, cod_tol, asm_cod = self.isbalanced(adm_cod, asm_vals, asm_i_COD) @@ -1787,9 +1792,7 @@ def balance_cod_tkn(self, adm_vals, asm_vals): f'effluent (ASM) COD is {asm_cod} or {asm_cod*(1+dcod)}. ') return asm_vals elif cod_bl and tp_bl: - print('sahi pakde hein') if tkn_bl: - print('bilkul sahi pakde hein') return asm_vals else: if tkn_err > 0: dtkn = -(tkn_err - tkn_tol)/asm_tkn @@ -2161,7 +2164,7 @@ def madm12asm2d(adm_vals): si_ndm = si_cod * asm_S_I_i_N si_pdm = si_cod * asm_S_I_i_P - if si_ndm < si_n + xi_n + S_IN and si_pdm < si_p + xi_p + S_IP: + if si_ndm <= si_n + xi_n + S_IN and si_pdm <= si_p + xi_p + S_IP: S_I = si_cod si_cod = 0 si_n -= si_ndm @@ -2332,9 +2335,9 @@ def balance_cod_tkn_tp(self, asm_vals, adm_vals): asm_cod = sum(asm_vals*asm_i_COD) # to ensure correct mechanism to check TN balance in case of mADM1 interfaces - X_I_asm = cmps_asm.indices(('X_I',)) - X_I_adm = cmps_adm.indices(('X_I',)) - asm_tkn = sum(asm_vals*asm_i_N) - sum(asm_vals[non_tkn_idx]) - asm_vals[X_I_asm]*asm_i_N[X_I_asm] + asm_vals[X_I_asm]*adm_i_N[X_I_adm] + X_I_asm_idx = cmps_asm.indices(('X_I',)) + X_I_adm_idx = cmps_adm.indices(('X_I',)) + asm_tkn = sum(asm_vals*asm_i_N) - sum(asm_vals[non_tkn_idx]) - asm_vals[X_I_asm_idx]*asm_i_N[X_I_asm_idx] + asm_vals[X_I_asm_idx]*adm_i_N[X_I_adm_idx] asm_tp = sum(asm_vals*asm_i_P) cod_bl, cod_err, cod_tol, adm_cod = self.isbalanced(asm_cod, adm_vals, adm_i_COD) From 5d91439ece7bad83521e7b016c5ca136e67fd7b1 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Wed, 27 Mar 2024 11:30:41 -0500 Subject: [PATCH 294/424] Editing unnecessary part This is not required. --- qsdsan/sanunits/_junction.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/qsdsan/sanunits/_junction.py b/qsdsan/sanunits/_junction.py index a2ef5f6d..bc8d2cfd 100644 --- a/qsdsan/sanunits/_junction.py +++ b/qsdsan/sanunits/_junction.py @@ -2329,16 +2329,12 @@ def balance_cod_tkn_tp(self, asm_vals, adm_vals): adm_i_COD = cmps_adm.i_COD non_tkn_idx = cmps_asm.indices(('S_N2', 'S_NO3')) asm_i_N = cmps_asm.i_N + adm_i_N = cmps_adm.i_N asm_i_P = cmps_asm.i_P adm_i_P = cmps_adm.i_P asm_cod = sum(asm_vals*asm_i_COD) - - # to ensure correct mechanism to check TN balance in case of mADM1 interfaces - X_I_asm_idx = cmps_asm.indices(('X_I',)) - X_I_adm_idx = cmps_adm.indices(('X_I',)) - asm_tkn = sum(asm_vals*asm_i_N) - sum(asm_vals[non_tkn_idx]) - asm_vals[X_I_asm_idx]*asm_i_N[X_I_asm_idx] + asm_vals[X_I_asm_idx]*adm_i_N[X_I_adm_idx] - + asm_tkn = sum(asm_vals*asm_i_N) - sum(asm_vals[non_tkn_idx]) asm_tp = sum(asm_vals*asm_i_P) cod_bl, cod_err, cod_tol, adm_cod = self.isbalanced(asm_cod, adm_vals, adm_i_COD) tkn_bl, tkn_err, tkn_tol, adm_tkn = self.isbalanced(asm_tkn, adm_vals, adm_i_N) @@ -2346,6 +2342,7 @@ def balance_cod_tkn_tp(self, asm_vals, adm_vals): if tkn_bl and tp_bl: if cod_bl: + print('Sab changa si') return adm_vals else: if cod_err > 0: dcod = -(cod_err - cod_tol)/adm_cod From afbab6546bff57002fd4b83fb6080192bf03a99f Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Wed, 27 Mar 2024 11:42:03 -0500 Subject: [PATCH 295/424] Update pKa used to calculate delta of S_IP pKa2 (7.20) should be used to calculate delta of S_IP, since it would be the reaction forming dominant specie between pH 5-8. --- qsdsan/processes/_adm1_p_extension.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/qsdsan/processes/_adm1_p_extension.py b/qsdsan/processes/_adm1_p_extension.py index 2d27e649..cac391db 100644 --- a/qsdsan/processes/_adm1_p_extension.py +++ b/qsdsan/processes/_adm1_p_extension.py @@ -232,7 +232,7 @@ def acid_base_rxn(h_ion, weak_acids_tot, Kas): # h, nh4, hco3, ac, pr, bu, va = mols # S_cat, S_K, S_Mg, S_an, S_IN, S_IP, S_IC, S_ac, S_pro, S_bu, S_va = weak_acids_tot # in M S_cat, S_K, S_Mg, S_an, S_IN, S_IP = weak_acids_tot[:6] - # Kw, Ka_nh, Ka_h3po4, Ka_co2, Ka_ac, Ka_pr, Ka_bu, Ka_va = Kas + # Kw, Ka_nh, Ka_h2po4, Ka_co2, Ka_ac, Ka_pr, Ka_bu, Ka_va = Kas Kw = Kas[0] oh_ion = Kw/h_ion nh3, h2po4, hco3, ac, pro, bu, va = Kas[1:] * weak_acids_tot[4:] / (Kas[1:] + h_ion) @@ -558,12 +558,12 @@ class ADM1_p_extension(CompiledProcesses): pKa_base : iterable[float], optional pKa (equilibrium coefficient) values of acid-base pairs at the base temperature, unitless, following the order of `ADM1._acid_base_pairs`. - The default is [14, 9.25, 2.12, 6.35, 4.76, 4.88, 4.82, 4.86]. + The default is [14, 9.25, 7.20, 6.35, 4.76, 4.88, 4.82, 4.86]. Ka_dH : iterable[float], optional Heat of reaction of each acid-base pair at base temperature [J/mol], following the order of `ADM1._acid_base_pairs`. The default is [55900, 51965, 5000, 7646, 0, 0, 0, 0]. - ('H+', 'OH-'), ('NH4+', 'NH3'), ('H3PO4', 'H2PO4 2-'), ('CO2', 'HCO3-'), + ('H+', 'OH-'), ('NH4+', 'NH3'), ('H2PO4 2-', 'HPO4 2-'), ('CO2', 'HCO3-'), ('HAc', 'Ac-'), ('HPr', 'Pr-'), ('HBu', 'Bu-'), ('HVa', 'Va-') kLa : float, optional Liquid-gas mass transfer coefficient [d^(-1)]. The default is 200. @@ -610,7 +610,7 @@ class ADM1_p_extension(CompiledProcesses): 'KS_IN', 'KS_IP', 'KI_nh3', 'KIs_h2', 'Ka_base', 'Ka_dH', 'K_H_base', 'K_H_dH', 'kLa', 'T_base', 'components', 'root') - _acid_base_pairs = (('H+', 'OH-'), ('NH4+', 'NH3'), ('H3PO4', 'H2PO4-'), + _acid_base_pairs = (('H+', 'OH-'), ('NH4+', 'NH3'), ('H2PO4-', 'HPO4 -2'), ('CO2', 'HCO3-'), ('HAc', 'Ac-'), ('HPr', 'Pr-'), ('HBu', 'Bu-'), ('HVa', 'Va-')) @@ -630,7 +630,7 @@ def __new__(cls, components=None, path=None, N_xc=2.686e-3, N_I=4.286e-3, N_aa=7 q_PHA=3, b_PAO=0.2, b_PP=0.2, b_PHA=0.2, KI_h2_fa=5e-6, KI_h2_c4=1e-5, KI_h2_pro=3.5e-6, KI_nh3=1.8e-3, KS_IN=1e-4, KS_IP=2e-5, pH_limits_aa=(4,5.5), pH_limits_ac=(6,7), pH_limits_h2=(5,6), - T_base=298.15, pKa_base=[14, 9.25, 2.12, 6.35, 4.76, 4.88, 4.82, 4.86], + T_base=298.15, pKa_base=[14, 9.25, 7.20, 6.35, 4.76, 4.88, 4.82, 4.86], Ka_dH=[55900, 51965, 5000, 7646, 0, 0, 0, 0], kLa=200, K_H_base=[7.8e-4, 1.4e-3, 3.5e-2], K_H_dH=[-4180, -14240, -19410], From 30eb0cb9cae62bee1a8b78b4bc3ee464703ce59b Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Wed, 27 Mar 2024 13:23:06 -0500 Subject: [PATCH 296/424] Removed unnecessary edits --- qsdsan/sanunits/_junction.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/qsdsan/sanunits/_junction.py b/qsdsan/sanunits/_junction.py index bc8d2cfd..ad8cfcb7 100644 --- a/qsdsan/sanunits/_junction.py +++ b/qsdsan/sanunits/_junction.py @@ -1758,13 +1758,7 @@ def balance_cod_tkn(self, adm_vals, asm_vals): asm_i_P = cmps_asm.i_P adm_i_P = cmps_adm.i_P adm_cod = sum(adm_vals*adm_i_COD) - sum(adm_vals[gas_idx]) - - # to ensure correct mechanism to check TN balance in case of mADM1 interfaces - X_PAO_asm_idx = cmps_asm.indices(('X_PAO',)) - X_PAO_adm_idx = cmps_adm.indices(('X_PAO',)) - adm_tkn = sum(adm_vals*adm_i_N) - adm_vals[X_PAO_adm_idx]*adm_i_N[X_PAO_adm_idx] - adm_vals[X_PAO_adm_idx]*asm_i_N[X_PAO_asm_idx] - - # adm_tkn = sum(adm_vals*adm_i_N) + adm_tkn = sum(adm_vals*adm_i_N) adm_tp = sum(adm_vals*adm_i_P) cod_bl, cod_err, cod_tol, asm_cod = self.isbalanced(adm_cod, asm_vals, asm_i_COD) @@ -2342,7 +2336,6 @@ def balance_cod_tkn_tp(self, asm_vals, adm_vals): if tkn_bl and tp_bl: if cod_bl: - print('Sab changa si') return adm_vals else: if cod_err > 0: dcod = -(cod_err - cod_tol)/adm_cod From f5ac2adc5624cbbdf92f3a24c72ff4a7daf2117a Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Thu, 28 Mar 2024 11:35:03 -0500 Subject: [PATCH 297/424] Updated constant: mADM1 Replaced placeholder value for Delta H for dissociation of H2PO4- with correct enthalpy of formation. --- qsdsan/processes/_adm1_p_extension.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qsdsan/processes/_adm1_p_extension.py b/qsdsan/processes/_adm1_p_extension.py index cac391db..06323394 100644 --- a/qsdsan/processes/_adm1_p_extension.py +++ b/qsdsan/processes/_adm1_p_extension.py @@ -562,7 +562,7 @@ class ADM1_p_extension(CompiledProcesses): Ka_dH : iterable[float], optional Heat of reaction of each acid-base pair at base temperature [J/mol], following the order of `ADM1._acid_base_pairs`. The default is - [55900, 51965, 5000, 7646, 0, 0, 0, 0]. + [55900, 51965, 3600, 7646, 0, 0, 0, 0]. ('H+', 'OH-'), ('NH4+', 'NH3'), ('H2PO4 2-', 'HPO4 2-'), ('CO2', 'HCO3-'), ('HAc', 'Ac-'), ('HPr', 'Pr-'), ('HBu', 'Bu-'), ('HVa', 'Va-') kLa : float, optional @@ -631,7 +631,7 @@ def __new__(cls, components=None, path=None, N_xc=2.686e-3, N_I=4.286e-3, N_aa=7 KI_h2_fa=5e-6, KI_h2_c4=1e-5, KI_h2_pro=3.5e-6, KI_nh3=1.8e-3, KS_IN=1e-4, KS_IP=2e-5, pH_limits_aa=(4,5.5), pH_limits_ac=(6,7), pH_limits_h2=(5,6), T_base=298.15, pKa_base=[14, 9.25, 7.20, 6.35, 4.76, 4.88, 4.82, 4.86], - Ka_dH=[55900, 51965, 5000, 7646, 0, 0, 0, 0], + Ka_dH=[55900, 51965, 3600, 7646, 0, 0, 0, 0], kLa=200, K_H_base=[7.8e-4, 1.4e-3, 3.5e-2], K_H_dH=[-4180, -14240, -19410], **kwargs): From da5a12ec259df843a8f33483524348e322827ce2 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Fri, 29 Mar 2024 00:39:13 -0500 Subject: [PATCH 298/424] Modified Ks Used correct position for substrates --- qsdsan/processes/_adm1_p_extension.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/qsdsan/processes/_adm1_p_extension.py b/qsdsan/processes/_adm1_p_extension.py index 06323394..f36899ce 100644 --- a/qsdsan/processes/_adm1_p_extension.py +++ b/qsdsan/processes/_adm1_p_extension.py @@ -266,6 +266,7 @@ def Hill_inhibit(H_ion, ul, ll): def rhos_adm1_p_extension(state_arr, params): ks = params['rate_constants'] Ks = params['half_sat_coeffs'] + cmps = params['components'] # n = len(cmps) pH_ULs = params['pH_ULs'] @@ -321,22 +322,21 @@ def rhos_adm1_p_extension(state_arr, params): KH = KHb * T_correction_factor(T_base, T_op, KH_dH) / unit_conversion[7:10] rhos[:-3] = ks * Cs - rhos[3:11] *= substr_inhibit(substrates, Ks) + rhos[3:11] *= substr_inhibit(substrates, Ks[0:8]) if S_va > 0: rhos[6] *= 1/(1+S_bu/S_va) if S_bu > 0: rhos[7] *= 1/(1+S_va/S_bu) # substrates_ids = cmps.indices(['S_va', 'S_bu', 'S_pro', 'S_ac']) # substrates_modified = state_arr[substrates_ids] substrates_modified = state_arr[3:7] - # Ka = K_a - Ka = Ks[-2] - rhos[18:22] *= substr_inhibit(substrates_modified, Ka) - # PP_PAO = X_PP/X_PAO + + K_a = Ks[-2] + rhos[18:22] *= substr_inhibit(substrates_modified, K_a) PP_PAO = state_arr[25]/state_arr[26] - # Kpp = K_pp - Kpp = Ks[-1] - rhos[18:22] *= substr_inhibit(PP_PAO, Kpp) + + K_pp = Ks[-1] + rhos[18:22] *= substr_inhibit(PP_PAO, K_pp) # Multiplication by {Sva, Sbu, Spro, Sac}/(Sva + Sbu + Spro + Sac) transformation_array = state_arr[3:7]/sum(state_arr[3:7]) @@ -606,10 +606,12 @@ class ADM1_p_extension(CompiledProcesses): 'f_ac_bu', 'f_h2_bu', 'f_ac_pro', 'f_h2_pro', 'f_ac_PHA', 'f_bu_PHA', 'f_pro_PHA', 'f_va_PHA', 'Y_su', 'Y_aa', 'Y_fa', 'Y_c4', 'Y_pro', 'Y_ac', 'Y_h2', 'Y_po4') + _kinetic_params = ('rate_constants', 'half_sat_coeffs', 'pH_ULs', 'pH_LLs', 'KS_IN', 'KS_IP', 'KI_nh3', 'KIs_h2', 'Ka_base', 'Ka_dH', 'K_H_base', 'K_H_dH', 'kLa', 'T_base', 'components', 'root') + _acid_base_pairs = (('H+', 'OH-'), ('NH4+', 'NH3'), ('H2PO4-', 'HPO4 -2'), ('CO2', 'HCO3-'), ('HAc', 'Ac-'), ('HPr', 'Pr-'), ('HBu', 'Bu-'), ('HVa', 'Va-')) @@ -724,6 +726,7 @@ def __new__(cls, components=None, path=None, N_xc=2.686e-3, N_I=4.286e-3, N_aa=7 q_PHA, q_PHA, q_PHA, q_PHA, b_PAO, b_PP, b_PHA)) Ks = np.array((K_su, K_aa, K_fa, K_c4, K_c4, K_pro, K_ac, K_h2, K_a, K_pp)) + KIs_h2 = np.array((KI_h2_fa, KI_h2_c4, KI_h2_c4, KI_h2_pro)) K_H_base = np.array(K_H_base) K_H_dH = np.array(K_H_dH) @@ -823,4 +826,4 @@ def check_stoichiometric_parameters(self): f_tot = sum([stoichio[k] for k in self._stoichio_params[:-7] \ if k.endswith(s)]) if f_tot != 1: - raise ValueError(f"the sum of 'f_()_{s}' values must equal 1") + raise ValueError(f"the sum of 'f_()_{s}' values must equal 1") \ No newline at end of file From 9ee94cff9c969752e5babe162180e0af9bc3fba9 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Wed, 3 Apr 2024 08:35:53 -0500 Subject: [PATCH 299/424] Minor edit Not needed --- qsdsan/sanunits/_junction.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qsdsan/sanunits/_junction.py b/qsdsan/sanunits/_junction.py index ad8cfcb7..beb44e94 100644 --- a/qsdsan/sanunits/_junction.py +++ b/qsdsan/sanunits/_junction.py @@ -2616,7 +2616,7 @@ def asm2d2madm1(asm_vals): # This needs to be followed by a corresponding loss in particulate organic N X_ND_asm1 -= X_pr * X_pr_i_N - # For P balance (CONFIRM LATER 05/16) + # For P balance # This needs to be followed by a corresponding loss in particulate organic P X_S_P -= X_pr * X_pr_i_P @@ -2631,7 +2631,7 @@ def asm2d2madm1(asm_vals): # All particulate organic N would thus be consumed in amino acid formation X_ND_asm1 = 0 - # For P balance (CONFIRM LATER 05/16) + # For P balance # This needs to be followed by a corresponding loss in particulate organic P X_S_P -= X_pr * X_pr_i_P From 8addf4195c35fd4b0e43155f463c8000542948e8 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Tue, 9 Apr 2024 10:36:20 -0500 Subject: [PATCH 300/424] Changed Step 2 (ASM2d-mADM1) I noticed that according to Flores Alsina et al. 2016 only SF is translated to amino acids and sugars, while acetate is directly mapped. --- qsdsan/sanunits/_junction.py | 36 +++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/qsdsan/sanunits/_junction.py b/qsdsan/sanunits/_junction.py index beb44e94..29e0ffc8 100644 --- a/qsdsan/sanunits/_junction.py +++ b/qsdsan/sanunits/_junction.py @@ -2424,6 +2424,7 @@ def _compile_reactions(self): X_AUT_i_N = cmps_asm.X_AUT.i_N S_F_i_N = cmps_asm.S_F.i_N X_S_i_N = cmps_asm.X_S.i_N + S_A_i_N = cmps_asm.S_A.i_N # Due to issue with mapping of X_I across ASM2d and ADM1, making this user dependent is important if self.asm_X_I_i_N == None: @@ -2439,6 +2440,7 @@ def _compile_reactions(self): S_F_i_P = cmps_asm.S_F.i_P X_S_i_P = cmps_asm.X_S.i_P asm_X_I_i_P = cmps_asm.X_I.i_P + S_A_i_P = cmps_asm.S_A.I_P if cmps_asm.S_A.i_N > 0: warn(f'S_A in ASM has positive nitrogen content: {cmps_asm.S_S.i_N} gN/gCOD. ' @@ -2460,12 +2462,14 @@ def _compile_reactions(self): cmps_adm = outs.components # For nitrogen balance + S_ac_i_N = cmps_adm.S_ac.i_N S_aa_i_N = cmps_adm.S_aa.i_N X_pr_i_N = cmps_adm.X_pr.i_N adm_S_I_i_N = cmps_adm.S_I.i_N adm_X_I_i_N = cmps_adm.X_I.i_N # For phosphorous balance + S_ac_i_P = cmps_adm.S_ac.i_P X_pr_i_P = cmps_adm.X_pr.i_P adm_S_I_i_P = cmps_adm.S_I.i_P adm_X_I_i_P = cmps_adm.X_I.i_P @@ -2573,29 +2577,32 @@ def asm2d2madm1(asm_vals): # Step 2: convert any readily biodegradable # COD and TKN into amino acids and sugars - # S_S (in asm1) equals to the sum of S_F and S_A (pg. 82 IWA ASM models handbook) - S_S_asm1 = S_F + S_A + # Directly map acetate + if S_ac_i_N == S_A_i_N and S_ac_i_P == S_A_i_P: + S_ac = S_A + else: + raise RuntimeError('N and P content should be the same in S_A and S_ac for direct translation') # First we calculate the amount of amino acid required in ADM1 # if all available soluble organic N can be mapped to amino acid req_scod = S_ND_asm1 / S_aa_i_N - # if available S_S is not enough to fulfill that amino acid requirement - if S_S_asm1 < req_scod: - # then all available S_S is mapped to amino acids - S_aa = S_S_asm1 + # if available S_F is not enough to fulfill that amino acid requirement + if S_F < req_scod: + # then all available S_F is mapped to amino acids + S_aa = S_F # and no S_S would be available for conversion to sugars S_su = 0 # This needs to be followed by a corresponding loss in soluble organic N S_ND_asm1 -= S_aa * S_aa_i_N - # if available S_S is more than enough to fulfill that amino acid requirement + # if available S_F is more than enough to fulfill that amino acid requirement else: # All soluble organic N will be mapped to amino acid S_aa = req_scod # The line above implies that a certain portion of S_S would also be consumed to form amino acid # The S_S which is left would form sugar - # In simpler terms; S_S = S_S - S_aa; S_su = S_S - S_su = S_S_asm1 - S_aa + # In simpler terms; S_F = S_F - S_aa; S_su = S_F + S_su = S_F - S_aa # All soluble organic N would thus be consumed in amino acid formation S_ND_asm1 = 0 @@ -2759,6 +2766,8 @@ def asm2d2madm1(asm_vals): raise RuntimeError('Not enough N in X_I, X_S to fully ' 'convert X_I in ASM2d into X_I in ADM1.') + # print(f'S_NH4 = {S_NH4}\n') + # 5(b) # Then determine the amount of soluble inert N/P that could be produced @@ -2852,17 +2861,18 @@ def asm2d2madm1(asm_vals): S_ND_asm1 = X_ND_asm1 = supply_inert_n_asm2d = 0 if S_PO4 < 0: - raise RuntimeError('Not enough P in S_F_P, X_S_P and S_PO4 to fully ' + raise RuntimeError(f'S_PO4 = {S_PO4}\n''Not enough P in S_F_P, X_S_P and S_PO4 to fully ' 'convert S_I in ASM2d into S_I in ADM1. Consider ' 'increasing the value of P content in S_I (ASM2d)') if S_NH4 < 0: - raise RuntimeError('Not enough N in S_I, S_ND_asm1, X_ND_asm1, and S_NH4 to fully ' - 'convert S_I in ASM2d into S_I in ADM1. Consider ' - 'increasing the value of N content in S_I (ASM2d)') + raise RuntimeError(f'S_NH4 = {S_NH4}\n''Not enough N in S_I, S_ND_asm1, X_ND_asm1, and S_NH4 to fully ' + 'convert S_I in ASM2d into S_I in ADM1. Consider ' + 'increasing the value of N content in S_I (ASM2d)') # Step 6: Step map any remaining TKN/P S_IN = S_ND_asm1 + X_ND_asm1 + S_NH4 + supply_inert_n_asm2d + # print(f'S_IN = {S_IN}') S_IP = S_F_P + X_S_P + S_PO4 # Step 8: check COD and TKN balance From 7f9052186e8f7c50c3b96cd245e2d268a4129aeb Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Tue, 9 Apr 2024 11:13:44 -0500 Subject: [PATCH 301/424] Step 5: mapping of X_I According to Flores Alsina et al. 2016, X_I should be directly mapped by ensuring N and P content is same across ASM-ADM models --- qsdsan/sanunits/_junction.py | 91 +++++++++++++++++++----------------- 1 file changed, 48 insertions(+), 43 deletions(-) diff --git a/qsdsan/sanunits/_junction.py b/qsdsan/sanunits/_junction.py index 29e0ffc8..61c76aa3 100644 --- a/qsdsan/sanunits/_junction.py +++ b/qsdsan/sanunits/_junction.py @@ -2440,7 +2440,7 @@ def _compile_reactions(self): S_F_i_P = cmps_asm.S_F.i_P X_S_i_P = cmps_asm.X_S.i_P asm_X_I_i_P = cmps_asm.X_I.i_P - S_A_i_P = cmps_asm.S_A.I_P + S_A_i_P = cmps_asm.S_A.i_P if cmps_asm.S_A.i_N > 0: warn(f'S_A in ASM has positive nitrogen content: {cmps_asm.S_S.i_N} gN/gCOD. ' @@ -2721,50 +2721,55 @@ def asm2d2madm1(asm_vals): # Step 5: map particulate inerts - # 5 (a) - # First determine the amount of particulate inert N/P available from ASM2d - xi_nsp_asm2d = X_I * asm_X_I_i_N - xi_psp_asm2d = X_I * asm_X_I_i_P + # # 5 (a) + # # First determine the amount of particulate inert N/P available from ASM2d + # xi_nsp_asm2d = X_I * asm_X_I_i_N + # xi_psp_asm2d = X_I * asm_X_I_i_P - # Then determine the amount of particulate inert N/P that could be produced - # in ADM1 given the ASM1 X_I - xi_ndm = X_I * adm_X_I_i_N - xi_pdm = X_I * adm_X_I_i_P + # # Then determine the amount of particulate inert N/P that could be produced + # # in ADM1 given the ASM1 X_I + # xi_ndm = X_I * adm_X_I_i_N + # xi_pdm = X_I * adm_X_I_i_P - # if particulate inert N available in ASM1 is greater than ADM1 demand - if xi_nsp_asm2d + X_ND_asm1 >= xi_ndm: - deficit = xi_ndm - xi_nsp_asm2d - # COD balance - X_I += (X_H+X_AUT) * (1-frac_deg) - # N balance - X_ND_asm1 -= deficit - # P balance - if xi_psp_asm2d + X_S_P >= xi_pdm: - deficit = xi_pdm - xi_psp_asm2d - X_S_P -= deficit - elif isclose(xi_psp_asm2d+X_S_P, xi_pdm, rel_tol=rtol, abs_tol=atol): - X_S_P = 0 - else: - raise RuntimeError('Not enough P in X_I, X_S to fully ' - 'convert X_I in ASM2d into X_I in ADM1.') - elif isclose(xi_nsp_asm2d+X_ND_asm1, xi_ndm, rel_tol=rtol, abs_tol=atol): - # COD balance - X_I += (X_H+X_AUT) * (1-frac_deg) - # N balance - X_ND_asm1 = 0 - # P balance - if xi_psp_asm2d + X_S_P >= xi_pdm: - deficit = xi_pdm - xi_psp_asm2d - X_S_P -= deficit - elif isclose(xi_psp_asm2d+X_S_P, xi_pdm, rel_tol=rtol, abs_tol=atol): - X_S_P = 0 - else: - raise RuntimeError('Not enough P in X_I, X_S to fully ' - 'convert X_I in ASM2d into X_I in ADM1.') + # # if particulate inert N available in ASM2d is greater than ADM1 demand + # if xi_nsp_asm2d + X_ND_asm1 >= xi_ndm: + # deficit = xi_ndm - xi_nsp_asm2d + # # COD balance + # X_I += (X_H+X_AUT) * (1-frac_deg) + # # N balance + # X_ND_asm1 -= deficit + # # P balance + # if xi_psp_asm2d + X_S_P >= xi_pdm: + # deficit = xi_pdm - xi_psp_asm2d + # X_S_P -= deficit + # elif isclose(xi_psp_asm2d+X_S_P, xi_pdm, rel_tol=rtol, abs_tol=atol): + # X_S_P = 0 + # else: + # raise RuntimeError('Not enough P in X_I, X_S to fully ' + # 'convert X_I in ASM2d into X_I in ADM1.') + # elif isclose(xi_nsp_asm2d+X_ND_asm1, xi_ndm, rel_tol=rtol, abs_tol=atol): + # # COD balance + # X_I += (X_H+X_AUT) * (1-frac_deg) + # # N balance + # X_ND_asm1 = 0 + # # P balance + # if xi_psp_asm2d + X_S_P >= xi_pdm: + # deficit = xi_pdm - xi_psp_asm2d + # X_S_P -= deficit + # elif isclose(xi_psp_asm2d+X_S_P, xi_pdm, rel_tol=rtol, abs_tol=atol): + # X_S_P = 0 + # else: + # raise RuntimeError('Not enough P in X_I, X_S to fully ' + # 'convert X_I in ASM2d into X_I in ADM1.') + # else: + # # Since the N balance cannot hold, the P balance is not futher checked + # raise RuntimeError('Not enough N in X_I, X_S to fully ' + # 'convert X_I in ASM2d into X_I in ADM1.') + + if asm_X_I_i_N == adm_X_I_i_N and asm_X_I_i_P == adm_X_I_i_P: + pass # we are directly mapping X_I else: - # Since the N balance cannot hold, the P balance is not futher checked - raise RuntimeError('Not enough N in X_I, X_S to fully ' - 'convert X_I in ASM2d into X_I in ADM1.') + raise RuntimeError('N and P content should be the same in X_I in ASM2d and mADM1 for direct translation') # print(f'S_NH4 = {S_NH4}\n') @@ -2893,7 +2898,7 @@ def asm2d2madm1(asm_vals): adm_vals = np.array([ S_su, S_aa, - 0, 0, 0, 0, 0, # S_fa, S_va, S_bu, S_pro, S_ac, + 0, 0, 0, 0, S_ac, # S_fa, S_va, S_bu, S_pro, 0, 0, # S_h2, S_ch4, S_IC, S_IN, S_IP, S_I, X_ch, X_pr, X_li, From fcca157d0ff70bf72d23dd343d50252f688a7d4d Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Tue, 9 Apr 2024 11:15:11 -0500 Subject: [PATCH 302/424] Revert "Step 5: mapping of X_I" This reverts commit 7f9052186e8f7c50c3b96cd245e2d268a4129aeb. --- qsdsan/sanunits/_junction.py | 91 +++++++++++++++++------------------- 1 file changed, 43 insertions(+), 48 deletions(-) diff --git a/qsdsan/sanunits/_junction.py b/qsdsan/sanunits/_junction.py index 61c76aa3..29e0ffc8 100644 --- a/qsdsan/sanunits/_junction.py +++ b/qsdsan/sanunits/_junction.py @@ -2440,7 +2440,7 @@ def _compile_reactions(self): S_F_i_P = cmps_asm.S_F.i_P X_S_i_P = cmps_asm.X_S.i_P asm_X_I_i_P = cmps_asm.X_I.i_P - S_A_i_P = cmps_asm.S_A.i_P + S_A_i_P = cmps_asm.S_A.I_P if cmps_asm.S_A.i_N > 0: warn(f'S_A in ASM has positive nitrogen content: {cmps_asm.S_S.i_N} gN/gCOD. ' @@ -2721,55 +2721,50 @@ def asm2d2madm1(asm_vals): # Step 5: map particulate inerts - # # 5 (a) - # # First determine the amount of particulate inert N/P available from ASM2d - # xi_nsp_asm2d = X_I * asm_X_I_i_N - # xi_psp_asm2d = X_I * asm_X_I_i_P + # 5 (a) + # First determine the amount of particulate inert N/P available from ASM2d + xi_nsp_asm2d = X_I * asm_X_I_i_N + xi_psp_asm2d = X_I * asm_X_I_i_P - # # Then determine the amount of particulate inert N/P that could be produced - # # in ADM1 given the ASM1 X_I - # xi_ndm = X_I * adm_X_I_i_N - # xi_pdm = X_I * adm_X_I_i_P + # Then determine the amount of particulate inert N/P that could be produced + # in ADM1 given the ASM1 X_I + xi_ndm = X_I * adm_X_I_i_N + xi_pdm = X_I * adm_X_I_i_P - # # if particulate inert N available in ASM2d is greater than ADM1 demand - # if xi_nsp_asm2d + X_ND_asm1 >= xi_ndm: - # deficit = xi_ndm - xi_nsp_asm2d - # # COD balance - # X_I += (X_H+X_AUT) * (1-frac_deg) - # # N balance - # X_ND_asm1 -= deficit - # # P balance - # if xi_psp_asm2d + X_S_P >= xi_pdm: - # deficit = xi_pdm - xi_psp_asm2d - # X_S_P -= deficit - # elif isclose(xi_psp_asm2d+X_S_P, xi_pdm, rel_tol=rtol, abs_tol=atol): - # X_S_P = 0 - # else: - # raise RuntimeError('Not enough P in X_I, X_S to fully ' - # 'convert X_I in ASM2d into X_I in ADM1.') - # elif isclose(xi_nsp_asm2d+X_ND_asm1, xi_ndm, rel_tol=rtol, abs_tol=atol): - # # COD balance - # X_I += (X_H+X_AUT) * (1-frac_deg) - # # N balance - # X_ND_asm1 = 0 - # # P balance - # if xi_psp_asm2d + X_S_P >= xi_pdm: - # deficit = xi_pdm - xi_psp_asm2d - # X_S_P -= deficit - # elif isclose(xi_psp_asm2d+X_S_P, xi_pdm, rel_tol=rtol, abs_tol=atol): - # X_S_P = 0 - # else: - # raise RuntimeError('Not enough P in X_I, X_S to fully ' - # 'convert X_I in ASM2d into X_I in ADM1.') - # else: - # # Since the N balance cannot hold, the P balance is not futher checked - # raise RuntimeError('Not enough N in X_I, X_S to fully ' - # 'convert X_I in ASM2d into X_I in ADM1.') - - if asm_X_I_i_N == adm_X_I_i_N and asm_X_I_i_P == adm_X_I_i_P: - pass # we are directly mapping X_I + # if particulate inert N available in ASM1 is greater than ADM1 demand + if xi_nsp_asm2d + X_ND_asm1 >= xi_ndm: + deficit = xi_ndm - xi_nsp_asm2d + # COD balance + X_I += (X_H+X_AUT) * (1-frac_deg) + # N balance + X_ND_asm1 -= deficit + # P balance + if xi_psp_asm2d + X_S_P >= xi_pdm: + deficit = xi_pdm - xi_psp_asm2d + X_S_P -= deficit + elif isclose(xi_psp_asm2d+X_S_P, xi_pdm, rel_tol=rtol, abs_tol=atol): + X_S_P = 0 + else: + raise RuntimeError('Not enough P in X_I, X_S to fully ' + 'convert X_I in ASM2d into X_I in ADM1.') + elif isclose(xi_nsp_asm2d+X_ND_asm1, xi_ndm, rel_tol=rtol, abs_tol=atol): + # COD balance + X_I += (X_H+X_AUT) * (1-frac_deg) + # N balance + X_ND_asm1 = 0 + # P balance + if xi_psp_asm2d + X_S_P >= xi_pdm: + deficit = xi_pdm - xi_psp_asm2d + X_S_P -= deficit + elif isclose(xi_psp_asm2d+X_S_P, xi_pdm, rel_tol=rtol, abs_tol=atol): + X_S_P = 0 + else: + raise RuntimeError('Not enough P in X_I, X_S to fully ' + 'convert X_I in ASM2d into X_I in ADM1.') else: - raise RuntimeError('N and P content should be the same in X_I in ASM2d and mADM1 for direct translation') + # Since the N balance cannot hold, the P balance is not futher checked + raise RuntimeError('Not enough N in X_I, X_S to fully ' + 'convert X_I in ASM2d into X_I in ADM1.') # print(f'S_NH4 = {S_NH4}\n') @@ -2898,7 +2893,7 @@ def asm2d2madm1(asm_vals): adm_vals = np.array([ S_su, S_aa, - 0, 0, 0, 0, S_ac, # S_fa, S_va, S_bu, S_pro, + 0, 0, 0, 0, 0, # S_fa, S_va, S_bu, S_pro, S_ac, 0, 0, # S_h2, S_ch4, S_IC, S_IN, S_IP, S_I, X_ch, X_pr, X_li, From e74b1c06b1a75922e44de093dfb205fbbadd91a4 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Tue, 9 Apr 2024 11:33:48 -0500 Subject: [PATCH 303/424] Mapping XI According to Flores Alsina et al. 2016, X_I should be directly mapped by ensuring N and P content is same across ASM-ADM models. --- qsdsan/sanunits/_junction.py | 93 +++++++++++++++++++----------------- 1 file changed, 50 insertions(+), 43 deletions(-) diff --git a/qsdsan/sanunits/_junction.py b/qsdsan/sanunits/_junction.py index 29e0ffc8..83d1cd87 100644 --- a/qsdsan/sanunits/_junction.py +++ b/qsdsan/sanunits/_junction.py @@ -2722,50 +2722,57 @@ def asm2d2madm1(asm_vals): # Step 5: map particulate inerts # 5 (a) - # First determine the amount of particulate inert N/P available from ASM2d - xi_nsp_asm2d = X_I * asm_X_I_i_N - xi_psp_asm2d = X_I * asm_X_I_i_P - - # Then determine the amount of particulate inert N/P that could be produced - # in ADM1 given the ASM1 X_I - xi_ndm = X_I * adm_X_I_i_N - xi_pdm = X_I * adm_X_I_i_P + # # First determine the amount of particulate inert N/P available from ASM2d + # xi_nsp_asm2d = X_I * asm_X_I_i_N + # xi_psp_asm2d = X_I * asm_X_I_i_P + + # # Then determine the amount of particulate inert N/P that could be produced + # # in ADM1 given the ASM1 X_I + # xi_ndm = X_I * adm_X_I_i_N + # xi_pdm = X_I * adm_X_I_i_P - # if particulate inert N available in ASM1 is greater than ADM1 demand - if xi_nsp_asm2d + X_ND_asm1 >= xi_ndm: - deficit = xi_ndm - xi_nsp_asm2d - # COD balance - X_I += (X_H+X_AUT) * (1-frac_deg) - # N balance - X_ND_asm1 -= deficit - # P balance - if xi_psp_asm2d + X_S_P >= xi_pdm: - deficit = xi_pdm - xi_psp_asm2d - X_S_P -= deficit - elif isclose(xi_psp_asm2d+X_S_P, xi_pdm, rel_tol=rtol, abs_tol=atol): - X_S_P = 0 - else: - raise RuntimeError('Not enough P in X_I, X_S to fully ' - 'convert X_I in ASM2d into X_I in ADM1.') - elif isclose(xi_nsp_asm2d+X_ND_asm1, xi_ndm, rel_tol=rtol, abs_tol=atol): - # COD balance - X_I += (X_H+X_AUT) * (1-frac_deg) - # N balance - X_ND_asm1 = 0 - # P balance - if xi_psp_asm2d + X_S_P >= xi_pdm: - deficit = xi_pdm - xi_psp_asm2d - X_S_P -= deficit - elif isclose(xi_psp_asm2d+X_S_P, xi_pdm, rel_tol=rtol, abs_tol=atol): - X_S_P = 0 - else: - raise RuntimeError('Not enough P in X_I, X_S to fully ' - 'convert X_I in ASM2d into X_I in ADM1.') + # # if particulate inert N available in ASM1 is greater than ADM1 demand + # if xi_nsp_asm2d + X_ND_asm1 >= xi_ndm: + # deficit = xi_ndm - xi_nsp_asm2d + # # COD balance + # X_I += (X_H+X_AUT) * (1-frac_deg) + # # N balance + # X_ND_asm1 -= deficit + # # P balance + # if xi_psp_asm2d + X_S_P >= xi_pdm: + # deficit = xi_pdm - xi_psp_asm2d + # X_S_P -= deficit + # elif isclose(xi_psp_asm2d+X_S_P, xi_pdm, rel_tol=rtol, abs_tol=atol): + # X_S_P = 0 + # else: + # raise RuntimeError('Not enough P in X_I, X_S to fully ' + # 'convert X_I in ASM2d into X_I in ADM1.') + # elif isclose(xi_nsp_asm2d+X_ND_asm1, xi_ndm, rel_tol=rtol, abs_tol=atol): + # # COD balance + # X_I += (X_H+X_AUT) * (1-frac_deg) + # # N balance + # X_ND_asm1 = 0 + # # P balance + # if xi_psp_asm2d + X_S_P >= xi_pdm: + # deficit = xi_pdm - xi_psp_asm2d + # X_S_P -= deficit + # elif isclose(xi_psp_asm2d+X_S_P, xi_pdm, rel_tol=rtol, abs_tol=atol): + # X_S_P = 0 + # else: + # raise RuntimeError('Not enough P in X_I, X_S to fully ' + # 'convert X_I in ASM2d into X_I in ADM1.') + # else: + # # Since the N balance cannot hold, the P balance is not futher checked + # raise RuntimeError('Not enough N in X_I, X_S to fully ' + # 'convert X_I in ASM2d into X_I in ADM1.') + + if asm_X_I_i_N == adm_X_I_i_N and asm_X_I_i_P == adm_X_I_i_P: + pass else: - # Since the N balance cannot hold, the P balance is not futher checked - raise RuntimeError('Not enough N in X_I, X_S to fully ' - 'convert X_I in ASM2d into X_I in ADM1.') - + raise RuntimeError('N and P content in X_I should be the same for direct translation') + # COD balance + X_I += (X_H+X_AUT) * (1-frac_deg) + # print(f'S_NH4 = {S_NH4}\n') # 5(b) @@ -2893,7 +2900,7 @@ def asm2d2madm1(asm_vals): adm_vals = np.array([ S_su, S_aa, - 0, 0, 0, 0, 0, # S_fa, S_va, S_bu, S_pro, S_ac, + 0, 0, 0, 0, S_ac, # S_fa, S_va, S_bu, S_pro, 0, 0, # S_h2, S_ch4, S_IC, S_IN, S_IP, S_I, X_ch, X_pr, X_li, From 326fb0dfc82a3898bbc598ade9c7a6a0a6ff40b5 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Tue, 9 Apr 2024 15:30:57 -0500 Subject: [PATCH 304/424] Mapping SI According to Flores Alsina et al. 2016 soluble inerts are also directly mapped. --- qsdsan/sanunits/_junction.py | 277 +++++++++++++++++++++-------------- 1 file changed, 164 insertions(+), 113 deletions(-) diff --git a/qsdsan/sanunits/_junction.py b/qsdsan/sanunits/_junction.py index 83d1cd87..3ca5bbfa 100644 --- a/qsdsan/sanunits/_junction.py +++ b/qsdsan/sanunits/_junction.py @@ -2303,12 +2303,16 @@ class ASM2dtomADM1(mADMjunction): bio_to_li = 0.4 frac_deg = 0.68 - # Since we are matching PAOs directly from ASM2d to mADM1, it is important + # Since we are matching PAOs, X_I, S_I, directly from ASM2d to mADM1, it is important # for PAOs to have identical N/P content across models adm_X_PAO_i_N = 0.07 adm_X_PAO_i_P = 0.02 - asm_X_I_i_N = 0.06 + + asm_X_I_i_N = 0.0600327162 + asm_X_I_i_P = 0.01 + asm_S_I_i_N = 0.06 + asm_S_I_i_P = 0.01 def isbalanced(self, lhs, rhs_vals, rhs_i): rhs = sum(rhs_vals*rhs_i) @@ -2415,6 +2419,7 @@ def _compile_reactions(self): atol = self.atol cmps_asm = ins.components + cmps_adm = outs.components # For COD balance S_NO3_i_COD = cmps_asm.S_NO3.i_COD @@ -2431,16 +2436,28 @@ def _compile_reactions(self): asm_X_I_i_N = cmps_asm.X_I.i_N else: asm_X_I_i_N = self.asm_X_I_i_N + + if self.asm_X_I_i_P == None: + asm_X_I_i_P = cmps_asm.X_I.i_P + else: + asm_X_I_i_P = self.asm_X_I_i_P - asm_S_I_i_N = cmps_asm.S_I.i_N + if self.asm_S_I_i_N == None: + asm_S_I_i_N = cmps_asm.S_I.i_N + else: + asm_S_I_i_N = self.asm_X_I_i_N + + if self.asm_S_I_i_P == None: + asm_S_I_i_P = cmps_asm.S_I.i_P + else: + asm_S_I_i_P = self.asm_S_I_i_P # For P balance X_H_i_P = cmps_asm.X_H.i_P X_AUT_i_P = cmps_asm.X_AUT.i_P S_F_i_P = cmps_asm.S_F.i_P X_S_i_P = cmps_asm.X_S.i_P - asm_X_I_i_P = cmps_asm.X_I.i_P - S_A_i_P = cmps_asm.S_A.I_P + S_A_i_P = cmps_asm.S_A.i_P if cmps_asm.S_A.i_N > 0: warn(f'S_A in ASM has positive nitrogen content: {cmps_asm.S_S.i_N} gN/gCOD. ' @@ -2451,16 +2468,17 @@ def _compile_reactions(self): warn(f'S_A in ASM has positive phosphorous content: {cmps_asm.S_S.i_P} gN/gCOD. ' 'These phosphorous will be ignored by the interface model ' 'and could lead to imbalance of TP after conversion.') - - if cmps_asm.S_I.i_P > 0: - warn(f'S_I in ASM has positive phosphorous content: {cmps_asm.S_I.i_P} gN/gCOD. ' - 'These phosphorous will be ignored by the interface model ' - 'and could lead to imbalance of TP after conversion.') + + # ------------------------------Rai Version--------------------------------------------- + # if cmps_asm.S_I.i_P > 0: + # warn(f'S_I in ASM has positive phosphorous content: {cmps_asm.S_I.i_P} gN/gCOD. ' + # 'These phosphorous will be ignored by the interface model ' + # 'and could lead to imbalance of TP after conversion.') + # ------------------------------Rai Version--------------------------------------------- + # We do not need to check if X_S.i_N != 0 since we take care of it using X_ND_asm1 # We do not need to check if S_F.i_N != 0 since we take care of it using S_ND_asm1 - cmps_adm = outs.components - # For nitrogen balance S_ac_i_N = cmps_adm.S_ac.i_N S_aa_i_N = cmps_adm.S_aa.i_N @@ -2504,6 +2522,26 @@ def _compile_reactions(self): # Checks not required for X_PP as measured as P in both, with i_COD = i_N = 0 # Checks not required for X_PHA as measured as COD in both, with i_N = i_P = 0 + if asm_X_I_i_N != adm_X_I_i_N: + raise RuntimeError('X_I cannot be directly mapped as N content' + f'in asm2d_X_I_i_N = {asm_X_I_i_N} is not equal to' + f'adm_X_I_i_N = {adm_X_I_i_N}') + + if asm_X_I_i_P != adm_X_I_i_P: + raise RuntimeError('X_I cannot be directly mapped as P content' + f'in asm2d_X_I_i_P = {asm_X_I_i_P} is not equal to' + f'adm_X_I_i_P = {adm_X_I_i_P}') + + if asm_S_I_i_N != adm_X_I_i_N: + raise RuntimeError('S_I cannot be directly mapped as N content' + f'in asm2d_S_I_i_N = {asm_S_I_i_N} is not equal to' + f'adm_S_I_i_N = {adm_S_I_i_N}') + + if asm_S_I_i_P != adm_S_I_i_P: + raise RuntimeError('S_I cannot be directly mapped as P content' + f'in asm2d_S_I_i_P = {asm_S_I_i_P} is not equal to \n' + f'adm_S_I_i_P = {adm_S_I_i_P}') + adm_ions_idx = cmps_adm.indices(['S_IN', 'S_IP', 'S_IC', 'S_cat', 'S_an']) frac_deg = self.frac_deg @@ -2722,6 +2760,8 @@ def asm2d2madm1(asm_vals): # Step 5: map particulate inerts # 5 (a) + + # --------------------------Rai version-------------------------------------- # # First determine the amount of particulate inert N/P available from ASM2d # xi_nsp_asm2d = X_I * asm_X_I_i_N # xi_psp_asm2d = X_I * asm_X_I_i_P @@ -2765,121 +2805,132 @@ def asm2d2madm1(asm_vals): # # Since the N balance cannot hold, the P balance is not futher checked # raise RuntimeError('Not enough N in X_I, X_S to fully ' # 'convert X_I in ASM2d into X_I in ADM1.') + # --------------------------Rai version-------------------------------------- - if asm_X_I_i_N == adm_X_I_i_N and asm_X_I_i_P == adm_X_I_i_P: - pass - else: - raise RuntimeError('N and P content in X_I should be the same for direct translation') + # --------------------------Flores Alsina et al. 2016 version-------------------------------------- + # X_I = X_I # COD balance X_I += (X_H+X_AUT) * (1-frac_deg) + # --------------------------Flores Alsina et al. 2016 version-------------------------------------- - # print(f'S_NH4 = {S_NH4}\n') - - # 5(b) + # # 5(b) - # Then determine the amount of soluble inert N/P that could be produced - # in ADM1 given the ASM1 X_I - req_sn = S_I * adm_S_I_i_N - req_sp = S_I * adm_S_I_i_P + # --------------------------Rai version-------------------------------------- + # # Then determine the amount of soluble inert N/P that could be produced + # # in ADM1 given the ASM1 X_I + # req_sn = S_I * adm_S_I_i_N + # req_sp = S_I * adm_S_I_i_P - supply_inert_n_asm2d = S_I * asm_S_I_i_N + # supply_inert_n_asm2d = S_I * asm_S_I_i_N - # N balance - if req_sn <= S_ND_asm1 + supply_inert_n_asm2d: - S_ND_asm1 -= (req_sn - supply_inert_n_asm2d) - supply_inert_n_asm2d = 0 - # P balance - if req_sp <= S_F_P: - S_F_P -= req_sp - elif req_sp <= S_F_P + X_S_P: - X_S_P -= (req_sp - S_F_P) - S_F_P = 0 - elif req_sp <= S_F_P + X_S_P + S_PO4: - S_PO4 -= (req_sp - S_F_P - X_S_P) - S_F_P = X_S_P = 0 - else: - S_PO4 -= (req_sp - S_F_P - X_S_P) - S_F_P = X_S_P = 0 - # N balance - elif req_sn <= S_ND_asm1 + X_ND_asm1 + supply_inert_n_asm2d: - X_ND_asm1 -= (req_sn - S_ND_asm1 - supply_inert_n_asm2d) - S_ND_asm1 = supply_inert_n_asm2d = 0 - # P balance - if req_sp <= S_F_P: - S_F_P -= req_sp - elif req_sp <= S_F_P + X_S_P: - X_S_P -= (req_sp - S_F_P) - S_F_P = 0 - elif req_sp <= S_F_P + X_S_P + S_PO4: - S_PO4 -= (req_sp - S_F_P - X_S_P) - S_F_P = X_S_P = 0 - else: - S_PO4 -= (req_sp - S_F_P - X_S_P) - S_F_P = X_S_P = 0 - # N balance - elif req_sn <= S_ND_asm1 + X_ND_asm1 + S_NH4 + supply_inert_n_asm2d: - S_NH4 -= (req_sn - S_ND_asm1 - X_ND_asm1 - supply_inert_n_asm2d) - S_ND_asm1 = X_ND_asm1 = supply_inert_n_asm2d = 0 - # P balance - if req_sp <= S_F_P: - S_F_P -= req_sp - elif req_sp <= S_F_P + X_S_P: - X_S_P -= (req_sp - S_F_P) - S_F_P = 0 - elif req_sp <= S_F_P + X_S_P + S_PO4: - S_PO4 -= (req_sp - S_F_P - X_S_P) - S_F_P = X_S_P = 0 - else: - S_PO4 -= (req_sp - S_F_P - X_S_P) - S_F_P = X_S_P = 0 - elif req_sp <= S_F_P or req_sp <= S_F_P + X_S_P or req_sp <= S_F_P + X_S_P + S_PO4: + # # N balance + # if req_sn <= S_ND_asm1 + supply_inert_n_asm2d: + # S_ND_asm1 -= (req_sn - supply_inert_n_asm2d) + # supply_inert_n_asm2d = 0 + # # P balance + # if req_sp <= S_F_P: + # S_F_P -= req_sp + # elif req_sp <= S_F_P + X_S_P: + # X_S_P -= (req_sp - S_F_P) + # S_F_P = 0 + # elif req_sp <= S_F_P + X_S_P + S_PO4: + # S_PO4 -= (req_sp - S_F_P - X_S_P) + # S_F_P = X_S_P = 0 + # else: + # S_PO4 -= (req_sp - S_F_P - X_S_P) + # S_F_P = X_S_P = 0 + # # N balance + # elif req_sn <= S_ND_asm1 + X_ND_asm1 + supply_inert_n_asm2d: + # X_ND_asm1 -= (req_sn - S_ND_asm1 - supply_inert_n_asm2d) + # S_ND_asm1 = supply_inert_n_asm2d = 0 + # # P balance + # if req_sp <= S_F_P: + # S_F_P -= req_sp + # elif req_sp <= S_F_P + X_S_P: + # X_S_P -= (req_sp - S_F_P) + # S_F_P = 0 + # elif req_sp <= S_F_P + X_S_P + S_PO4: + # S_PO4 -= (req_sp - S_F_P - X_S_P) + # S_F_P = X_S_P = 0 + # else: + # S_PO4 -= (req_sp - S_F_P - X_S_P) + # S_F_P = X_S_P = 0 + # # N balance + # elif req_sn <= S_ND_asm1 + X_ND_asm1 + S_NH4 + supply_inert_n_asm2d: + # S_NH4 -= (req_sn - S_ND_asm1 - X_ND_asm1 - supply_inert_n_asm2d) + # S_ND_asm1 = X_ND_asm1 = supply_inert_n_asm2d = 0 + # # P balance + # if req_sp <= S_F_P: + # S_F_P -= req_sp + # elif req_sp <= S_F_P + X_S_P: + # X_S_P -= (req_sp - S_F_P) + # S_F_P = 0 + # elif req_sp <= S_F_P + X_S_P + S_PO4: + # S_PO4 -= (req_sp - S_F_P - X_S_P) + # S_F_P = X_S_P = 0 + # else: + # S_PO4 -= (req_sp - S_F_P - X_S_P) + # S_F_P = X_S_P = 0 + # elif req_sp <= S_F_P or req_sp <= S_F_P + X_S_P or req_sp <= S_F_P + X_S_P + S_PO4: - S_NH4 -= (req_sn - S_ND_asm1 - X_ND_asm1 - supply_inert_n_asm2d) - S_ND_asm1 = X_ND_asm1 = supply_inert_n_asm2d = 0 + # S_NH4 -= (req_sn - S_ND_asm1 - X_ND_asm1 - supply_inert_n_asm2d) + # S_ND_asm1 = X_ND_asm1 = supply_inert_n_asm2d = 0 - if req_sp <= S_F_P: - S_F_P -= req_sp - elif req_sp <= S_F_P + X_S_P: - X_S_P -= (req_sp - S_F_P) - S_F_P = 0 - elif req_sp <= S_F_P + X_S_P + S_PO4: - S_PO4 -= (req_sp - S_F_P - X_S_P) - S_F_P = X_S_P = 0 - else: - if (S_ND_asm1 + X_ND_asm1 + S_NH4 + supply_inert_n_asm2d)/adm_S_I_i_N < (S_F_P + X_S_P + S_PO4)/adm_S_I_i_P: - warn('Additional soluble inert COD is mapped to S_su.') - SI_cod = (S_ND_asm1 + X_ND_asm1 + S_NH4 + supply_inert_n_asm2d)/adm_S_I_i_N - S_su += S_I - SI_cod - S_I = SI_cod - S_ND_asm1 = X_ND_asm1 = S_NH4 = supply_inert_n_asm2d = 0 + # if req_sp <= S_F_P: + # S_F_P -= req_sp + # elif req_sp <= S_F_P + X_S_P: + # X_S_P -= (req_sp - S_F_P) + # S_F_P = 0 + # elif req_sp <= S_F_P + X_S_P + S_PO4: + # S_PO4 -= (req_sp - S_F_P - X_S_P) + # S_F_P = X_S_P = 0 + # else: + # if (S_ND_asm1 + X_ND_asm1 + S_NH4 + supply_inert_n_asm2d)/adm_S_I_i_N < (S_F_P + X_S_P + S_PO4)/adm_S_I_i_P: + # warn('Additional soluble inert COD is mapped to S_su.') + # SI_cod = (S_ND_asm1 + X_ND_asm1 + S_NH4 + supply_inert_n_asm2d)/adm_S_I_i_N + # S_su += S_I - SI_cod + # S_I = SI_cod + # S_ND_asm1 = X_ND_asm1 = S_NH4 = supply_inert_n_asm2d = 0 - req_sp = S_I * adm_S_I_i_P - S_PO4 -= (req_sp - S_F_P - X_S_P) - S_F_P = X_S_P = 0 - else: - warn('Additional soluble inert COD is mapped to S_su.') - SI_cod = (S_F_P + X_S_P + S_PO4)/adm_S_I_i_P - S_su += S_I - SI_cod - S_I = SI_cod - S_F_P = X_S_P = S_PO4 = 0 + # req_sp = S_I * adm_S_I_i_P + # S_PO4 -= (req_sp - S_F_P - X_S_P) + # S_F_P = X_S_P = 0 + # else: + # warn('Additional soluble inert COD is mapped to S_su.') + # SI_cod = (S_F_P + X_S_P + S_PO4)/adm_S_I_i_P + # S_su += S_I - SI_cod + # S_I = SI_cod + # S_F_P = X_S_P = S_PO4 = 0 - req_sn = S_I * adm_S_I_i_N - S_NH4 -= (req_sn - S_ND_asm1 - X_ND_asm1 - supply_inert_n_asm2d) - S_ND_asm1 = X_ND_asm1 = supply_inert_n_asm2d = 0 - - if S_PO4 < 0: - raise RuntimeError(f'S_PO4 = {S_PO4}\n''Not enough P in S_F_P, X_S_P and S_PO4 to fully ' - 'convert S_I in ASM2d into S_I in ADM1. Consider ' - 'increasing the value of P content in S_I (ASM2d)') + # req_sn = S_I * adm_S_I_i_N + # S_NH4 -= (req_sn - S_ND_asm1 - X_ND_asm1 - supply_inert_n_asm2d) + # S_ND_asm1 = X_ND_asm1 = supply_inert_n_asm2d = 0 + + # if S_PO4 < 0: + # raise RuntimeError(f'S_PO4 = {S_PO4}\n''Not enough P in S_F_P, X_S_P and S_PO4 to fully ' + # 'convert S_I in ASM2d into S_I in ADM1. Consider ' + # 'increasing the value of P content in S_I (ASM2d)') - if S_NH4 < 0: - raise RuntimeError(f'S_NH4 = {S_NH4}\n''Not enough N in S_I, S_ND_asm1, X_ND_asm1, and S_NH4 to fully ' - 'convert S_I in ASM2d into S_I in ADM1. Consider ' - 'increasing the value of N content in S_I (ASM2d)') + # if S_NH4 < 0: + # raise RuntimeError(f'S_NH4 = {S_NH4}\n''Not enough N in S_I, S_ND_asm1, X_ND_asm1, and S_NH4 to fully ' + # 'convert S_I in ASM2d into S_I in ADM1. Consider ' + # 'increasing the value of N content in S_I (ASM2d)') + # --------------------------Rai version-------------------------------------- + + # --------------------------Flores Alsina et al. 2016 version-------------------------------------- + # S_I = S_I + # --------------------------Flores Alsina et al. 2016 version-------------------------------------- # Step 6: Step map any remaining TKN/P - S_IN = S_ND_asm1 + X_ND_asm1 + S_NH4 + supply_inert_n_asm2d - # print(f'S_IN = {S_IN}') + + # --------------------------Rai version-------------------------------------- + # S_IN = S_ND_asm1 + X_ND_asm1 + S_NH4 + supply_inert_n_asm2d + # --------------------------Rai version-------------------------------------- + + # --------------------------Flores Alsina et al. 2016 version-------------------------------------- + S_IN = S_ND_asm1 + X_ND_asm1 + S_NH4 + # --------------------------Flores Alsina et al. 2016 version-------------------------------------- + S_IP = S_F_P + X_S_P + S_PO4 # Step 8: check COD and TKN balance From c2e746702d3ebe4a42b898028bb6a9a9ea9d904f Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Tue, 9 Apr 2024 16:12:31 -0500 Subject: [PATCH 305/424] Substrate inhibition ADM1 Reframing calling the function to avoid zero error. --- qsdsan/processes/_adm1_p_extension.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/qsdsan/processes/_adm1_p_extension.py b/qsdsan/processes/_adm1_p_extension.py index f36899ce..e5ad7ad0 100644 --- a/qsdsan/processes/_adm1_p_extension.py +++ b/qsdsan/processes/_adm1_p_extension.py @@ -333,10 +333,12 @@ def rhos_adm1_p_extension(state_arr, params): K_a = Ks[-2] rhos[18:22] *= substr_inhibit(substrates_modified, K_a) - PP_PAO = state_arr[25]/state_arr[26] K_pp = Ks[-1] - rhos[18:22] *= substr_inhibit(PP_PAO, K_pp) + S_conc = state_arr[25] + K_half_sat = K_pp*state_arr[26] + + rhos[18:22] *= substr_inhibit(S_conc, K_half_sat) # Multiplication by {Sva, Sbu, Spro, Sac}/(Sva + Sbu + Spro + Sac) transformation_array = state_arr[3:7]/sum(state_arr[3:7]) From b692141196df74afa2b99c543a579c28d640bcbf Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Wed, 10 Apr 2024 12:59:28 -0500 Subject: [PATCH 306/424] Correcting stoichiometry Updating stoichiometry of new processes in P extension to ADM1. --- qsdsan/processes/_adm1_p_extension.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/qsdsan/processes/_adm1_p_extension.py b/qsdsan/processes/_adm1_p_extension.py index e5ad7ad0..e9d230be 100644 --- a/qsdsan/processes/_adm1_p_extension.py +++ b/qsdsan/processes/_adm1_p_extension.py @@ -656,44 +656,44 @@ def __new__(cls, components=None, path=None, N_xc=2.686e-3, N_I=4.286e-3, N_aa=7 # Need to add 5 processes separately for stoichiometry. See ASM2d for reference. if path == _path: _p19 = Process('storage_Sva_in_XPHA', - 'S_va + [Y_po4]X_PP -> X_PHA + [?]S_K + [?]S_Mg', + 'S_va + [Y_po4]X_PP -> [?]S_IC + [?]S_IN + [?]S_IP + X_PHA + [Y_po4*K_XPP]S_K + [Y_po4*Mg_XPP]S_Mg', components=cmps, ref_component='X_PHA', rate_equation='q_PHA * S_va/(K_a+S_va) * (X_PP/X_PAO)/(K_PP+(X_PP/X_PAO)) * X_PAO * S_va/(S_va+S_bu+S_pro+S_ac)', parameters=('Y_po4', 'q_PHA', 'K_a', 'K_PP'), - conserved_for=('K', 'Mg')) + conserved_for=('C', 'N', 'P')) _p20 = Process('storage_Sbu_in_XPHA', - 'S_bu + [Y_po4]X_PP -> X_PHA + [?]S_K + [?]S_Mg', + 'S_bu + [Y_po4]X_PP -> [?]S_IC + [?]S_IN + [?]S_IP + X_PHA + [Y_po4*K_XPP]S_K + [Y_po4*Mg_XPP]S_Mg', components=cmps, ref_component='X_PHA', rate_equation='q_PHA * S_bu/(K_a+S_bu) * (X_PP/X_PAO)/(K_PP+(X_PP/X_PAO)) * X_PAO * S_bu/(S_va+S_bu+S_pro+S_ac)', parameters=('Y_po4', 'q_PHA', 'K_a', 'K_PP'), - conserved_for=('K', 'Mg')) + conserved_for=('C', 'N', 'P')) _p21 = Process('storage_Spro_in_XPHA', - 'S_pro + [Y_po4]X_PP -> X_PHA + [?]S_K + [?]S_Mg', + 'S_pro + [Y_po4]X_PP -> [?]S_IC + [?]S_IN + [?]S_IP + X_PHA + [Y_po4*K_XPP]S_K + [Y_po4*Mg_XPP]S_Mg', components=cmps, ref_component='X_PHA', rate_equation='q_PHA * S_pro/(K_a+S_pro) * (X_PP/X_PAO)/(K_PP+(X_PP/X_PAO)) * X_PAO * S_pro/(S_va+S_bu+S_pro+S_ac)', parameters=('Y_po4', 'q_PHA', 'K_a', 'K_PP'), - conserved_for=('K', 'Mg')) + conserved_for=('C', 'N', 'P')) _p22 = Process('storage_Sac_in_XPHA', - 'S_ac + [Y_po4]X_PP -> X_PHA + [?]S_K + [?]S_Mg', + 'S_ac + [Y_po4]X_PP -> [?]S_IC + [?]S_IN + [?]S_IP + X_PHA + [Y_po4*K_XPP]S_K + [Y_po4*Mg_XPP]S_Mg', components=cmps, ref_component='X_PHA', rate_equation='q_PHA * S_ac/(K_a+S_ac) * (X_PP/X_PAO)/(K_PP+(X_PP/X_PAO)) * X_PAO * S_ac/(S_va+S_bu+S_pro+S_ac)', parameters=('Y_po4', 'q_PHA', 'K_a', 'K_PP'), - conserved_for=('K', 'Mg')) + conserved_for=('C', 'N', 'P')) _p24 = Process('lysis_XPP', - 'X_PP -> [?]S_K + [?]S_Mg', + 'X_PP -> [?]S_IC + [?]S_IN + [?]S_IP + [K_XPP]S_K + [Mg_XPP]S_Mg', components=cmps, ref_component='X_PP', rate_equation='b_PP*X_PP', parameters=('b_PP'), - conserved_for=('K', 'Mg')) + conserved_for=('C', 'N', 'P')) self.insert(18, _p19) self.insert(19, _p20) From 63c25db3ebf87a6a013339791b827fc341f7b161 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Wed, 10 Apr 2024 13:07:15 -0500 Subject: [PATCH 307/424] Minor rewriting Removing X_PAO from denominator. --- qsdsan/processes/_adm1_p_extension.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/qsdsan/processes/_adm1_p_extension.py b/qsdsan/processes/_adm1_p_extension.py index e9d230be..e972b1fd 100644 --- a/qsdsan/processes/_adm1_p_extension.py +++ b/qsdsan/processes/_adm1_p_extension.py @@ -659,7 +659,7 @@ def __new__(cls, components=None, path=None, N_xc=2.686e-3, N_I=4.286e-3, N_aa=7 'S_va + [Y_po4]X_PP -> [?]S_IC + [?]S_IN + [?]S_IP + X_PHA + [Y_po4*K_XPP]S_K + [Y_po4*Mg_XPP]S_Mg', components=cmps, ref_component='X_PHA', - rate_equation='q_PHA * S_va/(K_a+S_va) * (X_PP/X_PAO)/(K_PP+(X_PP/X_PAO)) * X_PAO * S_va/(S_va+S_bu+S_pro+S_ac)', + rate_equation='q_PHA * S_va/(K_a+S_va) * (X_PP)/((K_PP*X_PAO) + X_PP) * X_PAO * S_va/(S_va+S_bu+S_pro+S_ac)', parameters=('Y_po4', 'q_PHA', 'K_a', 'K_PP'), conserved_for=('C', 'N', 'P')) @@ -667,7 +667,7 @@ def __new__(cls, components=None, path=None, N_xc=2.686e-3, N_I=4.286e-3, N_aa=7 'S_bu + [Y_po4]X_PP -> [?]S_IC + [?]S_IN + [?]S_IP + X_PHA + [Y_po4*K_XPP]S_K + [Y_po4*Mg_XPP]S_Mg', components=cmps, ref_component='X_PHA', - rate_equation='q_PHA * S_bu/(K_a+S_bu) * (X_PP/X_PAO)/(K_PP+(X_PP/X_PAO)) * X_PAO * S_bu/(S_va+S_bu+S_pro+S_ac)', + rate_equation='q_PHA * S_bu/(K_a+S_bu) * (X_PP)/((K_PP*X_PAO) + X_PP) * X_PAO * S_bu/(S_va+S_bu+S_pro+S_ac)', parameters=('Y_po4', 'q_PHA', 'K_a', 'K_PP'), conserved_for=('C', 'N', 'P')) @@ -675,7 +675,7 @@ def __new__(cls, components=None, path=None, N_xc=2.686e-3, N_I=4.286e-3, N_aa=7 'S_pro + [Y_po4]X_PP -> [?]S_IC + [?]S_IN + [?]S_IP + X_PHA + [Y_po4*K_XPP]S_K + [Y_po4*Mg_XPP]S_Mg', components=cmps, ref_component='X_PHA', - rate_equation='q_PHA * S_pro/(K_a+S_pro) * (X_PP/X_PAO)/(K_PP+(X_PP/X_PAO)) * X_PAO * S_pro/(S_va+S_bu+S_pro+S_ac)', + rate_equation='q_PHA * S_pro/(K_a+S_pro) * (X_PP)/((K_PP*X_PAO) + X_PP) * X_PAO * S_pro/(S_va+S_bu+S_pro+S_ac)', parameters=('Y_po4', 'q_PHA', 'K_a', 'K_PP'), conserved_for=('C', 'N', 'P')) @@ -683,7 +683,7 @@ def __new__(cls, components=None, path=None, N_xc=2.686e-3, N_I=4.286e-3, N_aa=7 'S_ac + [Y_po4]X_PP -> [?]S_IC + [?]S_IN + [?]S_IP + X_PHA + [Y_po4*K_XPP]S_K + [Y_po4*Mg_XPP]S_Mg', components=cmps, ref_component='X_PHA', - rate_equation='q_PHA * S_ac/(K_a+S_ac) * (X_PP/X_PAO)/(K_PP+(X_PP/X_PAO)) * X_PAO * S_ac/(S_va+S_bu+S_pro+S_ac)', + rate_equation='q_PHA * S_ac/(K_a+S_ac) * (X_PP)/((K_PP*X_PAO) + X_PP) * X_PAO * S_ac/(S_va+S_bu+S_pro+S_ac)', parameters=('Y_po4', 'q_PHA', 'K_a', 'K_PP'), conserved_for=('C', 'N', 'P')) From 23b5abdd8dae177196205dc5ce74bf11aa34471e Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Wed, 10 Apr 2024 13:59:55 -0500 Subject: [PATCH 308/424] Defined stoichiometry Defined stoichiometric coefficients for associated with K and Mg in P extension processes. --- qsdsan/processes/_adm1_p_extension.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/qsdsan/processes/_adm1_p_extension.py b/qsdsan/processes/_adm1_p_extension.py index e972b1fd..2bbcedf2 100644 --- a/qsdsan/processes/_adm1_p_extension.py +++ b/qsdsan/processes/_adm1_p_extension.py @@ -607,7 +607,8 @@ class ADM1_p_extension(CompiledProcesses): 'f_ac_fa', 'f_h2_fa', 'f_pro_va', 'f_ac_va', 'f_h2_va', 'f_ac_bu', 'f_h2_bu', 'f_ac_pro', 'f_h2_pro', 'f_ac_PHA', 'f_bu_PHA', 'f_pro_PHA', 'f_va_PHA', - 'Y_su', 'Y_aa', 'Y_fa', 'Y_c4', 'Y_pro', 'Y_ac', 'Y_h2', 'Y_po4') + 'Y_su', 'Y_aa', 'Y_fa', 'Y_c4', 'Y_pro', 'Y_ac', 'Y_h2', 'Y_po4', + 'K_XPP', 'Mg_XPP') _kinetic_params = ('rate_constants', 'half_sat_coeffs', 'pH_ULs', 'pH_LLs', 'KS_IN', 'KS_IP', 'KI_nh3', 'KIs_h2', @@ -718,7 +719,7 @@ def __new__(cls, components=None, path=None, N_xc=2.686e-3, N_I=4.286e-3, N_aa=7 f_ac_fa, 1-f_ac_fa, f_pro_va, f_ac_va, 1-f_pro_va-f_ac_va, f_ac_bu, 1-f_ac_bu, f_ac_pro, 1-f_ac_pro, f_ac_PHA, f_bu_PHA, f_pro_PHA, 1-f_ac_PHA-f_bu_PHA-f_pro_PHA, - Y_su, Y_aa, Y_fa, Y_c4, Y_pro, Y_ac, Y_h2, Y_po4) + Y_su, Y_aa, Y_fa, Y_c4, Y_pro, Y_ac, Y_h2, Y_po4, cmps.X_PP.i_K, cmps.X_PP.i_Mg) pH_LLs = np.array([pH_limits_aa[0]]*6 + [pH_limits_ac[0], pH_limits_h2[0]]) pH_ULs = np.array([pH_limits_aa[1]]*6 + [pH_limits_ac[1], pH_limits_h2[1]]) From 40b9a2cc5dddb3466e9757cb188d7aabf5602b83 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Wed, 10 Apr 2024 14:02:37 -0500 Subject: [PATCH 309/424] Conserving C, N, and P Added mechanisms to conserve C, N, and P in process 23 and 25. --- .../data/process_data/_adm1_p_extension.tsv | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/qsdsan/data/process_data/_adm1_p_extension.tsv b/qsdsan/data/process_data/_adm1_p_extension.tsv index f704e080..64b881ff 100644 --- a/qsdsan/data/process_data/_adm1_p_extension.tsv +++ b/qsdsan/data/process_data/_adm1_p_extension.tsv @@ -1,21 +1,21 @@ - S_su S_aa S_fa S_va S_bu S_pro S_ac S_h2 S_ch4 S_IC S_IN S_IP S_I X_ch X_pr X_li X_su X_aa X_fa X_c4 X_pro X_ac X_h2 X_I X_PHA X_PP X_PAO S_K S_Mg X_MeOH X_MeP -hydrolysis_carbs 1 ? ? ? -1 -hydrolysis_proteins 1 ? ? ? -1 -hydrolysis_lipids 1-f_fa_li f_fa_li ? ? ? -1 -uptake_sugars -1 (1-Y_su)*f_bu_su (1-Y_su)*f_pro_su (1-Y_su)*f_ac_su (1-Y_su)*f_h2_su ? ? ? Y_su -uptake_amino_acids -1 (1-Y_aa)*f_va_aa (1-Y_aa)*f_bu_aa (1-Y_aa)*f_pro_aa (1-Y_aa)*f_ac_aa (1-Y_aa)*f_h2_aa ? ? ? Y_aa -uptake_LCFA -1 (1-Y_fa)*f_ac_fa (1-Y_fa)*f_h2_fa ? ? ? Y_fa -uptake_valerate -1 (1-Y_c4)*f_pro_va (1-Y_c4)*f_ac_va (1-Y_c4)*f_h2_va ? ? ? Y_c4 -uptake_butyrate -1 (1-Y_c4)*f_ac_bu (1-Y_c4)*f_h2_bu ? ? ? Y_c4 -uptake_propionate -1 (1-Y_pro)*f_ac_pro (1-Y_pro)*f_h2_pro ? ? ? Y_pro -uptake_acetate -1 1-Y_ac ? ? ? Y_ac -uptake_h2 -1 1-Y_h2 ? ? ? Y_h2 -decay_Xsu ? ? ? f_sI_xb f_ch_xb f_pr_xb f_li_xb -1 f_xI_xb -decay_Xaa ? ? ? f_sI_xb f_ch_xb f_pr_xb f_li_xb -1 f_xI_xb -decay_Xfa ? ? ? f_sI_xb f_ch_xb f_pr_xb f_li_xb -1 f_xI_xb -decay_Xc4 ? ? ? f_sI_xb f_ch_xb f_pr_xb f_li_xb -1 f_xI_xb -decay_Xpro ? ? ? f_sI_xb f_ch_xb f_pr_xb f_li_xb -1 f_xI_xb -decay_Xac ? ? ? f_sI_xb f_ch_xb f_pr_xb f_li_xb -1 f_xI_xb -decay_Xh2 ? ? ? f_sI_xb f_ch_xb f_pr_xb f_li_xb -1 f_xI_xb -lysis_XPAO f_sI_xb f_ch_xb f_pr_xb f_li_xb f_xI_xb -1 -lysis_XPHA f_va_PHA f_bu_PHA f_pro_PHA f_ac_PHA -1 + S_su S_aa S_fa S_va S_bu S_pro S_ac S_h2 S_ch4 S_IC S_IN S_IP S_I X_ch X_pr X_li X_su X_aa X_fa X_c4 X_pro X_ac X_h2 X_I X_PHA X_PP X_PAO S_K S_Mg X_MeOH X_MeP +hydrolysis_carbs 1 ? ? ? -1 +hydrolysis_proteins 1 ? ? ? -1 +hydrolysis_lipids 1-f_fa_li f_fa_li ? ? ? -1 +uptake_sugars -1 (1-Y_su)*f_bu_su (1-Y_su)*f_pro_su (1-Y_su)*f_ac_su (1-Y_su)*f_h2_su ? ? ? Y_su +uptake_amino_acids -1 (1-Y_aa)*f_va_aa (1-Y_aa)*f_bu_aa (1-Y_aa)*f_pro_aa (1-Y_aa)*f_ac_aa (1-Y_aa)*f_h2_aa ? ? ? Y_aa +uptake_LCFA -1 (1-Y_fa)*f_ac_fa (1-Y_fa)*f_h2_fa ? ? ? Y_fa +uptake_valerate -1 (1-Y_c4)*f_pro_va (1-Y_c4)*f_ac_va (1-Y_c4)*f_h2_va ? ? ? Y_c4 +uptake_butyrate -1 (1-Y_c4)*f_ac_bu (1-Y_c4)*f_h2_bu ? ? ? Y_c4 +uptake_propionate -1 (1-Y_pro)*f_ac_pro (1-Y_pro)*f_h2_pro ? ? ? Y_pro +uptake_acetate -1 1-Y_ac ? ? ? Y_ac +uptake_h2 -1 1-Y_h2 ? ? ? Y_h2 +decay_Xsu ? ? ? f_sI_xb f_ch_xb f_pr_xb f_li_xb -1 f_xI_xb +decay_Xaa ? ? ? f_sI_xb f_ch_xb f_pr_xb f_li_xb -1 f_xI_xb +decay_Xfa ? ? ? f_sI_xb f_ch_xb f_pr_xb f_li_xb -1 f_xI_xb +decay_Xc4 ? ? ? f_sI_xb f_ch_xb f_pr_xb f_li_xb -1 f_xI_xb +decay_Xpro ? ? ? f_sI_xb f_ch_xb f_pr_xb f_li_xb -1 f_xI_xb +decay_Xac ? ? ? f_sI_xb f_ch_xb f_pr_xb f_li_xb -1 f_xI_xb +decay_Xh2 ? ? ? f_sI_xb f_ch_xb f_pr_xb f_li_xb -1 f_xI_xb +lysis_XPAO ? ? ? f_sI_xb f_ch_xb f_pr_xb f_li_xb f_xI_xb -1 +lysis_XPHA f_va_PHA f_bu_PHA f_pro_PHA f_ac_PHA ? ? ? -1 \ No newline at end of file From e15585731727efa2ba43ac84c646f72d9b15dcb9 Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Thu, 11 Apr 2024 10:32:17 -0500 Subject: [PATCH 310/424] Rate eqn in process description Added rate equations to the description of processes. --- .../data/process_data/_adm1_p_extension.tsv | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/qsdsan/data/process_data/_adm1_p_extension.tsv b/qsdsan/data/process_data/_adm1_p_extension.tsv index 64b881ff..b4376c10 100644 --- a/qsdsan/data/process_data/_adm1_p_extension.tsv +++ b/qsdsan/data/process_data/_adm1_p_extension.tsv @@ -1,21 +1,21 @@ - S_su S_aa S_fa S_va S_bu S_pro S_ac S_h2 S_ch4 S_IC S_IN S_IP S_I X_ch X_pr X_li X_su X_aa X_fa X_c4 X_pro X_ac X_h2 X_I X_PHA X_PP X_PAO S_K S_Mg X_MeOH X_MeP -hydrolysis_carbs 1 ? ? ? -1 -hydrolysis_proteins 1 ? ? ? -1 -hydrolysis_lipids 1-f_fa_li f_fa_li ? ? ? -1 -uptake_sugars -1 (1-Y_su)*f_bu_su (1-Y_su)*f_pro_su (1-Y_su)*f_ac_su (1-Y_su)*f_h2_su ? ? ? Y_su -uptake_amino_acids -1 (1-Y_aa)*f_va_aa (1-Y_aa)*f_bu_aa (1-Y_aa)*f_pro_aa (1-Y_aa)*f_ac_aa (1-Y_aa)*f_h2_aa ? ? ? Y_aa -uptake_LCFA -1 (1-Y_fa)*f_ac_fa (1-Y_fa)*f_h2_fa ? ? ? Y_fa -uptake_valerate -1 (1-Y_c4)*f_pro_va (1-Y_c4)*f_ac_va (1-Y_c4)*f_h2_va ? ? ? Y_c4 -uptake_butyrate -1 (1-Y_c4)*f_ac_bu (1-Y_c4)*f_h2_bu ? ? ? Y_c4 -uptake_propionate -1 (1-Y_pro)*f_ac_pro (1-Y_pro)*f_h2_pro ? ? ? Y_pro -uptake_acetate -1 1-Y_ac ? ? ? Y_ac -uptake_h2 -1 1-Y_h2 ? ? ? Y_h2 -decay_Xsu ? ? ? f_sI_xb f_ch_xb f_pr_xb f_li_xb -1 f_xI_xb -decay_Xaa ? ? ? f_sI_xb f_ch_xb f_pr_xb f_li_xb -1 f_xI_xb -decay_Xfa ? ? ? f_sI_xb f_ch_xb f_pr_xb f_li_xb -1 f_xI_xb -decay_Xc4 ? ? ? f_sI_xb f_ch_xb f_pr_xb f_li_xb -1 f_xI_xb -decay_Xpro ? ? ? f_sI_xb f_ch_xb f_pr_xb f_li_xb -1 f_xI_xb -decay_Xac ? ? ? f_sI_xb f_ch_xb f_pr_xb f_li_xb -1 f_xI_xb -decay_Xh2 ? ? ? f_sI_xb f_ch_xb f_pr_xb f_li_xb -1 f_xI_xb -lysis_XPAO ? ? ? f_sI_xb f_ch_xb f_pr_xb f_li_xb f_xI_xb -1 -lysis_XPHA f_va_PHA f_bu_PHA f_pro_PHA f_ac_PHA ? ? ? -1 \ No newline at end of file + S_su S_aa S_fa S_va S_bu S_pro S_ac S_h2 S_ch4 S_IC S_IN S_IP S_I X_ch X_pr X_li X_su X_aa X_fa X_c4 X_pro X_ac X_h2 X_I X_PHA X_PP X_PAO S_K S_Mg X_MeOH X_MeP +hydrolysis_carbs 1 ? ? ? -1 q_ch_hyd * X_ch +hydrolysis_proteins 1 ? ? ? -1 q_pr_hyd* X_pr +hydrolysis_lipids 1-f_fa_li f_fa_li ? ? ? -1 q_li_hyd * X_li +uptake_sugars -1 (1-Y_su)*f_bu_su (1-Y_su)*f_pro_su (1-Y_su)*f_ac_su (1-Y_su)*f_h2_su ? ? ? Y_su k_su * X_su * S_su/(K_su + S_su) +uptake_amino_acids -1 (1-Y_aa)*f_va_aa (1-Y_aa)*f_bu_aa (1-Y_aa)*f_pro_aa (1-Y_aa)*f_ac_aa (1-Y_aa)*f_h2_aa ? ? ? Y_aa k_aa * X_aa * S_aa/(K_aa + S_aa) +uptake_LCFA -1 (1-Y_fa)*f_ac_fa (1-Y_fa)*f_h2_fa ? ? ? Y_fa k_fa * X_fa * S_fa/(K_fa + S_fa) +uptake_valerate -1 (1-Y_c4)*f_pro_va (1-Y_c4)*f_ac_va (1-Y_c4)*f_h2_va ? ? ? Y_c4 k_c4 * X_c4 * S_va/(K_c4 + S_va) * S_va/(S_va + S_bu) +uptake_butyrate -1 (1-Y_c4)*f_ac_bu (1-Y_c4)*f_h2_bu ? ? ? Y_c4 k_c4 * X_c4 * S_bu/(K_c4 + S_bu) * S_bu/(S_va + S_bu) +uptake_propionate -1 (1-Y_pro)*f_ac_pro (1-Y_pro)*f_h2_pro ? ? ? Y_pro k_pro * X_pro * S_pro/(K_pro + S_pro) +uptake_acetate -1 1-Y_ac ? ? ? Y_ac k_ac * X_ac * S_ac/(K_ac + S_ac) +uptake_h2 -1 1-Y_h2 ? ? ? Y_h2 k_h2 * X_h2 * S_h2/(K_h2 + S_h2) +decay_Xsu ? ? ? f_sI_xb f_ch_xb f_pr_xb f_li_xb -1 f_xI_xb b_su * X_su +decay_Xaa ? ? ? f_sI_xb f_ch_xb f_pr_xb f_li_xb -1 f_xI_xb b_aa * X_aa +decay_Xfa ? ? ? f_sI_xb f_ch_xb f_pr_xb f_li_xb -1 f_xI_xb b_fa * X_fa +decay_Xc4 ? ? ? f_sI_xb f_ch_xb f_pr_xb f_li_xb -1 f_xI_xb b_c4 * X_c4 +decay_Xpro ? ? ? f_sI_xb f_ch_xb f_pr_xb f_li_xb -1 f_xI_xb b_pro * X_pro +decay_Xac ? ? ? f_sI_xb f_ch_xb f_pr_xb f_li_xb -1 f_xI_xb b_ac * X_ac +decay_Xh2 ? ? ? f_sI_xb f_ch_xb f_pr_xb f_li_xb -1 f_xI_xb b_h2 * X_h2 +lysis_XPAO ? ? ? f_sI_xb f_ch_xb f_pr_xb f_li_xb f_xI_xb -1 b_PAO * X_PAO +lysis_XPHA f_va_PHA f_bu_PHA f_pro_PHA f_ac_PHA ? ? ? -1 b_PHA * X_PHA \ No newline at end of file From ef4d7b81575accfa4e10eb248cff25e8aa3a1df9 Mon Sep 17 00:00:00 2001 From: Yalin Date: Mon, 15 Apr 2024 09:15:07 -0400 Subject: [PATCH 311/424] mini code cleanup for BSM2 primary clarifier --- qsdsan/sanunits/_clarifier.py | 260 ++-------------------------------- 1 file changed, 15 insertions(+), 245 deletions(-) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index 53ea520f..a43d93c5 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -881,19 +881,18 @@ class PrimaryClarifierBSM2(SanUnit): Influent to the clarifier. Expected number of influent is 3. outs : class:`WasteStream` Sludge (uf) and treated effluent (of). - Hydraulic Retention time : float - Hydraulic Retention Time in days. The default is 0.04268 days, based on IWA report.[1] + HRT : float + Hydraulic retention time in days. The default is 0.04268 days, based on IWA report.[1] ratio_uf : float The ratio of sludge to primary influent. The default is 0.007, based on IWA report.[1] f_corr : float Dimensionless correction factor for removal efficiency in the primary clarifier.[1] - - # cylindrical_depth : float, optional - # The depth of the cylindrical portion of clarifier [in m]. - # upflow_velocity : float, optional - # Speed with which influent enters the center feed of the clarifier [m/hr]. The default is 43.2. - # F_BM : dict - # Equipment bare modules. + cylindrical_depth : float, optional + The depth of the cylindrical portion of clarifier [in m]. + upflow_velocity : float, optional + Speed with which influent enters the center feed of the clarifier [m/hr]. The default is 43.2. + F_BM : dict + Equipment bare modules. Examples -------- @@ -979,12 +978,12 @@ class PrimaryClarifierBSM2(SanUnit): pumps = ('sludge',) def __init__(self, ID='', ins=None, outs=(), thermo=None, - isdynamic=False, init_with='WasteStream', Hydraulic_Retention_Time=0.04268, + isdynamic=False, init_with='WasteStream', HRT=0.04268, ratio_uf=0.007, f_corr=0.65, cylindrical_depth = 5, upflow_velocity = 43.2, F_BM=default_F_BM, **kwargs): SanUnit.__init__(self, ID, ins, outs, thermo, isdynamic=isdynamic, init_with=init_with) - self.Hydraulic_Retention_Time = Hydraulic_Retention_Time #in days + self.HRT = HRT # in days self.ratio_uf = ratio_uf self.f_corr = f_corr self.cylindrical_depth = cylindrical_depth # in m @@ -992,51 +991,22 @@ def __init__(self, ID='', ins=None, outs=(), thermo=None, self.F_BM.update(default_F_BM) self._mixed = self.ins[0].copy(f'{ID}_mixed') self._sludge = self.outs[1].copy(f'{ID}_sludge') - - @property - def Hydraulic_Retention_Time(self): - '''The Hydraulic Retention time in days.''' - return self._HRT - - @Hydraulic_Retention_Time.setter - def Hydraulic_Retention_Time(self, HRT): - if HRT is not None: - self._HRT = HRT - else: - raise ValueError('HRT expected from user') + @property def ratio_uf(self): return self._r - @ratio_uf.setter def ratio_uf(self, r): - if r is not None: - if r > 1 or r < 0: - raise ValueError(f'Sludge to influent ratio must be within [0, 1], not {r}') - self._r = r - else: - raise ValueError('Sludge to influent ratio expected from user') + if r > 1 or r < 0: + raise ValueError(f'Sludge to influent ratio must be within [0, 1], not {r}') + self._r = r - @property - def f_corr(self): - return self._corr - - @f_corr.setter - def f_corr(self, corr): - if corr is not None: - # if corr > 1 or corr < 0: - # raise ValueError(f'correction factor must be within [0, 1], not {corr}') - self._corr = corr - else: - raise ValueError('correction factor expected from user') def _f_i(self): xcod = self._mixed.composite('COD', particle_size='x') fx = xcod/self._mixed.COD - corr = self._corr - HRT = self._HRT - n_COD = corr*(2.88*fx - 0.118)*(1.45 + 6.15*np.log(HRT*24*60)) + n_COD = self.f_corr*(2.88*fx - 0.118)*(1.45 + 6.15*np.log(self.HRT*24*60)) f_i = 1 - (n_COD/100) return f_i @@ -1115,206 +1085,6 @@ def yt(t, QC_ins, dQC_ins): _update_dstate() self._AE = yt - - # _units = { - # 'Number of clarifiers': '?', - # 'Cylindrical volume': 'm3', - # 'Cylindrical depth': 'm', - # 'Cylindrical diameter': 'm', - - # 'Conical radius': 'm', - # 'Conical depth': 'm', - # 'Conical volume': 'm3', - - # 'Volume': 'm3', - # 'Center feed depth': 'm', - # 'Upflow velocity': 'm/hr', - # 'Center feed diameter': 'm', - # 'Volume of concrete wall': 'm3', - # 'Stainless steel': 'kg', - # 'Pump pipe stainless steel' : 'kg', - # 'Pump stainless steel': 'kg', - # 'Number of pumps': '?' - # } - - - # def _design_pump(self): - # ID, pumps = self.ID, self.pumps - # self._sludge.copy_like(self.outs[1]) - - # ins_dct = { - # 'sludge': self._sludge, - # } - - # type_dct = dict.fromkeys(pumps, 'sludge') - # inputs_dct = dict.fromkeys(pumps, (1,),) - - # D = self.design_results - # influent_Q = self._sludge.get_total_flow('m3/hr')/D['Number of clarifiers'] - # influent_Q_mgd = influent_Q*0.00634 # m3/hr to MGD - - # for i in pumps: - # if hasattr(self, f'{i}_pump'): - # p = getattr(self, f'{i}_pump') - # setattr(p, 'add_inputs', inputs_dct[i]) - # else: - # ID = f'{ID}_{i}' - # capacity_factor=1 - # pump = WWTpump( - # ID=ID, ins=ins_dct[i], pump_type=type_dct[i], - # Q_mgd=influent_Q_mgd, add_inputs=inputs_dct[i], - # capacity_factor=capacity_factor, - # include_pump_cost=True, - # include_building_cost=False, - # include_OM_cost=True, - # ) - # setattr(self, f'{i}_pump', pump) - - # pipe_ss, pump_ss = 0., 0. - # for i in pumps: - # p = getattr(self, f'{i}_pump') - # p.simulate() - # p_design = p.design_results - # pipe_ss += p_design['Pump pipe stainless steel'] - # pump_ss += p_design['Pump stainless steel'] - # return pipe_ss, pump_ss - - - # def _design(self): - - # self._mixed.mix_from(self.ins) - - # D = self.design_results - # total_flow = self._mixed.get_total_flow('m3/hr') - - # if total_flow <= 1580: # 10 MGD - # design_flow = 790 # 5 MGD - # elif total_flow >1580 and total_flow <= 4730: # Between 10 and 30 MGD - # design_flow = 2365 # 15 MGD - # elif total_flow > 4730 and total_flow <= 15770: # Between 30 and 100 MGD - # design_flow = 3940 # 25 MGD - # else: - # design_flow = 5520 # 35 MGD - - # D['Number of clarifiers'] = np.ceil(total_flow/design_flow) - - # total_volume = 24*self._HRT*design_flow #in m3 - # working_volume = total_volume/0.8 # Assume 80% working volume - - # D['Cylindrical volume'] = working_volume - # # Sidewater depth of a cylindrical clarifier lies between 2.5-5m - # D['Cylindrical depth'] = self.cylindrical_depth # in m - # # The tank diameter can lie anywhere between 3 m to 100 m - # D['Cylindrical diameter'] = (4*working_volume/(3.14*D['Cylindrical depth']))**(1/2) # in m - - # D['Conical radius'] = D['Cylindrical diameter']/2 - # # The slope of the bottom conical floor lies between 1:10 to 1:12 - # D['Conical depth'] = D['Conical radius']/10 - # D['Conical volume'] = (3.14/3)*(D['Conical radius']**2)*D['Conical depth'] - - # D['Volume'] = D['Cylindrical volume'] + D['Conical volume'] - - # # Primary clarifiers can be center feed or peripheral feed. The design here is for the more - # # commonly deployed center feed. - - # # Depth of the center feed lies between 30-75% of sidewater depth - # D['Center feed depth'] = 0.5*D['Cylindrical depth'] - # # Typical conventional feed wells are designed for an average downflow velocity - # # of 10-13 mm/s and maximum velocity of 25-30 mm/s - # peak_flow_safety_factor = 2.5 # assumed based on average and maximum velocities - # upflow_velocity = self.upflow_velocity # in m/hr (converted from 12 mm/sec) - # D['Upflow velocity'] = upflow_velocity*peak_flow_safety_factor # in m/hr - # Center_feed_area = design_flow/D['Upflow velocity'] # in m2 - # D['Center feed diameter'] = ((4*Center_feed_area)/3.14)**(1/2) # Sanity check: Diameter of the center feed lies between 15-25% of tank diameter - - # # Amount of concrete required - # D_tank = D['Cylindrical depth']*39.37 # m to inches - # # Thickness of the wall concrete, [m]. Default to be minimum of 1 ft with 1 in added for every ft of depth over 12 ft. - # thickness_concrete_wall = (1 + max(D_tank-12, 0)/12)*0.3048 # from feet to m - # inner_diameter = D['Cylindrical diameter'] - # outer_diameter = inner_diameter + 2*thickness_concrete_wall - # volume_cylindrical_wall = (3.14*D['Cylindrical depth']/4)*(outer_diameter**2 - inner_diameter**2) - # volume_conical_wall = (3.14/3)*(D['Conical depth']/4)*(outer_diameter**2 - inner_diameter**2) - # D['Volume of concrete wall'] = volume_cylindrical_wall + volume_conical_wall # in m3 - - # # Amount of metal required for center feed - # thickness_metal_wall = 0.5 # in m (!! NEED A RELIABLE SOURCE !!) - # inner_diameter_center_feed = D['Center feed diameter'] - # outer_diameter_center_feed = inner_diameter_center_feed + 2*thickness_metal_wall - # volume_center_feed = (3.14*D['Center feed depth']/4)*(outer_diameter_center_feed**2 - inner_diameter_center_feed **2) - # density_ss = 7930 # kg/m3, 18/8 Chromium - # D['Stainless steel'] = volume_center_feed*density_ss # in kg - - # # Pumps - # pipe, pumps = self._design_pump() - # D['Pump pipe stainless steel'] = pipe - # D['Pump stainless steel'] = pumps - # # For primary clarifier - # D['Number of pumps'] = D['Number of clarifiers'] - - # def _cost(self): - - # self._mixed.mix_from(self.ins) - # D = self.design_results - # C = self.baseline_purchase_costs - - # # Construction of concrete and stainless steel walls - # C['Wall concrete'] = D['Number of clarifiers']*D['Volume of concrete wall']*self.wall_concrete_unit_cost - # C['Wall stainless steel'] = D['Number of clarifiers']*D['Stainless steel']*self.stainless_steel_unit_cost - - # # Cost of equipment - - # # Source of scaling exponents: Process Design and Economics for Biochemical Conversion of Lignocellulosic Biomass to Ethanol by NREL. - - # # Scraper - # # Source: https://www.alibaba.com/product-detail/Peripheral-driving-clarifier-mud-scraper-waste_1600891102019.html?spm=a2700.details.0.0.47ab45a4TP0DLb - # base_cost_scraper = 2500 - # base_flow_scraper = 1 # in m3/hr (!!! Need to know whether this is for solids or influent !!!) - # clarifier_flow = self._mixed.get_total_flow('m3/hr')/D['Number of clarifiers'] - # C['Scraper'] = D['Number of clarifiers']*base_cost_scraper*(clarifier_flow/base_flow_scraper)**0.6 - # base_power_scraper = 2.75 # in kW - # scraper_power = D['Number of clarifiers']*base_power_scraper*(clarifier_flow/base_flow_scraper)**0.6 - - # # v notch weir - # # Source: https://www.alibaba.com/product-detail/50mm-Tube-Settler-Media-Modules-Inclined_1600835845218.html?spm=a2700.galleryofferlist.normal_offer.d_title.69135ff6o4kFPb - # base_cost_v_notch_weir = 6888 - # base_flow_v_notch_weir = 10 # in m3/hr - # C['v notch weir'] = D['Number of clarifiers']*base_cost_v_notch_weir*(clarifier_flow/base_flow_v_notch_weir)**0.6 - - # # Pump (construction and maintenance) - # pumps = self.pumps - # add_OPEX = self.add_OPEX - # pump_cost = 0. - # building_cost = 0. - # opex_o = 0. - # opex_m = 0. - - # for i in pumps: - # p = getattr(self, f'{i}_pump') - # p_cost = p.baseline_purchase_costs - # p_add_opex = p.add_OPEX - # pump_cost += p_cost['Pump'] - # building_cost += p_cost['Pump building'] - # opex_o += p_add_opex['Pump operating'] - # opex_m += p_add_opex['Pump maintenance'] - - # C['Pumps'] = pump_cost*D['Number of pumps'] - # C['Pump building'] = building_cost*D['Number of pumps'] - # add_OPEX['Pump operating'] = opex_o*D['Number of pumps'] - # add_OPEX['Pump maintenance'] = opex_m*D['Number of pumps'] - - # # Power - # pumping = 0. - # for ID in self.pumps: - # p = getattr(self, f'{ID}_pump') - # if p is None: - # continue - # pumping += p.power_utility.rate - - # pumping = pumping*D['Number of pumps'] - - # self.power_utility.consumption += pumping - # self.power_utility.consumption += scraper_power #%% # Assign a bare module of 1 to all From 70456d46df5acf2976bd586d0a5b11842dcd5754 Mon Sep 17 00:00:00 2001 From: Yalin Date: Mon, 15 Apr 2024 09:15:41 -0400 Subject: [PATCH 312/424] update `get_SRT` util func to deal with no biomass waste --- qsdsan/utils/wwt_design.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/qsdsan/utils/wwt_design.py b/qsdsan/utils/wwt_design.py index 37b9bdb3..c7d454cf 100644 --- a/qsdsan/utils/wwt_design.py +++ b/qsdsan/utils/wwt_design.py @@ -13,7 +13,7 @@ ''' import numpy as np - +from warnings import warn __all__ = ('get_SRT', 'get_oxygen_heterotrophs', @@ -70,6 +70,9 @@ def get_SRT(system, biomass_IDs, wastage=None, active_unit_IDs=None): if wastage is None: wastage = [ws for ws in system.products if ws.phase in ('l','s')] waste = sum([ws.composite('solids', subgroup=biomass_IDs)*ws.F_vol*24 \ for ws in wastage]) + if waste == 0: + warn('Wasted biomass calculated to be 0.') + return units = system.units if active_unit_IDs is None \ else [u for u in system.units if u.ID in active_unit_IDs] retain = sum([u.get_retained_mass(biomass_IDs) for u in units if u.isdynamic]) From c305118f706ea1765586dfa36c428eba8348f208 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Tue, 16 Apr 2024 17:56:36 -0700 Subject: [PATCH 313/424] made PrimaryClarifierBSM2 dynamic w/ ODEs --- qsdsan/sanunits/_clarifier.py | 251 ++++++++++++------ .../sanunits/_suspended_growth_bioreactor.py | 5 +- 2 files changed, 176 insertions(+), 80 deletions(-) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index a43d93c5..0093d84b 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -867,7 +867,17 @@ def _design(self): pass -# %% +# %% +# Total COD removal efficiency +nCOD = lambda f_corr, fx, HRT: f_corr*(2.88*fx - 0.118)*(1.45 + 6.15*np.log(HRT*24*60)) + +def calc_f_i(fx, f_corr, HRT): + '''calculates the effluent-to-influent ratio of solid concentrations''' + nX = nCOD(f_corr, fx, HRT)/fx + if nX > 100: nX = 100 + if nX < 0: nX = 0 + return 1-(nX/100) + class PrimaryClarifierBSM2(SanUnit): """ @@ -968,7 +978,7 @@ class PrimaryClarifierBSM2(SanUnit): """ _N_ins = 3 - _N_outs = 2 + _N_outs = 2 # [0] effluent; [1] underflow _ins_size_is_fixed = False # Costs @@ -976,22 +986,27 @@ class PrimaryClarifierBSM2(SanUnit): stainless_steel_unit_cost=1.8 # $/kg (Taken from Joy's METAB code) https://www.alibaba.com/product-detail/brushed-stainless-steel-plate-304l-stainless_1600391656401.html?spm=a2700.details.0.0.230e67e6IKwwFd pumps = ('sludge',) + t_m = 0.125 # Smoothing time constant for qm calculation def __init__(self, ID='', ins=None, outs=(), thermo=None, - isdynamic=False, init_with='WasteStream', HRT=0.04268, - ratio_uf=0.007, f_corr=0.65, cylindrical_depth = 5, upflow_velocity = 43.2, + isdynamic=True, init_with='WasteStream', + volume=900, ratio_uf=0.007, mean_f_x=0.85, + f_corr=0.65, cylindrical_depth = 5, upflow_velocity=43.2, F_BM=default_F_BM, **kwargs): SanUnit.__init__(self, ID, ins, outs, thermo, isdynamic=isdynamic, - init_with=init_with) - self.HRT = HRT # in days + init_with=init_with) + # self.HRT = HRT # in days + self._mixed = self.ins[0].copy(f'{ID}_mixed') + self._sludge = np.ones(len(self.components)+1) + self._effluent = np.ones(len(self.components)+1) + self.V = volume self.ratio_uf = ratio_uf + self.f_x = mean_f_x self.f_corr = f_corr self.cylindrical_depth = cylindrical_depth # in m self.upflow_velocity = upflow_velocity # in m/hr (converted from 12 mm/sec) self.F_BM.update(default_F_BM) - self._mixed = self.ins[0].copy(f'{ID}_mixed') - self._sludge = self.outs[1].copy(f'{ID}_sludge') - + self._concs = None @property def ratio_uf(self): @@ -1001,90 +1016,174 @@ def ratio_uf(self, r): if r > 1 or r < 0: raise ValueError(f'Sludge to influent ratio must be within [0, 1], not {r}') self._r = r - - - def _f_i(self): - xcod = self._mixed.composite('COD', particle_size='x') - fx = xcod/self._mixed.COD - n_COD = self.f_corr*(2.88*fx - 0.118)*(1.45 + 6.15*np.log(self.HRT*24*60)) - f_i = 1 - (n_COD/100) - return f_i - - def _run(self): - uf, of = self.outs - cmps = self.components - mixed = self._mixed - mixed.mix_from(self.ins) + self._sludge[-1] = r + self._effluent[-1] = 1-r + + # def _f_i(self): + # xcod = self._mixed.composite('COD', particle_size='x') + # fx = xcod/self._mixed.COD + # n_COD = self.f_corr*(2.88*fx - 0.118)*(1.45 + 6.15*np.log(self.HRT*24*60)) + # f_i = 1 - (n_COD/100) + # return f_i - r = self._r - f_i = self._f_i() - - Xs = (1 - f_i)*mixed.mass*cmps.x - Xe = (f_i)*mixed.mass*cmps.x + # def _run(self): + # uf, of = self.outs + # cmps = self.components + # mixed = self._mixed + # mixed.mix_from(self.ins) + + # r = self._r + # f_i = self._f_i() - Zs = r*mixed.mass*cmps.s - Ze = (1-r)*mixed.mass*cmps.s + # Xs = (1 - f_i)*mixed.mass*cmps.x + # Xe = (f_i)*mixed.mass*cmps.x - Ce = Ze + Xe - Cs = Zs + Xs - of.set_flow(Ce,'kg/hr') - uf.set_flow(Cs,'kg/hr') + # Zs = r*mixed.mass*cmps.s + # Ze = (1-r)*mixed.mass*cmps.s + # Ce = Ze + Xe + # Cs = Zs + Xs + # of.set_flow(Ce,'kg/hr') + # uf.set_flow(Cs,'kg/hr') + + @property + def f_x(self): + '''[float] Fraction of particulate COD [-].''' + if self._f_x: return self._f_x + else: + concs = self._mixed.conc + cmps = self._mixed.components + cod_concs = concs*cmps.i_COD + if sum(cod_concs) == 0: return + return sum(cod_concs*cmps.x)/sum(cod_concs) + @f_x.setter + def f_x(self, f): + if isinstance(f, (float, int)) and (f < 0 or f > 1): + raise ValueError('f_x must be within [0,1]') + self._f_x = f + + def _run(self): + of, uf = self.outs + mixed = self._mixed + mixed.mix_from(self.ins) + x = self.components.x + r = self._r + f_i = calc_f_i(self.f_x, self.f_corr, self.t_m) + split_to_uf = (1-x)*r + x*(1-(1-r)*f_i) + mixed.split_to(uf, of, split_to_uf) + + def set_init_conc(self, **kwargs): + '''set the initial concentrations [mg/L].''' + self._concs = self.components.kwarray(kwargs) + def _init_state(self): - # if multiple wastestreams exist then concentration and total flow - # would be calculated assuming perfect mixing - Qs = self._ins_QC[:,-1] - Cs = self._ins_QC[:,:-1] - self._state = np.append(Qs @ Cs / Qs.sum(), Qs.sum()) + mixed = self._mixed + Q = mixed.get_total_flow('m3/d') + if self._concs is not None: Cs = self._concs + else: Cs = mixed.conc + self._state = np.append(Cs, Q).astype('float64') self._dstate = self._state * 0. - - uf, of = self.outs - s_flow = uf.F_vol/(uf.F_vol+of.F_vol) - denominator = uf.mass + of.mass - denominator += (denominator == 0) - s = uf.mass/denominator - self._sludge = np.append(s/s_flow, s_flow) - self._effluent = np.append((1-s)/(1-s_flow), 1-s_flow) - + + def _update_parameters(self): + x = self.components.x + r = self._r + Q = self._state[-1] + f_i = calc_f_i(self.f_x, self.f_corr, self.V/Q) + self._sludge[:-1] = x * ((1-f_i)/r+f_i) + (1-x) + self._effluent[:-1] = x * f_i + (1-x) + def _update_state(self): '''updates conditions of output stream based on conditions of the Primary Clarifier''' - self._outs[0].state = self._sludge * self._state - self._outs[1].state = self._effluent * self._state - + of, uf = self.outs + uf.state = self._sludge * self._state + of.state = self._effluent * self._state + def _update_dstate(self): '''updates rates of change of output stream from rates of change of the Primary Clarifier''' - self._outs[0].dstate = self._sludge * self._dstate - self._outs[1].dstate = self._effluent * self._dstate + of, uf = self.outs + uf.dstate = self._sludge * self._dstate + of.dstate = self._effluent * self._dstate + + # def _init_state(self): + # # if multiple wastestreams exist then concentration and total flow + # # would be calculated assuming perfect mixing + # Qs = self._ins_QC[:,-1] + # Cs = self._ins_QC[:,:-1] + # self._state = np.append(Qs @ Cs / Qs.sum(), Qs.sum()) + # self._dstate = self._state * 0. + + # uf, of = self.outs + # s_flow = uf.F_vol/(uf.F_vol+of.F_vol) + # denominator = uf.mass + of.mass + # denominator += (denominator == 0) + # s = uf.mass/denominator + # self._sludge = np.append(s/s_flow, s_flow) + # self._effluent = np.append((1-s)/(1-s_flow), 1-s_flow) + + # def _update_state(self): + # '''updates conditions of output stream based on conditions of the Primary Clarifier''' + # self._outs[0].state = self._sludge * self._state + # self._outs[1].state = self._effluent * self._state + + # def _update_dstate(self): + # '''updates rates of change of output stream from rates of change of the Primary Clarifier''' + # self._outs[0].dstate = self._sludge * self._dstate + # self._outs[1].dstate = self._effluent * self._dstate + # @property + # def AE(self): + # if self._AE is None: + # self._compile_AE() + # return self._AE + + # def _compile_AE(self): + # _state = self._state + # _dstate = self._dstate + # _update_state = self._update_state + # _update_dstate = self._update_dstate + # def yt(t, QC_ins, dQC_ins): + # #Because there are multiple inlets + # Q_ins = QC_ins[:, -1] + # C_ins = QC_ins[:, :-1] + # dQ_ins = dQC_ins[:, -1] + # dC_ins = dQC_ins[:, :-1] + # Q = Q_ins.sum() + # C = Q_ins @ C_ins / Q + # _state[-1] = Q + # _state[:-1] = C + # Q_dot = dQ_ins.sum() + # C_dot = (dQ_ins @ C_ins + Q_ins @ dC_ins - Q_dot * C)/Q + # _dstate[-1] = Q_dot + # _dstate[:-1] = C_dot + # _update_state() + # _update_dstate() + # self._AE = yt @property - def AE(self): - if self._AE is None: - self._compile_AE() - return self._AE - - def _compile_AE(self): - _state = self._state + def ODE(self): + if self._ODE is None: + self._compile_ODE() + return self._ODE + + def _compile_ODE(self): _dstate = self._dstate - _update_state = self._update_state + _update_parameters = self._update_parameters _update_dstate = self._update_dstate - def yt(t, QC_ins, dQC_ins): - #Because there are multiple inlets + V = self.V + t_m = self.t_m + def dy_dt(t, QC_ins, QC, dQC_ins): Q_ins = QC_ins[:, -1] C_ins = QC_ins[:, :-1] - dQ_ins = dQC_ins[:, -1] - dC_ins = dQC_ins[:, :-1] - Q = Q_ins.sum() - C = Q_ins @ C_ins / Q - _state[-1] = Q - _state[:-1] = C - Q_dot = dQ_ins.sum() - C_dot = (dQ_ins @ C_ins + Q_ins @ dC_ins - Q_dot * C)/Q - _dstate[-1] = Q_dot - _dstate[:-1] = C_dot - _update_state() + # dQ_ins = dQC_ins[:, -1] + # dC_ins = dQC_ins[:, :-1] + Q_in = Q_ins.sum() + C_in = Q_ins @ C_ins / Q_in + C = QC[:-1] + Q = QC[-1] + _dstate[:-1] = Q_in*(C_in - C)/V + _dstate[-1] = (Q_in-Q)/t_m + _update_parameters() _update_dstate() - self._AE = yt - + self._ODE = dy_dt #%% # Assign a bare module of 1 to all diff --git a/qsdsan/sanunits/_suspended_growth_bioreactor.py b/qsdsan/sanunits/_suspended_growth_bioreactor.py index cb714900..f3e01f86 100644 --- a/qsdsan/sanunits/_suspended_growth_bioreactor.py +++ b/qsdsan/sanunits/_suspended_growth_bioreactor.py @@ -272,10 +272,7 @@ def state(self, QCs): def set_init_conc(self, **kwargs): '''set the initial concentrations [mg/L] of the CSTR.''' - Cs = np.zeros(len(self.components)) - cmpx = self.components.index - for k, v in kwargs.items(): Cs[cmpx(k)] = v - self._concs = Cs + self._concs = self.components.kwarray(kwargs) def _init_state(self): mixed = self._mixed From 2f97d2127f460e6f68a719de7e341fe6aaaefc8d Mon Sep 17 00:00:00 2001 From: RaiSaumitra <85169426+RaiSaumitra@users.noreply.github.com> Date: Wed, 17 Apr 2024 10:31:25 -0500 Subject: [PATCH 314/424] Removing rate equations Removing rate equations description from ADM1 processes since they are not entirely correct. --- .../data/process_data/_adm1_p_extension.tsv | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/qsdsan/data/process_data/_adm1_p_extension.tsv b/qsdsan/data/process_data/_adm1_p_extension.tsv index b4376c10..64b881ff 100644 --- a/qsdsan/data/process_data/_adm1_p_extension.tsv +++ b/qsdsan/data/process_data/_adm1_p_extension.tsv @@ -1,21 +1,21 @@ - S_su S_aa S_fa S_va S_bu S_pro S_ac S_h2 S_ch4 S_IC S_IN S_IP S_I X_ch X_pr X_li X_su X_aa X_fa X_c4 X_pro X_ac X_h2 X_I X_PHA X_PP X_PAO S_K S_Mg X_MeOH X_MeP -hydrolysis_carbs 1 ? ? ? -1 q_ch_hyd * X_ch -hydrolysis_proteins 1 ? ? ? -1 q_pr_hyd* X_pr -hydrolysis_lipids 1-f_fa_li f_fa_li ? ? ? -1 q_li_hyd * X_li -uptake_sugars -1 (1-Y_su)*f_bu_su (1-Y_su)*f_pro_su (1-Y_su)*f_ac_su (1-Y_su)*f_h2_su ? ? ? Y_su k_su * X_su * S_su/(K_su + S_su) -uptake_amino_acids -1 (1-Y_aa)*f_va_aa (1-Y_aa)*f_bu_aa (1-Y_aa)*f_pro_aa (1-Y_aa)*f_ac_aa (1-Y_aa)*f_h2_aa ? ? ? Y_aa k_aa * X_aa * S_aa/(K_aa + S_aa) -uptake_LCFA -1 (1-Y_fa)*f_ac_fa (1-Y_fa)*f_h2_fa ? ? ? Y_fa k_fa * X_fa * S_fa/(K_fa + S_fa) -uptake_valerate -1 (1-Y_c4)*f_pro_va (1-Y_c4)*f_ac_va (1-Y_c4)*f_h2_va ? ? ? Y_c4 k_c4 * X_c4 * S_va/(K_c4 + S_va) * S_va/(S_va + S_bu) -uptake_butyrate -1 (1-Y_c4)*f_ac_bu (1-Y_c4)*f_h2_bu ? ? ? Y_c4 k_c4 * X_c4 * S_bu/(K_c4 + S_bu) * S_bu/(S_va + S_bu) -uptake_propionate -1 (1-Y_pro)*f_ac_pro (1-Y_pro)*f_h2_pro ? ? ? Y_pro k_pro * X_pro * S_pro/(K_pro + S_pro) -uptake_acetate -1 1-Y_ac ? ? ? Y_ac k_ac * X_ac * S_ac/(K_ac + S_ac) -uptake_h2 -1 1-Y_h2 ? ? ? Y_h2 k_h2 * X_h2 * S_h2/(K_h2 + S_h2) -decay_Xsu ? ? ? f_sI_xb f_ch_xb f_pr_xb f_li_xb -1 f_xI_xb b_su * X_su -decay_Xaa ? ? ? f_sI_xb f_ch_xb f_pr_xb f_li_xb -1 f_xI_xb b_aa * X_aa -decay_Xfa ? ? ? f_sI_xb f_ch_xb f_pr_xb f_li_xb -1 f_xI_xb b_fa * X_fa -decay_Xc4 ? ? ? f_sI_xb f_ch_xb f_pr_xb f_li_xb -1 f_xI_xb b_c4 * X_c4 -decay_Xpro ? ? ? f_sI_xb f_ch_xb f_pr_xb f_li_xb -1 f_xI_xb b_pro * X_pro -decay_Xac ? ? ? f_sI_xb f_ch_xb f_pr_xb f_li_xb -1 f_xI_xb b_ac * X_ac -decay_Xh2 ? ? ? f_sI_xb f_ch_xb f_pr_xb f_li_xb -1 f_xI_xb b_h2 * X_h2 -lysis_XPAO ? ? ? f_sI_xb f_ch_xb f_pr_xb f_li_xb f_xI_xb -1 b_PAO * X_PAO -lysis_XPHA f_va_PHA f_bu_PHA f_pro_PHA f_ac_PHA ? ? ? -1 b_PHA * X_PHA \ No newline at end of file + S_su S_aa S_fa S_va S_bu S_pro S_ac S_h2 S_ch4 S_IC S_IN S_IP S_I X_ch X_pr X_li X_su X_aa X_fa X_c4 X_pro X_ac X_h2 X_I X_PHA X_PP X_PAO S_K S_Mg X_MeOH X_MeP +hydrolysis_carbs 1 ? ? ? -1 +hydrolysis_proteins 1 ? ? ? -1 +hydrolysis_lipids 1-f_fa_li f_fa_li ? ? ? -1 +uptake_sugars -1 (1-Y_su)*f_bu_su (1-Y_su)*f_pro_su (1-Y_su)*f_ac_su (1-Y_su)*f_h2_su ? ? ? Y_su +uptake_amino_acids -1 (1-Y_aa)*f_va_aa (1-Y_aa)*f_bu_aa (1-Y_aa)*f_pro_aa (1-Y_aa)*f_ac_aa (1-Y_aa)*f_h2_aa ? ? ? Y_aa +uptake_LCFA -1 (1-Y_fa)*f_ac_fa (1-Y_fa)*f_h2_fa ? ? ? Y_fa +uptake_valerate -1 (1-Y_c4)*f_pro_va (1-Y_c4)*f_ac_va (1-Y_c4)*f_h2_va ? ? ? Y_c4 +uptake_butyrate -1 (1-Y_c4)*f_ac_bu (1-Y_c4)*f_h2_bu ? ? ? Y_c4 +uptake_propionate -1 (1-Y_pro)*f_ac_pro (1-Y_pro)*f_h2_pro ? ? ? Y_pro +uptake_acetate -1 1-Y_ac ? ? ? Y_ac +uptake_h2 -1 1-Y_h2 ? ? ? Y_h2 +decay_Xsu ? ? ? f_sI_xb f_ch_xb f_pr_xb f_li_xb -1 f_xI_xb +decay_Xaa ? ? ? f_sI_xb f_ch_xb f_pr_xb f_li_xb -1 f_xI_xb +decay_Xfa ? ? ? f_sI_xb f_ch_xb f_pr_xb f_li_xb -1 f_xI_xb +decay_Xc4 ? ? ? f_sI_xb f_ch_xb f_pr_xb f_li_xb -1 f_xI_xb +decay_Xpro ? ? ? f_sI_xb f_ch_xb f_pr_xb f_li_xb -1 f_xI_xb +decay_Xac ? ? ? f_sI_xb f_ch_xb f_pr_xb f_li_xb -1 f_xI_xb +decay_Xh2 ? ? ? f_sI_xb f_ch_xb f_pr_xb f_li_xb -1 f_xI_xb +lysis_XPAO ? ? ? f_sI_xb f_ch_xb f_pr_xb f_li_xb f_xI_xb -1 +lysis_XPHA f_va_PHA f_bu_PHA f_pro_PHA f_ac_PHA ? ? ? -1 \ No newline at end of file From 7cdb3de035f0cb965d9e88237ff7c5c02c991dda Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Thu, 18 Apr 2024 10:32:54 -0700 Subject: [PATCH 315/424] Update _anaerobic_reactor.py --- qsdsan/sanunits/_anaerobic_reactor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qsdsan/sanunits/_anaerobic_reactor.py b/qsdsan/sanunits/_anaerobic_reactor.py index 567fa6f0..a7d95488 100644 --- a/qsdsan/sanunits/_anaerobic_reactor.py +++ b/qsdsan/sanunits/_anaerobic_reactor.py @@ -474,7 +474,7 @@ def _update_state(self): gas.state[:n_cmps] = gas.state[:n_cmps] * chem_MW / i_mass * 1e3 # i.e., M biogas to mg (measured_unit) / L def _update_dstate(self): - self._tempstate = self.model.rate_function._params['root'].data.copy() + # self._tempstate = self.model.rate_function._params['root'].data.copy() dy = self._dstate f_rtn = self._f_retain n_cmps = len(self.components) From 03599d3f6b7d125e22f6853da09ec838229ea24b Mon Sep 17 00:00:00 2001 From: Yalin Date: Fri, 19 Apr 2024 17:12:44 -0400 Subject: [PATCH 316/424] temporarily disable BSM2 primary clarifier doctest --- qsdsan/sanunits/_clarifier.py | 50 +---------------------------------- 1 file changed, 1 insertion(+), 49 deletions(-) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index 0093d84b..6b7ec213 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -917,55 +917,7 @@ class PrimaryClarifierBSM2(SanUnit): >>> uf, of = PC.outs >>> uf.imass['X_OHO']/ws.imass['X_OHO'] # doctest: +ELLIPSIS 0.280... - >>> PC.show() - PrimaryClarifierBSM2: PC - ins... - [0] ws - phase: 'l', T: 298.15 K, P: 101325 Pa - flow (g/hr): S_F 1e+04 - S_NH4 2e+04 - X_OHO 1.5e+04 - H2O 1e+06 - WasteStream-specific properties: - pH : 7.0 - COD : 23873.0 mg/L - BOD : 14963.2 mg/L - TC : 8298.3 mg/L - TOC : 8298.3 mg/L - TN : 20363.2 mg/L - TP : 367.6 mg/L - TK : 68.3 mg/L - outs... - [0] eff - phase: 'l', T: 298.15 K, P: 101325 Pa - flow (g/hr): S_F 70 - S_NH4 140 - X_OHO 4.2e+03 - H2O 7e+03 - WasteStream-specific properties: - pH : 7.0 - COD : 428873.3 mg/L - BOD : 244072.7 mg/L - TC : 156644.5 mg/L - TOC : 156644.5 mg/L - TN : 43073.0 mg/L - TP : 8085.4 mg/L - TK : 2011.4 mg/L - [1] sludge - phase: 'l', T: 298.15 K, P: 101325 Pa - flow (g/hr): S_F 9.93e+03 - S_NH4 1.99e+04 - X_OHO 1.08e+04 - H2O 9.93e+05 - WasteStream-specific properties: - pH : 7.0 - COD : 19982.3 mg/L - BOD : 12762.2 mg/L - TC : 6873.2 mg/L - TOC : 6873.2 mg/L - TN : 20145.0 mg/L - TP : 293.5 mg/L - TK : 49.6 mg/L + >>> # PC.show() References ---------- From adac018cc8b52f4042e8b4ecb0b51241f414f64b Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Mon, 22 Apr 2024 09:05:57 -0700 Subject: [PATCH 317/424] consolidate CSTR ODEs & minor speed-up --- qsdsan/sanunits/__init__.py | 11 ++- qsdsan/sanunits/_clarifier.py | 87 +++++++++++-------- .../sanunits/_suspended_growth_bioreactor.py | 12 +-- 3 files changed, 65 insertions(+), 45 deletions(-) diff --git a/qsdsan/sanunits/__init__.py b/qsdsan/sanunits/__init__.py index c8559ecc..37469a1c 100644 --- a/qsdsan/sanunits/__init__.py +++ b/qsdsan/sanunits/__init__.py @@ -32,7 +32,16 @@ Please refer to https://github.com/QSD-Group/QSDsan/blob/main/LICENSE.txt for license details. ''' - +# %% +from numba import njit +@njit(cache=True) +def dydt_cstr(QC_ins, QC, V, _dstate): + Q_ins = QC_ins[:, -1] + C_ins = QC_ins[:, :-1] + _dstate[-1] = 0 + _dstate[:-1] = (Q_ins @ C_ins - sum(Q_ins)*QC[:-1])/V + +#%% # **NOTE** PLEASE ORDER THE MODULES ALPHABETICALLY # # Units that do not rely on other units diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index 6b7ec213..218f0a20 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -17,10 +17,12 @@ from numpy import maximum as npmax, minimum as npmin, exp as npexp from warnings import warn +from numba import njit from .. import SanUnit, WasteStream import numpy as np from ..sanunits import WWTpump from ..sanunits._pumping import default_F_BM as default_WWTpump_F_BM +from ..sanunits import dydt_cstr __all__ = ('FlatBottomCircularClarifier', 'IdealClarifier', @@ -52,12 +54,19 @@ default_F_BM.update(default_WWTpump_F_BM) #%% Takács Clarifer - +@njit(cache=True) def _settling_flux(X, v_max, v_max_practical, X_min, rh, rp, n0): X_star = npmax(X-X_min, n0) v = npmin(v_max_practical, v_max*(npexp(-rh*X_star) - npexp(-rp*X_star))) return X*npmax(v, n0) +# from math import exp +# def _settling_flux(X, v_max, v_max_practical, X_min, rh, rp, n0): +# X_star = max(X-X_min, 0) +# v = min(v_max_practical, v_max*(exp(-rh*X_star) - exp(-rp*X_star))) +# return X*max(v, 0) + + class FlatBottomCircularClarifier(SanUnit): """ A flat-bottom circular clarifier with a simple 1-dimensional @@ -374,6 +383,8 @@ def _update_state(self): arr = self._state x = self.components.x n = self._N_layer + # arr[-(1+n)] = Q_in = self._ins_QC[0, -1] + # Q_e = Q_in - self._Qras - self._Qwas Q_e = arr[-(1+n)] - self._Qras - self._Qwas Z = arr[:len(x)] inf, = self.ins @@ -472,22 +483,28 @@ def _compile_ODE(self): settle_in = nzeros.copy() # Make these constants into arrays so it'll be faster in `dy_dt` - vmax_arr = np.full_like(nzeros, self._v_max) - vmaxp_arr = np.full_like(nzeros, self._v_max_p) - rh_arr = np.full_like(nzeros, self._rh) - rp_arr = np.full_like(nzeros, self._rp) + # vmax_arr = np.full_like(nzeros, self._v_max) + # vmaxp_arr = np.full_like(nzeros, self._v_max_p) + # rh_arr = np.full_like(nzeros, self._rh) + # rp_arr = np.full_like(nzeros, self._rp) + vmax_arr = self._v_max + vmaxp_arr = self._v_max_p + rh_arr = self._rh + rp_arr = self._rp func_vx = lambda x_arr, xmin_arr : _settling_flux(x_arr, vmax_arr, vmaxp_arr, xmin_arr, rh_arr, rp_arr, nzeros) - + A, hj, V = self._A, self._hj, self._V - A_arr = np.full_like(nzeros, A) - hj_arr = np.full_like(nzeros, hj) + # A_arr = np.full_like(nzeros, A) + # hj_arr = np.full_like(nzeros, hj) J = np.zeros(n-1) - X_t_arr = np.full(jf, self._X_t) - Q_in_arr = np.zeros(m) - V_arr = np.full(m, V) + # X_t_arr = np.full(jf, self._X_t) + X_t = self._X_t + # Q_in_arr = np.zeros(m) + # V_arr = np.full(m, V) def dy_dt(t, QC_ins, QC, dQC_ins): - dQC[-(n+1)] = dQC_ins[0,-1] + # dQC[-(n+1)] = dQC_ins[0,-1] + dQC[-(n+1)] = 0. Q_in = QC_ins[0,-1] Q_e = Q_in - Q_s C_in = QC_ins[0,:-1] @@ -509,14 +526,16 @@ def dy_dt(t, QC_ins, QC, dQC_ins): flow_in = X_rolled * Q_jout VX = func_vx(X, X_min_arr) J[:] = npmin(VX[:-1], VX[1:]) - condition = (X_rolled[:jf] 0.25*D['Clarifier diameter']: @@ -891,16 +910,21 @@ class PrimaryClarifierBSM2(SanUnit): Influent to the clarifier. Expected number of influent is 3. outs : class:`WasteStream` Sludge (uf) and treated effluent (of). - HRT : float - Hydraulic retention time in days. The default is 0.04268 days, based on IWA report.[1] + volume : float, optional + Clarifier volume, in m^3. The default is 900. ratio_uf : float - The ratio of sludge to primary influent. The default is 0.007, based on IWA report.[1] + The volumetric ratio of sludge to primary influent. The default is 0.007, + based on IWA report.[1] + mean_f_x : float, optional + The average fraction of particulate COD out of total COD in primary influent. + The default is 0.85. f_corr : float - Dimensionless correction factor for removal efficiency in the primary clarifier.[1] + Dimensionless correction factor for removal efficiency in the primary clarifier.[1] cylindrical_depth : float, optional The depth of the cylindrical portion of clarifier [in m]. upflow_velocity : float, optional - Speed with which influent enters the center feed of the clarifier [m/hr]. The default is 43.2. + Speed with which influent enters the center feed of the clarifier [m/hr]. + The default is 43.2. F_BM : dict Equipment bare modules. @@ -912,11 +936,12 @@ class PrimaryClarifierBSM2(SanUnit): >>> set_thermo(cmps_test) >>> ws = WasteStream('ws', S_F = 10, S_NH4 = 20, X_OHO = 15, H2O=1000) >>> from qsdsan.sanunits import PrimaryClarifierBSM2 - >>> PC = PrimaryClarifierBSM2(ID='PC', ins= (ws,), outs=('eff', 'sludge')) + >>> PC = PrimaryClarifierBSM2(ID='PC', ins= (ws,), outs=('eff', 'sludge'), + isdynamic=False) >>> PC.simulate() - >>> uf, of = PC.outs + >>> of, uf = PC.outs >>> uf.imass['X_OHO']/ws.imass['X_OHO'] # doctest: +ELLIPSIS - 0.280... + 0.598... >>> # PC.show() References @@ -1123,16 +1148,8 @@ def _compile_ODE(self): V = self.V t_m = self.t_m def dy_dt(t, QC_ins, QC, dQC_ins): - Q_ins = QC_ins[:, -1] - C_ins = QC_ins[:, :-1] - # dQ_ins = dQC_ins[:, -1] - # dC_ins = dQC_ins[:, :-1] - Q_in = Q_ins.sum() - C_in = Q_ins @ C_ins / Q_in - C = QC[:-1] - Q = QC[-1] - _dstate[:-1] = Q_in*(C_in - C)/V - _dstate[-1] = (Q_in-Q)/t_m + dydt_cstr(QC_ins, QC, V, _dstate) + _dstate[-1] = (sum(QC_ins[:,-1])-QC[-1])/t_m _update_parameters() _update_dstate() self._ODE = dy_dt diff --git a/qsdsan/sanunits/_suspended_growth_bioreactor.py b/qsdsan/sanunits/_suspended_growth_bioreactor.py index 7b88e73c..955807b6 100644 --- a/qsdsan/sanunits/_suspended_growth_bioreactor.py +++ b/qsdsan/sanunits/_suspended_growth_bioreactor.py @@ -12,18 +12,18 @@ from .. import SanUnit, WasteStream, Process, Processes, CompiledProcesses from ._clarifier import _settling_flux +from ..sanunits import dydt_cstr from sympy import symbols, lambdify, Matrix from scipy.integrate import solve_ivp from warnings import warn from math import floor, ceil import numpy as np import pandas as pd -from numba import njit +# from numba import njit __all__ = ('CSTR', 'BatchExperiment', 'SBR', - 'dydt_cstr' ) def _add_aeration_to_growth_model(aer, model): @@ -36,13 +36,7 @@ def _add_aeration_to_growth_model(aer, model): processes.compile() return processes -# %% -@njit(cache=True) -def dydt_cstr(QC_ins, QC, V, _dstate): - Q_ins = QC_ins[:, -1] - C_ins = QC_ins[:, :-1] - _dstate[-1] = 0 - _dstate[:-1] = (Q_ins @ C_ins - sum(Q_ins)*QC[:-1])/V + #%% class CSTR(SanUnit): From 0e7abfffc1886d1897ddb7a930a4cf6448d343fa Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Tue, 23 Apr 2024 13:18:11 -0700 Subject: [PATCH 318/424] allow redefine WasteStream pH --- qsdsan/_waste_stream.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/qsdsan/_waste_stream.py b/qsdsan/_waste_stream.py index 3f597fbd..0def02aa 100644 --- a/qsdsan/_waste_stream.py +++ b/qsdsan/_waste_stream.py @@ -452,15 +452,16 @@ def _wastestream_info(self, details=True, concentrations=None, N=15): _ws_info += int(bool(self.pH))*f' pH : {self.pH:.1f}\n' _ws_info += int(bool(self.SAlk))*f' Alkalinity : {self.SAlk:.1f} mg/L\n' if details: - _ws_info += int(bool(self.COD)) *f' COD : {self.COD:.1f} mg/L\n' - _ws_info += int(bool(self.BOD)) *f' BOD : {self.BOD:.1f} mg/L\n' - _ws_info += int(bool(self.TC)) *f' TC : {self.TC:.1f} mg/L\n' - _ws_info += int(bool(self.TOC)) *f' TOC : {self.TOC:.1f} mg/L\n' - _ws_info += int(bool(self.TN)) *f' TN : {self.TN:.1f} mg/L\n' + _ws_info += int(bool(self.COD)) *f' COD : {self.COD:.1f} mg/L\n' + _ws_info += int(bool(self.BOD)) *f' BOD : {self.BOD:.1f} mg/L\n' + _ws_info += int(bool(self.TC)) *f' TC : {self.TC:.1f} mg/L\n' + _ws_info += int(bool(self.TOC)) *f' TOC : {self.TOC:.1f} mg/L\n' + _ws_info += int(bool(self.TN)) *f' TN : {self.TN:.1f} mg/L\n' # `TKN` not included as the users need to define that to include in TKN calculation # _ws_info += int(bool(self.TKN)) *f' TKN : {self.TKN:.1f} mg/L\n' - _ws_info += int(bool(self.TP)) *f' TP : {self.TP:.1f} mg/L\n' - _ws_info += int(bool(self.TK)) *f' TK : {self.TK:.1f} mg/L\n' + _ws_info += int(bool(self.TP)) *f' TP : {self.TP:.1f} mg/L\n' + _ws_info += int(bool(self.TK)) *f' TK : {self.TK:.1f} mg/L\n' + _ws_info += int(bool(self.get_TSS())) *f' TSS : {self.get_TSS():.1f} mg/L\n' # _ws_info += int(bool(self.charge))*f' charge : {self.charge:.1f} mmol/L\n' else: _ws_info += ' ...\n' @@ -729,6 +730,10 @@ def _liq_sol_properties(self, prop, value): def pH(self): '''[float] pH, unitless.''' return self._liq_sol_properties('pH', 7.) + @pH.setter + def pH(self, ph): + if self.phase != 'g': + self._pH = ph @property def SAlk(self): From 1c474a4ebefa22a2ea050c486e92d1acd7b857d1 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Tue, 23 Apr 2024 13:18:38 -0700 Subject: [PATCH 319/424] updates downstream pH during dynamic simulation --- qsdsan/sanunits/_anaerobic_reactor.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/qsdsan/sanunits/_anaerobic_reactor.py b/qsdsan/sanunits/_anaerobic_reactor.py index a7d95488..94fd56be 100644 --- a/qsdsan/sanunits/_anaerobic_reactor.py +++ b/qsdsan/sanunits/_anaerobic_reactor.py @@ -291,7 +291,7 @@ def __init__(self, ID='', ins=None, outs=(), thermo=None, self._f_retain = np.array([fraction_retain if cmp.ID in retain_cmps \ else 0 for cmp in self.components]) self._mixed = WasteStream() - self._tempstate = [] + self._tempstate = {} def ideal_gas_law(self, p=None, S=None): '''Calculates partial pressure [bar] given concentration [M] at @@ -450,6 +450,7 @@ def _update_state(self): chem_MW = self.components.chem_MW n_cmps = len(self.components) Cs = y[:n_cmps]*(1-f_rtn)*1e3 # kg/m3 to mg/L + pH = self._tempstate.pop('pH', 7.2631) if self.split is None: gas, liquid = self._outs if liquid.state is None: @@ -457,6 +458,7 @@ def _update_state(self): else: liquid.state[:n_cmps] = Cs liquid.state[-1] = y[-1] + liquid._pH = pH else: gas = self._outs[0] liquids = self._outs[1:] @@ -465,7 +467,8 @@ def _update_state(self): liquid.state = np.append(Cs, y[-1]*spl) else: liquid.state[:n_cmps] = Cs - liquid.state[-1] = y[-1]*spl + liquid.state[-1] = y[-1]*spl + liquid._pH = pH if gas.state is None: gas.state = np.zeros(n_cmps+1) gas.state[self._gas_cmp_idx] = y[n_cmps:(n_cmps + self._n_gas)] @@ -474,7 +477,7 @@ def _update_state(self): gas.state[:n_cmps] = gas.state[:n_cmps] * chem_MW / i_mass * 1e3 # i.e., M biogas to mg (measured_unit) / L def _update_dstate(self): - # self._tempstate = self.model.rate_function._params['root'].data.copy() + self._tempstate = self.model.rate_function._params['root'].data.copy() dy = self._dstate f_rtn = self._f_retain n_cmps = len(self.components) @@ -550,6 +553,7 @@ def M_stoichio(state_arr): _M_stoichio = self.model.stoichio_eval().T M_stoichio = lambda state_arr: _M_stoichio def dy_dt(t, QC_ins, QC, dQC_ins): + QC[QC < 2.2e-16] = 0. S_liq = QC[:n_cmps] S_gas = QC[n_cmps: (n_cmps+n_gas)] #!!! Volume change due to temperature difference accounted for From 6f5a5186c1ed791d685b702ebf4cd333f4ed65d5 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Tue, 23 Apr 2024 13:19:05 -0700 Subject: [PATCH 320/424] minor bug fix --- qsdsan/sanunits/_junction.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qsdsan/sanunits/_junction.py b/qsdsan/sanunits/_junction.py index 3ca5bbfa..6a6a91ff 100644 --- a/qsdsan/sanunits/_junction.py +++ b/qsdsan/sanunits/_junction.py @@ -19,7 +19,7 @@ from warnings import warn from math import isclose from biosteam.units import Junction as BSTjunction -from .. import SanUnit, processes as pc +from .. import Stream, SanUnit, processes as pc __all__ = ( 'Junction', @@ -63,7 +63,7 @@ class Junction(SanUnit): def __init__(self, ID='', upstream=None, downstream=(), thermo=None, init_with='WasteStream', F_BM_default=None, isdynamic=False, reactions=None, **kwargs): - thermo = downstream.thermo if downstream else thermo + thermo = downstream.thermo if isinstance(downstream, Stream) else thermo SanUnit.__init__(self, ID, ins=upstream, outs=downstream, thermo=thermo, init_with=init_with, F_BM_default=F_BM_default, isdynamic=isdynamic, From 121c3c1f61d1c14aee00b3d9c1a2b9aaecba1a2d Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Tue, 23 Apr 2024 13:19:34 -0700 Subject: [PATCH 321/424] Update _clarifier.py --- qsdsan/sanunits/_clarifier.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index 218f0a20..48c74b27 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -383,9 +383,9 @@ def _update_state(self): arr = self._state x = self.components.x n = self._N_layer - # arr[-(1+n)] = Q_in = self._ins_QC[0, -1] - # Q_e = Q_in - self._Qras - self._Qwas - Q_e = arr[-(1+n)] - self._Qras - self._Qwas + arr[-(1+n)] = Q_in = self._ins_QC[0, -1] + Q_e = Q_in - self._Qras - self._Qwas + # Q_e = arr[-(1+n)] - self._Qras - self._Qwas Z = arr[:len(x)] inf, = self.ins imass = self.components.i_mass From fea7d31852c0ccad4163113cdea654b08d4e3413 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Tue, 23 Apr 2024 14:27:17 -0700 Subject: [PATCH 322/424] minor bug fix --- qsdsan/processes/_adm1.py | 6 ++++-- qsdsan/sanunits/_anaerobic_reactor.py | 2 +- qsdsan/sanunits/_junction.py | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/qsdsan/processes/_adm1.py b/qsdsan/processes/_adm1.py index 779bba30..b6f0e5f8 100644 --- a/qsdsan/processes/_adm1.py +++ b/qsdsan/processes/_adm1.py @@ -36,8 +36,10 @@ # ADM1-specific components # ============================================================================= -C_mw = get_mw({'C':1}) -N_mw = get_mw({'N':1}) +# C_mw = get_mw({'C':1}) +# N_mw = get_mw({'N':1}) +C_mw = 12 +N_mw = 14 def create_adm1_cmps(set_thermo=True): cmps_all = Components.load_default() diff --git a/qsdsan/sanunits/_anaerobic_reactor.py b/qsdsan/sanunits/_anaerobic_reactor.py index 94fd56be..3985e80b 100644 --- a/qsdsan/sanunits/_anaerobic_reactor.py +++ b/qsdsan/sanunits/_anaerobic_reactor.py @@ -450,7 +450,7 @@ def _update_state(self): chem_MW = self.components.chem_MW n_cmps = len(self.components) Cs = y[:n_cmps]*(1-f_rtn)*1e3 # kg/m3 to mg/L - pH = self._tempstate.pop('pH', 7.2631) + pH = self._tempstate.pop('pH', 7) if self.split is None: gas, liquid = self._outs if liquid.state is None: diff --git a/qsdsan/sanunits/_junction.py b/qsdsan/sanunits/_junction.py index 6a6a91ff..23acdc69 100644 --- a/qsdsan/sanunits/_junction.py +++ b/qsdsan/sanunits/_junction.py @@ -322,7 +322,7 @@ def pKa(self): ('H+', 'OH-'), ('NH4+', 'NH3'), ('CO2', 'HCO3-'), ('HAc', 'Ac-'), ('HPr', 'Pr-'), ('HBu', 'Bu-'), ('HVa', 'Va-') ''' - return self.pKa_base-np.log10(np.exp(pc.T_correction_factor(self.T_base, self.T, self.Ka_dH))) + return self.pKa_base-np.log10(pc.T_correction_factor(self.T_base, self.T, self.Ka_dH)) @property def alpha_IC(self): @@ -438,7 +438,7 @@ def pKa(self): ('H+', 'OH-'), ('NH4+', 'NH3'), ('H3PO4', 'H2PO4 2-'), ('CO2', 'HCO3-'), ('HAc', 'Ac-'), ('HPr', 'Pr-'), ('HBu', 'Bu-'), ('HVa', 'Va-') ''' - return self.pKa_base-np.log10(np.exp(pc.T_correction_factor(self.T_base, self.T, self.Ka_dH))) + return self.pKa_base-np.log10(pc.T_correction_factor(self.T_base, self.T, self.Ka_dH)) @property def alpha_IN(self): From f316d8a6347c0e6f2eeb6261e3fd84e2d4061cec Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Tue, 23 Apr 2024 15:26:41 -0700 Subject: [PATCH 323/424] maintain pH during SanUnit init --- qsdsan/_sanunit.py | 3 ++- qsdsan/sanunits/_junction.py | 33 ++++++++++++++++++--------------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/qsdsan/_sanunit.py b/qsdsan/_sanunit.py index 12c43a25..2f42b748 100644 --- a/qsdsan/_sanunit.py +++ b/qsdsan/_sanunit.py @@ -269,7 +269,8 @@ def _convert_stream(self, strm_inputs, streams, init_with, ins_or_outs): elif v == 'ss': converted.append(SanStream.from_stream(stream=s)) else: - converted.append(WasteStream.from_stream(stream=s)) + if isa(s, WasteStream): converted.append(s) + else: converted.append(WasteStream.from_stream(stream=s)) diff = len(converted) + len(missing) - len(streams) if diff != 0: diff --git a/qsdsan/sanunits/_junction.py b/qsdsan/sanunits/_junction.py index 23acdc69..beb9225f 100644 --- a/qsdsan/sanunits/_junction.py +++ b/qsdsan/sanunits/_junction.py @@ -261,6 +261,8 @@ class ADMjunction(Junction): _parse_reactions = Junction._no_parse_reactions rtol = 1e-2 atol = 1e-6 + # Should be constants + cod_vfa = np.array([64, 112, 160, 208]) def __init__(self, ID='', upstream=None, downstream=(), thermo=None, init_with='WasteStream', F_BM_default=None, isdynamic=False, @@ -338,6 +340,11 @@ def alpha_IN(self): pKa_IN = self.pKa[1] return 10**(pKa_IN-pH)/(1+10**(pKa_IN-pH))/14 + @property + def alpha_vfa(self): + '''[float] charge per g of VFA-COD.''' + return 1.0/self.cod_vfa*(-1.0/(1.0 + 10**(self.pKa[3:]-self.pH))) + def _compile_AE(self): _state = self._state _dstate = self._dstate @@ -481,8 +488,9 @@ def yt(t, QC_ins, dQC_ins): _update_dstate() self._AE = yt - -# %% + + +#%% class ADMtoASM(ADMjunction): ''' @@ -522,9 +530,6 @@ class ADMtoASM(ADMjunction): # User defined values bio_to_xs = 0.7 - # Should be constants - cod_vfa = np.array([64, 112, 160, 208]) - # whether to conserve the nitrogen split between soluble and particulate components conserve_particulate_N = False @@ -604,9 +609,6 @@ def _compile_reactions(self): asm_X_P_i_N = cmps_asm.X_P.i_N asm_ions_idx = cmps_asm.indices(('S_NH', 'S_ALK')) - alpha_IN = self.alpha_IN - alpha_IC = self.alpha_IC - alpha_vfa = self.alpha_vfa f_corr = self.balance_cod_tkn conserve_particulate_N = self.conserve_particulate_N @@ -724,6 +726,9 @@ def adm2asm(adm_vals): asm_vals = f_corr(adm_vals, asm_vals) # Step 5: charge balance for alkalinity + alpha_IN = self.alpha_IN + alpha_IC = self.alpha_IC + alpha_vfa = self.alpha_vfa S_NH = asm_vals[asm_ions_idx[0]] S_ALK = (sum(_ions * np.append([alpha_IN, alpha_IC], alpha_vfa)) - S_NH/14)*(-12) asm_vals[asm_ions_idx[1]] = S_ALK @@ -731,10 +736,8 @@ def adm2asm(adm_vals): return asm_vals self._reactions = adm2asm - - @property - def alpha_vfa(self): - return 1.0/self.cod_vfa*(-1.0/(1.0 + 10**(self.pKa[3:]-self.pH))) + + # %% @@ -864,9 +867,6 @@ def _compile_reactions(self): adm_ions_idx = cmps_adm.indices(['S_IN', 'S_IC', 'S_cat', 'S_an']) frac_deg = self.frac_deg - alpha_IN = self.alpha_IN - alpha_IC = self.alpha_IC - proton_charge = 10**(-self.pKa[0]+self.pH) - 10**(-self.pH) # self.pKa[0] is pKw f_corr = self.balance_cod_tkn def asm2adm(asm_vals): @@ -997,6 +997,9 @@ def asm2adm(asm_vals): adm_vals = f_corr(asm_vals, adm_vals) # Step 7: charge balance + alpha_IN = self.alpha_IN + alpha_IC = self.alpha_IC + proton_charge = 10**(-self.pKa[0]+self.pH) - 10**(-self.pH) # self.pKa[0] is pKw asm_charge_tot = _snh/14 - _sno/14 - _salk/12 #!!! charge balance should technically include VFAs, # but VFAs concentrations are assumed zero per previous steps?? From 9af9b062fe160bead4c4e4ccc8dff6436d49ed4c Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Wed, 24 Apr 2024 16:26:03 -0700 Subject: [PATCH 324/424] update thickener_factor in _run iterations --- qsdsan/sanunits/_sludge_treatment.py | 208 +++++++++++++-------------- 1 file changed, 98 insertions(+), 110 deletions(-) diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py index dfe27fc7..0fd39147 100644 --- a/qsdsan/sanunits/_sludge_treatment.py +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -40,6 +40,24 @@ default_F_BM.update(default_WWTpump_F_BM) #%% Thickener + +def calc_f_thick(thickener_perc, TSS_in): + if TSS_in > 0: + thickener_factor = thickener_perc*10000/TSS_in + if thickener_factor < 1: thickener_factor = 1 + return thickener_factor + else: + raise ValueError(f'Influent TSS is not valid: ({TSS_in:.2f} mg/L).') + +def calc_f_Qu_thin(TSS_removal_perc, thickener_factor): + if thickener_factor <= 1: + Qu_factor = 1 + thinning_factor=0 + else: + Qu_factor = TSS_removal_perc/(100*thickener_factor) + thinning_factor = (1 - TSS_removal_perc/100)/(1 - Qu_factor) + return Qu_factor, thinning_factor + class Thickener(SanUnit): """ @@ -145,7 +163,7 @@ class Thickener(SanUnit): """ _N_ins = 1 - _N_outs = 2 + _N_outs = 2 # [0] thickened sludge, [1] reject water _ins_size_is_fixed = False _outs_size_is_fixed = False @@ -176,7 +194,7 @@ def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, @property def thickener_perc(self): - '''tp is the percentage of Suspended Sludge in the underflow of the thickener''' + '''The percentage of suspended solids in the thickened sludge, in %.''' return self._tp @thickener_perc.setter @@ -216,73 +234,47 @@ def TSS_removal_perc(self, TSS_rmv): @property def thickener_factor(self): - if self._thickener_factor is None: - self._mixed.mix_from(self.ins) - inf = self._mixed - _cal_thickener_factor = self._cal_thickener_factor - if not self.ins: return - elif inf.isempty(): return - else: - TSS_in = inf.get_TSS() - self._thickener_factor = _cal_thickener_factor(TSS_in) - return self._thickener_factor + inf = self._mixed + inf.mix_from(self.ins) + if not self.ins: return + elif inf.isempty(): return + else: + TSS_in = inf.get_TSS() + self._Qu_factor = None + return calc_f_thick(self._tp, TSS_in) @property def thinning_factor(self): - if self._thinning_factor is None: - self._Qu_factor, self._thinning_factor = self._cal_Qu_fthin(self.thickener_factor) - return self._thinning_factor - - def _cal_thickener_factor(self, TSS_in): - if TSS_in > 0: - thickener_factor = self._tp*10000/TSS_in - if thickener_factor < 1: thickener_factor = 1 - return thickener_factor - else: - raise ValueError(f'Influent TSS is not valid: ({TSS_in:.2f} mg/L).') - - def _cal_Qu_fthin(self, thickener_factor): - if thickener_factor<1: - Qu_factor = 1 - thinning_factor=0 - else: - Qu_factor = self._TSS_rmv/(100*thickener_factor) - thinning_factor = (1 - (self._TSS_rmv/100))/(1 - Qu_factor) - return Qu_factor, thinning_factor + f_Qu, f_thin = calc_f_Qu_thin(self.TSS_removal_perc, self.thickener_factor) + return f_thin + @property + def Qu_factor(self): + f_Qu, f_thin = calc_f_Qu_thin(self.TSS_removal_perc, self.thickener_factor) + return f_Qu + def _update_parameters(self): # Thickener_factor, Thinning_factor, and Qu_factor need to be # updated again and again. while dynamic simulations cmps = self.components TSS_in = np.sum(self._state[:-1]*cmps.i_mass*cmps.x) - self._thickener_factor = f_thick = self._cal_thickener_factor(TSS_in) - self._Qu_factor, self._thinning_factor = self._cal_Qu_fthin(f_thick) + self._f_thick = f_thick = calc_f_thick(self._tp, TSS_in) + self._f_Qu, self._f_thin = calc_f_Qu_thin(self._TSS_rmv, f_thick) def _run(self): - self._mixed.mix_from(self.ins) - inf = self._mixed - sludge, eff = self.outs - cmps = self.components + mixed = self._mixed + mixed.mix_from(self.ins) + x = self.components.x + uf, of = self.outs TSS_rmv = self._TSS_rmv - thinning_factor = self.thinning_factor - thickener_factor = self.thickener_factor - - # The following are splits by mass of particulates and solubles - - # Note: (1 - thinning_factor)/(thickener_factor - thinning_factor) = Qu_factor - Zs = (1 - thinning_factor)/(thickener_factor - thinning_factor)*inf.mass*cmps.s - Ze = (thickener_factor - 1)/(thickener_factor - thinning_factor)*inf.mass*cmps.s + TSS_in = mixed.get_TSS() + f_thick = calc_f_thick(self._tp, TSS_in) + f_Qu, f_thin = calc_f_Qu_thin(TSS_rmv, f_thick) - Xe = (1 - TSS_rmv/100)*inf.mass*cmps.x - Xs = (TSS_rmv/100)*inf.mass*cmps.x - - # e stands for effluent, s stands for sludge - Ce = Ze + Xe - Cs = Zs + Xs - - eff.set_flow(Ce,'kg/hr') - sludge.set_flow(Cs,'kg/hr') + if f_thick > 1: split_to_uf = (1-x)*f_Qu + x*TSS_rmv/100 + else: split_to_uf = 1 + mixed.split_to(uf, of, split_to_uf) def _init_state(self): @@ -301,64 +293,57 @@ def _init_state(self): def _update_state(self): '''updates conditions of output stream based on conditions of the Thickener''' + + thickener_factor = self._f_thick + thinning_factor = self._f_thin + Qu_factor = self._f_Qu + x = self.components.x - # This function is run multiple times during dynamic simulation - - # Remember that here we are updating the state array of size n, which is made up - # of component concentrations in the first (n-1) cells and the last cell is flowrate. - - # So, while in the run function the effluent and sludge are split by mass, - # here they are split by concentration. Therefore, the split factors are different. - - # Updated intrinsic modelling parameters are used for dynamic simulation - thickener_factor = self.thickener_factor - thinning_factor = self.thinning_factor - Qu_factor = self._Qu_factor - cmps = self.components - - # For sludge, the particulate concentrations are multipled by thickener factor, and - # flowrate is multiplied by Qu_factor. The soluble concentrations remains same. uf, of = self.outs - if uf.state is None: uf.state = np.zeros(len(cmps)+1) - uf.state[:-1] = self._state[:-1]*cmps.s*1 + self._state[:-1]*cmps.x*thickener_factor - uf.state[-1] = self._state[-1]*Qu_factor - - # For effluent, the particulate concentrations are multipled by thinning factor, and - # flowrate is multiplied by Qu_factor. The soluble concentrations remains same. - if of.state is None: of.state = np.zeros(len(cmps)+1) - of.state[:-1] = self._state[:-1]*cmps.s*1 + self._state[:-1]*cmps.x*thinning_factor - of.state[-1] = self._state[-1]*(1 - Qu_factor) + if uf.state is None: uf.state = np.zeros(len(self.components)+1) + if of.state is None: of.state = np.zeros(len(self.components)+1) + + arr = self._state + if thickener_factor <= 1: + uf.state[:] = arr + of.state[:] = 0. + else: + # For sludge, the particulate concentrations (x) are multipled by thickener factor, and + # flowrate is multiplied by Qu_factor. The soluble concentrations (1-x) remains same. + uf.state[:-1] = arr[:-1] * ((1-x) + x*thickener_factor) + uf.state[-1] = arr[-1] * Qu_factor + + # For effluent, the particulate concentrations (x) are multipled by thinning factor, and + # flowrate is multiplied by Qu_factor. The soluble concentrations (1-x) remains same. + of.state[:-1] = arr[:-1] * ((1-x) + x*thinning_factor) + of.state[-1] = arr[-1] * (1 - Qu_factor) def _update_dstate(self): '''updates rates of change of output stream from rates of change of the Thickener''' - # This function is run multiple times during dynamic simulation - - # Remember that here we are updating the state array of size n, which is made up - # of component concentrations in the first (n-1) cells and the last cell is flowrate. - - # So, while in the run function the effluent and sludge are split by mass, - # here they are split by concentration. Therefore, the split factors are different. - - # Updated intrinsic modelling parameters are used for dynamic simulation - thickener_factor = self.thickener_factor - thinning_factor = self.thinning_factor - Qu_factor = self._Qu_factor - cmps = self.components - - # For sludge, the particulate concentrations are multipled by thickener factor, and - # flowrate is multiplied by Qu_factor. The soluble concentrations remains same. + thickener_factor = self._f_thick + thinning_factor = self._f_thin + Qu_factor = self._f_Qu + x = self.components.x + uf, of = self.outs - if uf.dstate is None: uf.dstate = np.zeros(len(cmps)+1) - uf.dstate[:-1] = self._dstate[:-1]*cmps.s*1 + self._dstate[:-1]*cmps.x*thickener_factor - uf.dstate[-1] = self._dstate[-1]*Qu_factor - - # For effluent, the particulate concentrations are multipled by thinning factor, and - # flowrate is multiplied by Qu_factor. The soluble concentrations remains same. - if of.dstate is None: of.dstate = np.zeros(len(cmps)+1) - of.dstate[:-1] = self._dstate[:-1]*cmps.s*1 + self._dstate[:-1]*cmps.x*thinning_factor - of.dstate[-1] = self._dstate[-1]*(1 - Qu_factor) - + if uf.dstate is None: uf.dstate = np.zeros(len(self.components)+1) + if of.dstate is None: of.dstate = np.zeros(len(self.components)+1) + arr = self._dstate + if thickener_factor <= 1: + uf.dstate[:] = arr + of.dstate[:] = 0. + else: + # For sludge, the particulate concentrations are multipled by thickener factor, and + # flowrate is multiplied by Qu_factor. The soluble concentrations remains same. + uf.dstate[:-1] = arr[:-1] * ((1-x) + x*thickener_factor) + uf.dstate[-1] = arr[-1] * Qu_factor + + # For effluent, the particulate concentrations are multipled by thinning factor, and + # flowrate is multiplied by Qu_factor. The soluble concentrations remains same. + of.dstate[:-1] = arr[:-1] * ((1-x) + x*thinning_factor) + of.dstate[-1] = arr[-1] * (1 - Qu_factor) + @property def AE(self): if self._AE is None: @@ -697,12 +682,15 @@ class Centrifuge(Thickener): polymer_cost_by_weight = 2.2 # $/Kg (Source: https://www.alibaba.com/product-detail/dewatering-pool-chemicals-cationic-polyacrylamide-cas_1600194474507.html?spm=a2700.galleryofferlist.topad_classic.i5.5de8615c4zGAhg) def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, - init_with='WasteStream', F_BM_default=default_F_BM, thickener_perc=28, TSS_removal_perc=98, - solids_feed_rate = 70, g_factor=2500, rotational_speed = 2500, LtoD = 4, F_BM = default_F_BM, - polymer_dosage = 20, h_cylindrical=2, h_conical=1, **kwargs): + init_with='WasteStream', F_BM_default=default_F_BM, + thickener_perc=28, TSS_removal_perc=98, + solids_feed_rate=70, g_factor=2500, rotational_speed = 2500, + LtoD=4, F_BM=default_F_BM, + polymer_dosage=20, h_cylindrical=2, h_conical=1, **kwargs): Thickener.__init__(self, ID=ID, ins=ins, outs=outs, thermo=thermo, isdynamic=isdynamic, - init_with=init_with, F_BM_default=1, thickener_perc=thickener_perc, + init_with=init_with, F_BM_default=F_BM_default, + thickener_perc=thickener_perc, TSS_removal_perc=TSS_removal_perc, **kwargs) self.solids_feed_rate = solids_feed_rate From 76260c43a1060e7b30670865b081f7a611825f79 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Wed, 24 Apr 2024 16:31:44 -0700 Subject: [PATCH 325/424] add S_h2 algebraic solver --- qsdsan/processes/_adm1.py | 104 +++++++++++++++++++++++++------------- 1 file changed, 70 insertions(+), 34 deletions(-) diff --git a/qsdsan/processes/_adm1.py b/qsdsan/processes/_adm1.py index b6f0e5f8..2e57eb9f 100644 --- a/qsdsan/processes/_adm1.py +++ b/qsdsan/processes/_adm1.py @@ -181,25 +181,25 @@ def create_adm1_cmps(set_thermo=True): def non_compet_inhibit(Si, Ki): return Ki/(Ki+Si) +def grad_non_compet_inhibit(Si, Ki): + return -Ki/(Ki+Si)**2 + def substr_inhibit(Si, Ki): return Si/(Ki+Si) +def grad_substr_inhibit(Si, Ki): + return Ki/(Ki+Si)**2 + def mass2mol_conversion(cmps): '''conversion factor from kg[measured_as]/m3 to mol[component]/L''' return cmps.i_mass / cmps.chem_MW -# def T_correction_factor(T1, T2, theta): -# return np.exp(theta * (T2-T1)) - def T_correction_factor(T1, T2, delta_H): """compute temperature correction factor for equilibrium constants based on the Van't Holf equation.""" if T1 == T2: return 1 return np.exp(delta_H/(R*100) * (1/T1 - 1/T2)) # R converted to SI -# def calc_Kas(pKas, T_base, T_op, theta): -# pKas = np.asarray(pKas) -# return 10**(-pKas) * T_correction_factor(T_base, T_op, theta) def acid_base_rxn(h_ion, weak_acids_tot, Kas): # h, nh4, hco3, ac, pr, bu, va = mols @@ -220,8 +220,6 @@ def fprime_abr(h_ion, weak_acids_tot, Kas): def pH_inhibit(pH, ul, ll, lower_only=True): if lower_only: - # if pH >= ul: return 1 - # else: return exp(-3 * ((pH-ul)/(ul-ll))**2) low_by = np.minimum(pH-ul, 0) return np.exp(-3 * (low_by/(ul-ll))**2) else: @@ -235,11 +233,22 @@ def Hill_inhibit(H_ion, ul, ll): rhos = np.zeros(22) # 22 kinetic processes Cs = np.empty(19) -def rhos_adm1(state_arr, params): +def solve_pH(state_arr, Ka, unit_conversion): + cmps_in_M = state_arr[:27] * unit_conversion + weak_acids = cmps_in_M[[24, 25, 10, 9, 6, 5, 4, 3]] + h = brenth(acid_base_rxn, 1e-14, 1.0, + args=(weak_acids, Ka), + xtol=1e-12, maxiter=100) + nh3 = Ka[1] * weak_acids[2] / (Ka[1] + h) + co2 = weak_acids[3] - Ka[2] * weak_acids[3] / (Ka[2] + h) + return h, nh3, co2 + +rhos_adm1 = lambda state_arr, params: _rhos_adm1(state_arr, params, h=None) + +def _rhos_adm1(state_arr, params, h=None): ks = params['rate_constants'] Ks = params['half_sat_coeffs'] cmps = params['components'] - # n = len(cmps) pH_ULs = params['pH_ULs'] pH_LLs = params['pH_LLs'] KS_IN = params['KS_IN'] @@ -252,26 +261,20 @@ def rhos_adm1(state_arr, params): kLa = params['kLa'] T_base = params['T_base'] root = params['root'] + if 'unit_conv' in params: + unit_conversion = params['unit_conv'] + else: + unit_conversion = params['unit_conv'] = mass2mol_conversion(cmps) - # Cs_ids = cmps.indices(['X_c', 'X_ch', 'X_pr', 'X_li', 'X_su', 'X_aa', - # 'X_fa', 'X_c4', 'X_c4', 'X_pro', 'X_ac', 'X_h2', - # 'X_su', 'X_aa', 'X_fa', 'X_c4', 'X_pro', 'X_ac', 'X_h2']) - # Cs = state_arr[Cs_ids] Cs[:8] = state_arr[12:20] Cs[8:12] = state_arr[19:23] Cs[12:] = state_arr[16:23] - # substrates_ids = cmps.indices(['S_su', 'S_aa', 'S_fa', 'S_va', - # 'S_bu', 'S_pro', 'S_ac', 'S_h2']) - # substrates = state_arr[substrates_ids] + substrates = state_arr[:8] - # S_va, S_bu, S_h2, S_IN = state_arr[cmps.indices(['S_va', 'S_bu', 'S_h2', 'S_IN'])] - # S_va, S_bu, S_h2, S_ch4, S_IC, S_IN = state_arr[[3,4,7,8,9,10]] S_va, S_bu, S_h2, S_IN = state_arr[[3,4,7,10]] - unit_conversion = mass2mol_conversion(cmps) - cmps_in_M = state_arr[:27] * unit_conversion - weak_acids = cmps_in_M[[24, 25, 10, 9, 6, 5, 4, 3]] T_op = state_arr[-1] + # Ka, KH = T_corrected_params(T_op, params) if T_op == T_base: Ka = Kab KH = KHb / unit_conversion[7:10] @@ -288,8 +291,6 @@ def rhos_adm1(state_arr, params): biogas_S = state_arr[7:10].copy() biogas_p = R * T_op * state_arr[27:30] - # Kas = Kab * T_correction_factor(T_base, T_op, Ka_dH) - # KH = KHb * T_correction_factor(T_base, T_op, KH_dH) / unit_conversion[7:10] rhos[:-3] = ks * Cs Monod = substr_inhibit(substrates, Ks) @@ -297,13 +298,12 @@ def rhos_adm1(state_arr, params): if S_va > 0: rhos[7] *= 1/(1+S_bu/S_va) if S_bu > 0: rhos[8] *= 1/(1+S_va/S_bu) - h = brenth(acid_base_rxn, 1e-14, 1.0, - args=(weak_acids, Ka), - xtol=1e-12, maxiter=100) - # h = 10**(-7.46) - - nh3 = Ka[1] * weak_acids[2] / (Ka[1] + h) - co2 = weak_acids[3] - Ka[2] * weak_acids[3] / (Ka[2] + h) + if h is None: + h, nh3, co2 = solve_pH(state_arr, Ka, unit_conversion) + else: + nh3 = Ka[1] * S_IN * unit_conversion[10] / (Ka[1] + h) + S_IC = state_arr[9] * unit_conversion[9] + co2 = S_IC - Ka[2] * S_IC / (Ka[2] + h) biogas_S[-1] = co2 / unit_conversion[9] Iph = Hill_inhibit(h, pH_ULs, pH_LLs) @@ -312,8 +312,6 @@ def rhos_adm1(state_arr, params): Inh3 = non_compet_inhibit(nh3, KI_nh3) rhos[4:12] *= Iph * Iin rhos[6:10] *= Ih2 - # rhos[4:12] *= Hill_inhibit(h, pH_ULs, pH_LLs) * substr_inhibit(S_IN, KS_IN) - # rhos[6:10] *= non_compet_inhibit(S_h2, KIs_h2) rhos[10] *= Inh3 root.data = { 'pH':-np.log10(h), @@ -325,9 +323,47 @@ def rhos_adm1(state_arr, params): 'rhos':rhos[4:12].copy() } rhos[-3:] = kLa * (biogas_S - KH * biogas_p) - # print(rhos) return rhos +def dydt_Sh2_AD(S_h2, state_arr, h, params, f_stoichio, S_h2_in, V_liq): + state_arr[7] = S_h2 + Q = state_arr[30] + rxn = _rhos_adm1(state_arr, params, h=h) + stoichio = f_stoichio(state_arr) # should return the stoichiometric coefficients of S_h2 for all processes + return Q/V_liq*(S_h2_in - S_h2) + np.dot(rxn, stoichio) + +grad_rhos = np.zeros(5) +X_bio = np.zeros(5) +def grad_dydt_Sh2_AD(S_h2, state_arr, h, params, f_stoichio, V_liq): + state_arr[7] = S_h2 + ks = params['rate_constants'][[6,7,8,9,11]] + Ks = params['half_sat_coeffs'][6:10] + pH_ULs = params['pH_ULs'] + pH_LLs = params['pH_LLs'] + KS_IN = params['KS_IN'] + KIs_h2 = params['KIs_h2'] + kLa = params['kLa'] + + X_bio[:] = state_arr[[18,19,19,20,22]] + substrates = state_arr[2:6] + S_va, S_bu, S_IN = state_arr[[3,4,10]] + Iph = Hill_inhibit(h, pH_ULs, pH_LLs)[[2,3,4,5,7]] + Iin = substr_inhibit(S_IN, KS_IN) + gIh2 = grad_non_compet_inhibit(S_h2, KIs_h2) + + grad_rhos[:] = ks * X_bio * Iph * Iin + grad_rhos[:4] *= substr_inhibit(substrates, Ks) * gIh2 + if S_va > 0: rhos[1] *= 1/(1+S_bu/S_va) + if S_bu > 0: rhos[2] *= 1/(1+S_va/S_bu) + + K_h2 = params['half_sat_coeffs'][11] + grad_rhos[-1] *= grad_substr_inhibit(S_h2, K_h2) + stoichio = f_stoichio(state_arr) + + Q = state_arr[30] + return -Q/V_liq + np.dot(grad_rhos, stoichio[[6,7,8,9,11]]) + kLa*stoichio[-3] + + #%% # ============================================================================= # ADM1 class From 9fb865979c936feb8bc4e85fb908e9165e99f1be Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Wed, 24 Apr 2024 17:31:29 -0700 Subject: [PATCH 326/424] enable DEA solving with AnaerobicCSTR --- qsdsan/processes/_adm1.py | 16 +++++---- qsdsan/sanunits/_anaerobic_reactor.py | 48 ++++++++++++++++++++++----- qsdsan/sanunits/_sludge_treatment.py | 2 -- 3 files changed, 49 insertions(+), 17 deletions(-) diff --git a/qsdsan/processes/_adm1.py b/qsdsan/processes/_adm1.py index 2e57eb9f..d7cbfbd4 100644 --- a/qsdsan/processes/_adm1.py +++ b/qsdsan/processes/_adm1.py @@ -26,7 +26,9 @@ 'non_compet_inhibit', 'substr_inhibit', 'T_correction_factor', 'pH_inhibit', 'Hill_inhibit', - 'rhos_adm1') + 'rhos_adm1', + 'solve_pH', + 'dydt_Sh2_AD', 'grad_dydt_Sh2_AD') _path = ospath.join(data_path, 'process_data/_adm1.tsv') _load_components = settings.get_default_chemicals @@ -325,7 +327,7 @@ def _rhos_adm1(state_arr, params, h=None): rhos[-3:] = kLa * (biogas_S - KH * biogas_p) return rhos -def dydt_Sh2_AD(S_h2, state_arr, h, params, f_stoichio, S_h2_in, V_liq): +def dydt_Sh2_AD(S_h2, state_arr, h, params, f_stoichio, V_liq, S_h2_in): state_arr[7] = S_h2 Q = state_arr[30] rxn = _rhos_adm1(state_arr, params, h=h) @@ -334,10 +336,11 @@ def dydt_Sh2_AD(S_h2, state_arr, h, params, f_stoichio, S_h2_in, V_liq): grad_rhos = np.zeros(5) X_bio = np.zeros(5) -def grad_dydt_Sh2_AD(S_h2, state_arr, h, params, f_stoichio, V_liq): +def grad_dydt_Sh2_AD(S_h2, state_arr, h, params, f_stoichio, V_liq, S_h2_in): state_arr[7] = S_h2 ks = params['rate_constants'][[6,7,8,9,11]] - Ks = params['half_sat_coeffs'][6:10] + Ks = params['half_sat_coeffs'][2:6] + K_h2 = params['half_sat_coeffs'][7] pH_ULs = params['pH_ULs'] pH_LLs = params['pH_LLs'] KS_IN = params['KS_IN'] @@ -349,14 +352,13 @@ def grad_dydt_Sh2_AD(S_h2, state_arr, h, params, f_stoichio, V_liq): S_va, S_bu, S_IN = state_arr[[3,4,10]] Iph = Hill_inhibit(h, pH_ULs, pH_LLs)[[2,3,4,5,7]] Iin = substr_inhibit(S_IN, KS_IN) - gIh2 = grad_non_compet_inhibit(S_h2, KIs_h2) + grad_Ih2 = grad_non_compet_inhibit(S_h2, KIs_h2) grad_rhos[:] = ks * X_bio * Iph * Iin - grad_rhos[:4] *= substr_inhibit(substrates, Ks) * gIh2 + grad_rhos[:-1] *= substr_inhibit(substrates, Ks) * grad_Ih2 if S_va > 0: rhos[1] *= 1/(1+S_bu/S_va) if S_bu > 0: rhos[2] *= 1/(1+S_va/S_bu) - K_h2 = params['half_sat_coeffs'][11] grad_rhos[-1] *= grad_substr_inhibit(S_h2, K_h2) stoichio = f_stoichio(state_arr) diff --git a/qsdsan/sanunits/_anaerobic_reactor.py b/qsdsan/sanunits/_anaerobic_reactor.py index 3985e80b..06d47d20 100644 --- a/qsdsan/sanunits/_anaerobic_reactor.py +++ b/qsdsan/sanunits/_anaerobic_reactor.py @@ -18,10 +18,13 @@ from math import ceil, pi from biosteam import Stream from .. import SanUnit, Construction, WasteStream -from ..processes import Decay +from ..processes import Decay, T_correction_factor, solve_pH, \ + dydt_Sh2_AD, grad_dydt_Sh2_AD from ..sanunits import HXutility, WWTpump, CSTR from ..utils import ospath, load_data, data_path, auom, \ calculate_excavation_volume, ExogenousDynamicVariable as EDV +from scipy.optimize import newton + __all__ = ( 'AnaerobicBaffledReactor', 'AnaerobicCSTR', @@ -261,6 +264,7 @@ class AnaerobicCSTR(CSTR): _ins_size_is_fixed = False _outs_size_is_fixed = False _R = 8.3145e-2 # Universal gas constant, [bar/M/K] + algebraic_h2 = True def __init__(self, ID='', ins=None, outs=(), thermo=None, init_with='WasteStream', V_liq=3400, V_gas=300, model=None, @@ -519,15 +523,16 @@ def f_q_gas_var_P_headspace(self, rhoTs, S_gas, T): @property def ODE(self): if self._ODE is None: - self._compile_ODE() + self._compile_ODE(self.algebraic_h2) return self._ODE - def _compile_ODE(self): + def _compile_ODE(self, algebraic_h2=True): if self._model is None: CSTR._compile_ODE(self) else: cmps = self.components f_rtn = self._f_retain + _state = self._state _dstate = self._dstate _update_dstate = self._update_dstate _f_rhos = self.model.rate_function @@ -544,7 +549,6 @@ def _compile_ODE(self): f_qgas = self.f_q_gas_fixed_P_headspace else: f_qgas = self.f_q_gas_var_P_headspace - if self.model._dyn_params: def M_stoichio(state_arr): _f_param(state_arr) @@ -552,21 +556,47 @@ def M_stoichio(state_arr): else: _M_stoichio = self.model.stoichio_eval().T M_stoichio = lambda state_arr: _M_stoichio + + h2_idx = cmps.index('S_h2') + if algebraic_h2: + params = self.model.rate_function.params + if self.model._dyn_params: + def h2_stoichio(state_arr): + return M_stoichio(state_arr)[h2_idx] + else: + _h2_stoichio = _M_stoichio[h2_idx] + h2_stoichio = lambda state_arr: _h2_stoichio + unit_conversion = cmps.i_mass / cmps.chem_MW + def solve_h2(QC, S_in, T): + Ka = params['Ka_base'] * T_correction_factor(params['T_base'], T, params['Ka_dH']) + h, nh3, co2 = solve_pH(QC, Ka, unit_conversion) + S_h2_0 = QC[h2_idx] + S_h2_in = S_in[h2_idx] + S_h2 = newton(dydt_Sh2_AD, S_h2_0, grad_dydt_Sh2_AD, + args=(QC, h, params, h2_stoichio, V_liq, S_h2_in), + ) + return S_h2 + def update_h2_dstate(dstate): + dstate[h2_idx] = 0. + else: + solve_h2 = lambda QC, S_ins, T: QC[h2_idx] + def update_h2_dstate(dstate): + pass def dy_dt(t, QC_ins, QC, dQC_ins): QC[QC < 2.2e-16] = 0. - S_liq = QC[:n_cmps] - S_gas = QC[n_cmps: (n_cmps+n_gas)] - #!!! Volume change due to temperature difference accounted for - # in _run and _init_state Q_ins = QC_ins[:, -1] S_ins = QC_ins[:, :-1] * 1e-3 # mg/L to kg/m3 Q = sum(Q_ins) + S_in = Q_ins @ S_ins / Q if hasexo: exo_vars = f_exovars(t) QC = np.append(QC, exo_vars) T = exo_vars[0] else: T = self.T + QC[h2_idx] = _state[h2_idx] = solve_h2(QC, S_in, T) rhos =_f_rhos(QC) + S_liq = QC[:n_cmps] + S_gas = QC[n_cmps: (n_cmps+n_gas)] _dstate[:n_cmps] = (Q_ins @ S_ins - Q*S_liq*(1-f_rtn))/V_liq \ + np.dot(M_stoichio(QC), rhos) q_gas = f_qgas(rhos[-3:], S_gas, T) @@ -574,7 +604,9 @@ def dy_dt(t, QC_ins, QC, dQC_ins): + rhos[-3:] * V_liq/V_gas * gas_mass2mol_conversion # _dstate[-1] = dQC_ins[0,-1] _dstate[-1] = 0. + update_h2_dstate(_dstate) _update_dstate() + self._ODE = dy_dt def get_retained_mass(self, biomass_IDs): diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py index 0fd39147..43b9c3d7 100644 --- a/qsdsan/sanunits/_sludge_treatment.py +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -352,8 +352,6 @@ def AE(self): def _compile_AE(self): - # This function is run multiple times during dynamic simulation - _state = self._state _dstate = self._dstate _update_state = self._update_state From 61cd5ec1add131e5e9cd0208809c99ce5e5d47c8 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Fri, 26 Apr 2024 16:28:40 -0700 Subject: [PATCH 327/424] minor bug fix --- qsdsan/sanunits/_clarifier.py | 5 +-- qsdsan/sanunits/_junction.py | 59 ++++++++++++++++++++++++++--------- 2 files changed, 45 insertions(+), 19 deletions(-) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index 48c74b27..c30390ca 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -526,16 +526,13 @@ def dy_dt(t, QC_ins, QC, dQC_ins): flow_in = X_rolled * Q_jout VX = func_vx(X, X_min_arr) J[:] = npmin(VX[:-1], VX[1:]) - # condition = (X_rolled[:jf] Date: Thu, 9 May 2024 13:17:58 -0700 Subject: [PATCH 328/424] minor typo fix --- qsdsan/utils/construction.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/qsdsan/utils/construction.py b/qsdsan/utils/construction.py index d4725167..45583a40 100644 --- a/qsdsan/utils/construction.py +++ b/qsdsan/utils/construction.py @@ -5,11 +5,11 @@ QSDsan: Quantitative Sustainable Design for sanitation and resource recovery systems This module is developed by: - + Yalin Li - + Saumitra Rai - + Joy Zhang This module is under the University of Illinois/NCSA Open Source License. @@ -474,10 +474,10 @@ def calculate_pipe_material(OD, t, ID, L, density=None): # Based on ANSI (American National Standards Institute) pipe chart # the original code has a bug (no data for 22) and has been fixed here IDs = np.array([ - 0.307, 0.410, 0.545, 0.674, 0.884, 1.097, 1.442, 1.682, 2.157, - 2.635, 3.260, 3.760, 4.260, 4.760, 5.295, 6.357, 7.357, 8.329, - 9.329, 10.420, 11.420, 12.390, 13.624, 15.602, 17.624, 19.564, 21.500, - 23.500, 25.500, 27.500, 29.376, 31.376, 33.376, 35.376, 41.376, 47.376 + 0.307, 0.410, 0.545, 0.674, 0.884, 1.097, 1.442, 1.682, 2.157, + 2.635, 3.260, 3.760, 4.260, 4.760, 5.295, 6.357, 7.357, 8.329, + 9.329, 10.420, 11.420, 12.390, 13.624, 15.602, 17.624, 19.564, 21.500, + 23.500, 25.500, 27.500, 29.376, 31.376, 33.376, 35.376, 41.376, 47.376 ]) size = IDs.shape[0] @@ -535,7 +535,7 @@ def select_pipe(Q, v): Q : float Flow rate of the fluid, [ft3/s] (cfs). v : float - Minumum permissible velocity of the fluid, [ft/s]. + Minimum permissible velocity of the fluid, [ft/s]. Returns ------- @@ -544,12 +544,12 @@ def select_pipe(Q, v): A = Q / v # cross-section area ID = (4*A/np.pi) ** 0.5 # maximum inner diameter, [ft] ID *= 12 # maximum inner diameter, [in] - + ids = IDs[IDs <= ID] - if ids.size == 0: + if ids.size == 0: ID = IDs[0] # inch - else: + else: ID = ids[-1] OD, t = pipe_dct[ID] - return OD, t, ID \ No newline at end of file + return OD, t, ID From 35333be643dc6a7010dc0081affa54b08105d1d1 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Thu, 9 May 2024 14:01:47 -0700 Subject: [PATCH 329/424] merged changes from `metro-bsm2` & resolve bug --- qsdsan/sanunits/_anaerobic_reactor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qsdsan/sanunits/_anaerobic_reactor.py b/qsdsan/sanunits/_anaerobic_reactor.py index 88473a0a..06d47d20 100644 --- a/qsdsan/sanunits/_anaerobic_reactor.py +++ b/qsdsan/sanunits/_anaerobic_reactor.py @@ -481,7 +481,7 @@ def _update_state(self): gas.state[:n_cmps] = gas.state[:n_cmps] * chem_MW / i_mass * 1e3 # i.e., M biogas to mg (measured_unit) / L def _update_dstate(self): - # self._tempstate = self.model.rate_function._params['root'].data.copy() + self._tempstate = self.model.rate_function._params['root'].data.copy() dy = self._dstate f_rtn = self._f_retain n_cmps = len(self.components) From 58accdfc9326e6c86ce4f9e3d3cf8292f95ce8e7 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Thu, 9 May 2024 14:51:16 -0700 Subject: [PATCH 330/424] fix failed tests --- qsdsan/_waste_stream.py | 140 +++++++++++----------- qsdsan/sanunits/_clarifier.py | 172 +++++++++------------------ qsdsan/sanunits/_sludge_treatment.py | 20 +++- 3 files changed, 142 insertions(+), 190 deletions(-) diff --git a/qsdsan/_waste_stream.py b/qsdsan/_waste_stream.py index 0def02aa..ca737b98 100644 --- a/qsdsan/_waste_stream.py +++ b/qsdsan/_waste_stream.py @@ -1280,23 +1280,23 @@ def codstates_inf_model(cls, ID='', flow_tot=0., units = ('L/hr', 'mg/L'), >>> ws = WasteStream.codstates_inf_model('ws_codstates', flow_tot=100) >>> ws.show() WasteStream: ws_codstates - phase: 'l', T: 298.15 K, P: 101325 Pa - flow (g/hr): S_F 8.6 - S_U_Inf 2.15 - C_B_Subst 4 - X_B_Subst 22.7 - X_U_Inf 5.59 - X_Ig_ISS 5.23 - S_NH4 2.5 - S_PO4 0.8 - S_K 2.8 - S_Ca 14 - S_Mg 5 - S_CO3 12 - S_N2 1.8 - S_CAT 0.3 - S_AN 1.2 - ... + phase: 'l', T: 298.15 K, P: 101325 Pa + flow (g/hr): S_F 8.6 + S_U_Inf 2.15 + C_B_Subst 4 + X_B_Subst 22.7 + X_U_Inf 5.59 + X_Ig_ISS 5.23 + S_NH4 2.5 + S_PO4 0.8 + S_K 2.8 + S_Ca 14 + S_Mg 5 + S_CO3 12 + S_N2 1.8 + S_CAT 0.3 + S_AN 1.2 + ... 9.96e+04 WasteStream-specific properties: pH : 7.0 Alkalinity : 10.0 mg/L @@ -1307,6 +1307,7 @@ def codstates_inf_model(cls, ID='', flow_tot=0., units = ('L/hr', 'mg/L'), TN : 40.0 mg/L TP : 10.0 mg/L TK : 28.0 mg/L + TSS : 209.3 mg/L Component concentrations (mg/L): S_F 86.0 S_U_Inf 21.5 @@ -1502,23 +1503,23 @@ def codbased_inf_model(cls, ID='', flow_tot=0., units = ('L/hr', 'mg/L'), >>> ws = WasteStream.codbased_inf_model('ws_codinf', flow_tot=100) >>> ws.show() WasteStream: ws_codinf - phase: 'l', T: 298.15 K, P: 101325 Pa - flow (g/hr): S_F 7.5 - S_U_Inf 3.25 - C_B_Subst 4 - X_B_Subst 22.7 - X_U_Inf 5.58 - X_Ig_ISS 5.23 - S_NH4 2.5 - S_PO4 0.8 - S_K 2.8 - S_Ca 14 - S_Mg 5 - S_CO3 12 - S_N2 1.8 - S_CAT 0.3 - S_AN 1.2 - ... + phase: 'l', T: 298.15 K, P: 101325 Pa + flow (g/hr): S_F 7.5 + S_U_Inf 3.25 + C_B_Subst 4 + X_B_Subst 22.7 + X_U_Inf 5.58 + X_Ig_ISS 5.23 + S_NH4 2.5 + S_PO4 0.8 + S_K 2.8 + S_Ca 14 + S_Mg 5 + S_CO3 12 + S_N2 1.8 + S_CAT 0.3 + S_AN 1.2 + ... 9.96e+04 WasteStream-specific properties: pH : 7.0 Alkalinity : 10.0 mg/L @@ -1529,6 +1530,7 @@ def codbased_inf_model(cls, ID='', flow_tot=0., units = ('L/hr', 'mg/L'), TN : 40.0 mg/L TP : 10.0 mg/L TK : 28.0 mg/L + TSS : 209.3 mg/L Component concentrations (mg/L): S_F 75.0 S_U_Inf 32.5 @@ -1730,23 +1732,23 @@ def bodbased_inf_model(cls, ID='', flow_tot=0., units = ('L/hr', 'mg/L'), >>> ws = WasteStream.bodbased_inf_model('ws_bodinf', flow_tot=100) >>> ws.show() WasteStream: ws_bodinf - phase: 'l', T: 298.15 K, P: 101325 Pa - flow (g/hr): S_F 8.72 - S_U_Inf 3.78 - C_B_Subst 4 - X_B_Subst 22.7 - X_U_Inf 3.94 - X_Ig_ISS 4.93 - S_NH4 2.5 - S_PO4 0.8 - S_K 2.8 - S_Ca 14 - S_Mg 5 - S_CO3 12 - S_N2 1.8 - S_CAT 0.3 - S_AN 1.2 - ... + phase: 'l', T: 298.15 K, P: 101325 Pa + flow (g/hr): S_F 8.72 + S_U_Inf 3.78 + C_B_Subst 4 + X_B_Subst 22.7 + X_U_Inf 3.94 + X_Ig_ISS 4.93 + S_NH4 2.5 + S_PO4 0.8 + S_K 2.8 + S_Ca 14 + S_Mg 5 + S_CO3 12 + S_N2 1.8 + S_CAT 0.3 + S_AN 1.2 + ... 9.96e+04 WasteStream-specific properties: pH : 7.0 Alkalinity : 10.0 mg/L @@ -1757,6 +1759,7 @@ def bodbased_inf_model(cls, ID='', flow_tot=0., units = ('L/hr', '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 @@ -1961,23 +1964,23 @@ def sludge_inf_model(cls, ID='', flow_tot=0., units = ('L/hr', 'mg/L'), >>> ws = WasteStream.sludge_inf_model('ws_sludgeinf', flow_tot=100) >>> ws.show() WasteStream: ws_sludgeinf - phase: 'l', T: 298.15 K, P: 101325 Pa - flow (g/hr): S_F 1.08 - S_U_Inf 8.65 - C_B_Subst 1.08 - X_B_Subst 81.9 - X_OHO 192 - X_AOO 9.62 - X_NOO 9.62 - X_PAO 9.62 - X_U_Inf 468 - X_U_OHO_E 285 - X_U_PAO_E 14.3 - X_Ig_ISS 298 - S_NH4 10 - S_PO4 5 - S_K 2.8 - ... + phase: 'l', T: 298.15 K, P: 101325 Pa + flow (g/hr): S_F 1.08 + S_U_Inf 8.65 + C_B_Subst 1.08 + X_B_Subst 81.9 + X_OHO 192 + X_AOO 9.62 + X_NOO 9.62 + X_PAO 9.62 + X_U_Inf 468 + X_U_OHO_E 285 + X_U_PAO_E 14.3 + X_Ig_ISS 298 + S_NH4 10 + S_PO4 5 + S_K 2.8 + ... 9.88e+04 WasteStream-specific properties: pH : 7.0 Alkalinity : 10.0 mg/L @@ -1988,6 +1991,7 @@ def sludge_inf_model(cls, ID='', flow_tot=0., units = ('L/hr', 'mg/L'), TN : 750.0 mg/L TP : 250.0 mg/L TK : 52.9 mg/L + TSS : 10000.0 mg/L Component concentrations (mg/L): S_F 10.8 S_U_Inf 86.5 diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index c30390ca..7d68753a 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -934,12 +934,64 @@ class PrimaryClarifierBSM2(SanUnit): >>> ws = WasteStream('ws', S_F = 10, S_NH4 = 20, X_OHO = 15, H2O=1000) >>> from qsdsan.sanunits import PrimaryClarifierBSM2 >>> PC = PrimaryClarifierBSM2(ID='PC', ins= (ws,), outs=('eff', 'sludge'), - isdynamic=False) + ... isdynamic=False) >>> PC.simulate() >>> of, uf = PC.outs >>> uf.imass['X_OHO']/ws.imass['X_OHO'] # doctest: +ELLIPSIS 0.598... - >>> # PC.show() + >>> PC.show() + PrimaryClarifierBSM2: PC + ins... + [0] ws + phase: 'l', T: 298.15 K, P: 101325 Pa + flow (g/hr): S_F 1e+04 + S_NH4 2e+04 + X_OHO 1.5e+04 + H2O 1e+06 + WasteStream-specific properties: + pH : 7.0 + Alkalinity : 2.5 mg/L + COD : 23873.0 mg/L + BOD : 14963.2 mg/L + TC : 8298.3 mg/L + TOC : 8298.3 mg/L + TN : 20363.2 mg/L + TP : 367.6 mg/L + TK : 68.3 mg/L + TSS : 11124.4 mg/L + outs... + [0] eff + phase: 'l', T: 298.15 K, P: 101325 Pa + flow (g/hr): S_F 9.93e+03 + S_NH4 1.99e+04 + X_OHO 6.03e+03 + H2O 9.93e+05 + WasteStream-specific properties: + pH : 7.0 + COD : 15436.7 mg/L + BOD : 10190.8 mg/L + TC : 5208.2 mg/L + TOC : 5208.2 mg/L + TN : 19890.2 mg/L + TP : 206.9 mg/L + TK : 27.8 mg/L + TSS : 4531.6 mg/L + [1] sludge + phase: 'l', T: 298.15 K, P: 101325 Pa + flow (g/hr): S_F 70 + S_NH4 140 + X_OHO 8.97e+03 + H2O 7e+03 + WasteStream-specific properties: + pH : 7.0 + COD : 693717.8 mg/L + BOD : 393895.8 mg/L + TC : 253653.5 mg/L + TOC : 253653.5 mg/L + TN : 57923.7 mg/L + TP : 13132.3 mg/L + TK : 3282.0 mg/L + TSS : 534594.2 mg/L References ---------- @@ -1078,60 +1130,6 @@ def _update_dstate(self): uf.dstate = self._sludge * self._dstate of.dstate = self._effluent * self._dstate - # def _init_state(self): - # # if multiple wastestreams exist then concentration and total flow - # # would be calculated assuming perfect mixing - # Qs = self._ins_QC[:,-1] - # Cs = self._ins_QC[:,:-1] - # self._state = np.append(Qs @ Cs / Qs.sum(), Qs.sum()) - # self._dstate = self._state * 0. - - # uf, of = self.outs - # s_flow = uf.F_vol/(uf.F_vol+of.F_vol) - # denominator = uf.mass + of.mass - # denominator += (denominator == 0) - # s = uf.mass/denominator - # self._sludge = np.append(s/s_flow, s_flow) - # self._effluent = np.append((1-s)/(1-s_flow), 1-s_flow) - - # def _update_state(self): - # '''updates conditions of output stream based on conditions of the Primary Clarifier''' - # self._outs[0].state = self._sludge * self._state - # self._outs[1].state = self._effluent * self._state - - # def _update_dstate(self): - # '''updates rates of change of output stream from rates of change of the Primary Clarifier''' - # self._outs[0].dstate = self._sludge * self._dstate - # self._outs[1].dstate = self._effluent * self._dstate - - # @property - # def AE(self): - # if self._AE is None: - # self._compile_AE() - # return self._AE - - # def _compile_AE(self): - # _state = self._state - # _dstate = self._dstate - # _update_state = self._update_state - # _update_dstate = self._update_dstate - # def yt(t, QC_ins, dQC_ins): - # #Because there are multiple inlets - # Q_ins = QC_ins[:, -1] - # C_ins = QC_ins[:, :-1] - # dQ_ins = dQC_ins[:, -1] - # dC_ins = dQC_ins[:, :-1] - # Q = Q_ins.sum() - # C = Q_ins @ C_ins / Q - # _state[-1] = Q - # _state[:-1] = C - # Q_dot = dQ_ins.sum() - # C_dot = (dQ_ins @ C_ins + Q_ins @ dC_ins - Q_dot * C)/Q - # _dstate[-1] = Q_dot - # _dstate[:-1] = C_dot - # _update_state() - # _update_dstate() - # self._AE = yt @property def ODE(self): if self._ODE is None: @@ -1193,68 +1191,6 @@ class PrimaryClarifier(SanUnit): F_BM : dict Equipment bare modules. - Examples - -------- - >>> from qsdsan import set_thermo, Components, WasteStream - >>> cmps = Components.load_default() - >>> cmps_test = cmps.subgroup(['S_F', 'S_NH4', 'X_OHO', 'H2O']) - >>> set_thermo(cmps_test) - >>> ws = WasteStream('ws', S_F = 10, S_NH4 = 20, X_OHO = 15, H2O=1000) - >>> from qsdsan.sanunits import Thickener - >>> TC = Thickener(ID='TC', ins= (ws), outs=('sludge', 'effluent')) - >>> TC.simulate() - >>> sludge, effluent = TC.outs - >>> sludge.imass['X_OHO']/ws.imass['X_OHO'] - 0.98 - >>> TC.show() # doctest: +ELLIPSIS - Thickener: TC - ins... - [0] ws - phase: 'l', T: 298.15 K, P: 101325 Pa - flow (g/hr): S_F 1e+04 - S_NH4 2e+04 - X_OHO 1.5e+04 - H2O 1e+06 - WasteStream-specific properties: - pH : 7.0 - COD : 23873.0 mg/L - BOD : 14963.2 mg/L - TC : 8298.3 mg/L - TOC : 8298.3 mg/L - TN : 20363.2 mg/L - TP : 367.6 mg/L - TK : 68.3 mg/L - outs... - [0] sludge - phase: 'l', T: 298.15 K, P: 101325 Pa - flow (g/hr): S_F 1.56e+03 - S_NH4 3.11e+03 - X_OHO 1.47e+04 - H2O 1.56e+05 - WasteStream-specific properties: - pH : 7.0 - COD : 95050.4 mg/L - BOD : 55228.4 mg/L - TC : 34369.6 mg/L - TOC : 34369.6 mg/L - TN : 24354.4 mg/L - TP : 1724.0 mg/L - TK : 409.8 mg/L - [1] effluent - phase: 'l', T: 298.15 K, P: 101325 Pa - flow (g/hr): S_F 8.44e+03 - S_NH4 1.69e+04 - X_OHO 300 - H2O 8.44e+05 - WasteStream-specific properties: - pH : 7.0 - COD : 9978.2 mg/L - BOD : 7102.9 mg/L - TC : 3208.8 mg/L - TOC : 3208.8 mg/L - TN : 19584.1 mg/L - TP : 102.9 mg/L - TK : 1.6 mg/L References ---------- diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py index 43b9c3d7..2616d704 100644 --- a/qsdsan/sanunits/_sludge_treatment.py +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -112,6 +112,7 @@ class Thickener(SanUnit): H2O 1e+06 WasteStream-specific properties: pH : 7.0 + Alkalinity : 2.5 mg/L COD : 23873.0 mg/L BOD : 14963.2 mg/L TC : 8298.3 mg/L @@ -119,6 +120,7 @@ class Thickener(SanUnit): TN : 20363.2 mg/L TP : 367.6 mg/L TK : 68.3 mg/L + TSS : 11124.4 mg/L outs... [0] sludge phase: 'l', T: 298.15 K, P: 101325 Pa @@ -135,6 +137,7 @@ class Thickener(SanUnit): TN : 24354.4 mg/L TP : 1724.0 mg/L TK : 409.8 mg/L + TSS : 66748.0 mg/L [1] effluent phase: 'l', T: 298.15 K, P: 101325 Pa flow (g/hr): S_F 8.44e+03 @@ -150,6 +153,7 @@ class Thickener(SanUnit): TN : 19584.1 mg/L TP : 102.9 mg/L TK : 1.6 mg/L + TSS : 265.9 mg/L References ---------- @@ -891,16 +895,22 @@ class Incinerator(SanUnit): -------- >>> import qsdsan as qs >>> cmps = qs.Components.load_default() - >>> CO2 = qs.Component.from_chemical('S_CO2', search_ID='CO2', particle_size='Soluble', degradability='Undegradable', organic=False) - >>> cmps_test = qs.Components([cmps.S_F, cmps.S_NH4, cmps.X_OHO, cmps.H2O, cmps.S_CH4, cmps.S_O2, cmps.S_N2, cmps.S_H2, cmps.X_Ig_ISS, CO2]) + >>> CO2 = qs.Component.from_chemical('S_CO2', search_ID='CO2', + ... particle_size='Soluble', + ... degradability='Undegradable', + ... organic=False) + >>> cmps_test = qs.Components([cmps.S_F, cmps.S_NH4, cmps.X_OHO, cmps.H2O, + ... cmps.S_CH4, cmps.S_O2, cmps.S_N2, cmps.S_H2, + ... cmps.X_Ig_ISS, CO2]) >>> cmps_test.default_compile() >>> qs.set_thermo(cmps_test) >>> ws = qs.WasteStream('ws', S_F=10, S_NH4=20, X_OHO=15, H2O=1000) >>> natural_gas = qs.WasteStream('nat_gas', phase='g', S_CH4=1000) >>> air = qs.WasteStream('air', phase='g', S_O2=210, S_N2=780, S_H2=10) >>> from qsdsan.sanunits import Incinerator - >>> Inc = Incinerator(ID='Inc', ins= (ws, air, natural_gas), outs=('flu_gas', 'ash'), - ... isdynamic=False) + >>> Inc = Incinerator(ID='Inc', ins= (ws, air, natural_gas), + ... outs=('flu_gas', 'ash'), + ... isdynamic=False) >>> Inc.simulate() >>> Inc.show() Incinerator: Inc @@ -913,6 +923,7 @@ class Incinerator(SanUnit): H2O 1e+06 WasteStream-specific properties: pH : 7.0 + Alkalinity : 2.5 mg/L COD : 23873.0 mg/L BOD : 14963.2 mg/L TC : 8298.3 mg/L @@ -920,6 +931,7 @@ class Incinerator(SanUnit): TN : 20363.2 mg/L TP : 367.6 mg/L TK : 68.3 mg/L + TSS : 11124.4 mg/L [1] air phase: 'g', T: 298.15 K, P: 101325 Pa flow (g/hr): S_O2 2.1e+05 From 013b59ee91462e0fc8d9c783f04afdc5dc63f748 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Thu, 9 May 2024 17:26:59 -0700 Subject: [PATCH 331/424] enable dynamic `IdealClarifier` --- qsdsan/sanunits/_clarifier.py | 166 ++++++++++++++++++++++++---------- 1 file changed, 120 insertions(+), 46 deletions(-) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index 7d68753a..5910a460 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -786,7 +786,7 @@ def _cost(self): class IdealClarifier(SanUnit): _N_ins = 1 - _N_outs = 2 + _N_outs = 2 # [0] effluent overflow, [1] sludge underflow def __init__(self, ID='', ins=None, outs=(), thermo=None, sludge_flow_rate=2000, solids_removal_efficiency=.995, @@ -798,6 +798,9 @@ def __init__(self, ID='', ins=None, outs=(), thermo=None, self.sludge_flow_rate = sludge_flow_rate self.solids_removal_efficiency = solids_removal_efficiency self.sludge_MLSS = sludge_MLSS + self._mixed = WasteStream() + self._f_uf = None + self._f_of = None @property def sludge_flow_rate(self): @@ -806,9 +809,7 @@ def sludge_flow_rate(self): @sludge_flow_rate.setter def sludge_flow_rate(self, Qs): - if Qs is not None: self._Qs = Qs - elif self.ins[0].isempty(): self._Qs = None - else: self._Qs = self._calc_Qs() + self._Qs = Qs @property def solids_removal_efficiency(self): @@ -816,12 +817,9 @@ def solids_removal_efficiency(self): @solids_removal_efficiency.setter def solids_removal_efficiency(self, f): - if f is not None: - if f > 1 or f < 0: - raise ValueError(f'solids removal efficiency must be within [0, 1], not {f}') - self._e_rmv = f - elif self.ins[0].isempty(): self._e_rmv = None - else: self._e_rmv = self._calc_ermv() + if f is not None and (f > 1 or f < 0): + raise ValueError(f'solids removal efficiency must be within [0, 1], not {f}') + self._e_rmv = f @property def sludge_MLSS(self): @@ -829,58 +827,134 @@ def sludge_MLSS(self): @sludge_MLSS.setter def sludge_MLSS(self, MLSS): - if MLSS is not None: self._MLSS = MLSS - elif self.ins[0].isempty(): self._MLSS = None - else: self._MLSS = self._calc_SS()[1] + if MLSS is not None: + warn(f'sludge MLSS {MLSS} mg/L is only used to estimate ' + f'sludge flowrate or solids removal efficiency, when either' + f'one of them is unspecified.') + self._MLSS = MLSS + def _calc_Qs(self, TSS_in=None, Q_in=None): - if Q_in is None: Q_in = self.ins[0].get_total_flow('m3/d') - if TSS_in is None: TSS_in = self.ins[0].get_TSS() + if Q_in is None: Q_in = self._mixed.get_total_flow('m3/d') + if TSS_in is None: TSS_in = self._mixed.get_TSS() return Q_in*TSS_in*self._e_rmv/(self._MLSS-TSS_in) def _calc_ermv(self, TSS_in=None, Q_in=None): - if Q_in is None: Q_in = self.ins[0].get_total_flow('m3/d') - if TSS_in is None: TSS_in = self.ins[0].get_TSS() + if Q_in is None: Q_in = self._mixed.get_total_flow('m3/d') + if TSS_in is None: TSS_in = self._mixed.get_TSS() return self._Qs*(self._MLSS-TSS_in)/TSS_in/(Q_in-self._Qs) def _calc_SS(self, SS_in=None, Q_in=None): - if Q_in is None: Q_in = self.ins[0].get_total_flow('m3/d') - if SS_in is None: SS_in = self.ins[0].get_TSS() + if Q_in is None: Q_in = self._mixed.get_total_flow('m3/d') + if SS_in is None: SS_in = self._mixed.get_TSS() SS_e = (1-self._e_rmv)*SS_in Qs = self._Qs Qe = Q_in - Qs return SS_e, (Q_in*SS_in - Qe*SS_e)/Qs def _run(self): - inf, = self.ins - eff, sludge = self.outs - cmps = self.components - Q_in = inf.get_total_flow('m3/d') - TSS_in = (inf.conc*cmps.x*cmps.i_mass).sum() - params = (Qs, e_rmv, MLSS) = self._Qs, self._e_rmv, self._MLSS - if sum([i is None for i in params]) > 1: - raise RuntimeError('must specify two of the following parameters: ' - 'sludge_flow_rate, solids_removal_efficiency, sludge_MLSS') - if Qs is None: - Qs = self._calc_Qs(TSS_in, Q_in) - Xs = MLSS / TSS_in * inf.conc * cmps.x - Xe = (1-e_rmv) * inf.conc * cmps.x - elif e_rmv is None: - e_rmv = self._calc_ermv(TSS_in, Q_in) - Xs = MLSS / TSS_in * inf.conc * cmps.x - Xe = (1-e_rmv) * inf.conc * cmps.x + inf = self._mixed + inf.mix_from(self.ins) + of, uf = self.outs + Qs, e_rmv = self._Qs, self._e_rmv + TSS_in = inf.get_TSS() + Q_in = inf.F_vol * 24 # m3/d + if Qs is None: Qs = self._calc_Qs(TSS_in, Q_in) + if e_rmv is None: e_rmv = self._calc_ermv(TSS_in, Q_in) + f_Qu = Qs/Q_in + x = inf.components.x + split_to_uf = (1-x)*f_Qu + x*e_rmv + if any(split_to_uf > 1): split_to_uf = 1 + inf.split_to(uf, of, split_to_uf) + + def _init_state(self): + inf = self._mixed + C_in = inf.conc + TSS_in = inf.get_TSS() + Q_in = inf.F_vol * 24 + self._state = np.append(C_in, Q_in) + self._dstate = self._state * 0. + if not self._Qs: self._Qs = self._calc_Qs(TSS_in, Q_in) + if not self._e_rmv: self._e_rmv = self._calc_ermv(TSS_in, Q_in) + + def _update_state(self): + arr = self._state + Cs = arr[:-1] + Qi = arr[-1] + Qs = self._Qs + e_rmv = self._e_rmv + x = self.components.x + + of, uf = self.outs + if uf.state is None: uf.state = np.zeros(len(x)+1) + if of.state is None: of.state = np.zeros(len(x)+1) + + if Qs >= Qi: + uf.state[:] = arr + of.state[:] = 0. + elif Qs <= 0: + uf.state[:] = 0. + of.state[:] = arr else: - Xe, Xs = self._calc_SS(inf.conc * cmps.x, Q_in) - Zs = Ze = inf.conc * (1-cmps.x) - Ce = dict(zip(cmps.IDs, Ze+Xe)) - Cs = dict(zip(cmps.IDs, Zs+Xs)) - Ce.pop('H2O', None) - Cs.pop('H2O', None) - eff.set_flow_by_concentration(Q_in-Qs, Ce, units=('m3/d', 'mg/L')) - sludge.set_flow_by_concentration(Qs, Cs, units=('m3/d', 'mg/L')) + self._f_uf = fuf = e_rmv*Qi/Qs + self._f_of = fof = (1-e_rmv)/(1-Qi/Qs) + uf.state[:-1] = Cs * ((1-x) + x*fuf) + uf.state[-1] = Qs + of.state[:-1] = Cs * ((1-x) + x*fof) + of.state[-1] = Qi - Qs - def _design(self): - pass + def _update_dstate(self): + arr = self._dstate + dCs = arr[:-1] + dQi = arr[-1] + Cs = self._state[:-1] + Qi = self._state[-1] + Qs = self._Qs + e_rmv = self._e_rmv + x = self.components.x + + uf, of = self.outs + if uf.dstate is None: uf.dstate = np.zeros(len(x)+1) + if of.dstate is None: of.dstate = np.zeros(len(x)+1) + if Qs >= Qi: + uf.dstate[:] = arr + of.dstate[:] = 0. + elif Qs <= 0: + uf.dstate[:] = 0. + of.dstate[:] = arr + else: + uf.dstate[:-1] = dCs * ((1-x) + x*self._f_uf) + Cs*x*e_rmv*dQi/Qs + uf.dstate[-1] = 0. + of.dstate[:-1] = dCs * ((1-x) + x*self._f_of) - Cs*x*(1-e_rmv)*Qs/(Qi-Qs)**2 * dQi + of.dstate[-1] = dQi + + @property + def AE(self): + if self._AE is None: + self._compile_AE() + return self._AE + + def _compile_AE(self): + _state = self._state + _dstate = self._dstate + _update_state = self._update_state + _update_dstate = self._update_dstate + def yt(t, QC_ins, dQC_ins): + Q_ins = QC_ins[:, -1] + C_ins = QC_ins[:, :-1] + dQ_ins = dQC_ins[:, -1] + dC_ins = dQC_ins[:, :-1] + Q = Q_ins.sum() + C = Q_ins @ C_ins / Q + _state[-1] = Q + _state[:-1] = C + Q_dot = dQ_ins.sum() + C_dot = (dQ_ins @ C_ins + Q_ins @ dC_ins - Q_dot * C)/Q + _dstate[-1] = Q_dot + _dstate[:-1] = C_dot + _update_state() + _update_dstate() + self._AE = yt # %% From 1ac9fe693881e129767cb5381f971d61fc5dc73a Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Thu, 9 May 2024 18:05:04 -0700 Subject: [PATCH 332/424] clean up `PrimaryClarifier` --- qsdsan/sanunits/_clarifier.py | 411 ++++++++++------------------------ 1 file changed, 120 insertions(+), 291 deletions(-) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index 5910a460..a92dbdac 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -783,13 +783,29 @@ def _cost(self): # %% -class IdealClarifier(SanUnit): +class IdealClarifier(SanUnit): + """ + Ideal clarifier that settles suspended solids by specified efficiency. Has + no design or costing algorithm. + + Parameters + ---------- + sludge_flow_rate : float, optional + Underflow sludge flowrate [m3/d]. The default is 2000. + solids_removal_efficiency : float, optional + Removal efficiency of suspended solids, unitless. The default is 0.995. + sludge_MLSS : float, optional + Underflow MLSS [mg/L]. Used only when either `solids_removal_efficiency` + or `sludge_flow_rate` is unspecified. The default is None. + + """ _N_ins = 1 _N_outs = 2 # [0] effluent overflow, [1] sludge underflow + _outs_size_is_fixed = True def __init__(self, ID='', ins=None, outs=(), thermo=None, - sludge_flow_rate=2000, solids_removal_efficiency=.995, + sludge_flow_rate=2000, solids_removal_efficiency=0.995, sludge_MLSS=None, isdynamic=False, init_with='WasteStream', F_BM_default=None, **kwargs): @@ -913,7 +929,7 @@ def _update_dstate(self): e_rmv = self._e_rmv x = self.components.x - uf, of = self.outs + of, uf = self.outs if uf.dstate is None: uf.dstate = np.zeros(len(x)+1) if of.dstate is None: of.dstate = np.zeros(len(x)+1) if Qs >= Qi: @@ -1119,33 +1135,6 @@ def ratio_uf(self, r): self._sludge[-1] = r self._effluent[-1] = 1-r - # def _f_i(self): - # xcod = self._mixed.composite('COD', particle_size='x') - # fx = xcod/self._mixed.COD - # n_COD = self.f_corr*(2.88*fx - 0.118)*(1.45 + 6.15*np.log(self.HRT*24*60)) - # f_i = 1 - (n_COD/100) - # return f_i - - # def _run(self): - # uf, of = self.outs - # cmps = self.components - # mixed = self._mixed - # mixed.mix_from(self.ins) - - # r = self._r - # f_i = self._f_i() - - # Xs = (1 - f_i)*mixed.mass*cmps.x - # Xe = (f_i)*mixed.mass*cmps.x - - # Zs = r*mixed.mass*cmps.s - # Ze = (1-r)*mixed.mass*cmps.s - - # Ce = Ze + Xe - # Cs = Zs + Xs - # of.set_flow(Ce,'kg/hr') - # uf.set_flow(Cs,'kg/hr') - @property def f_x(self): '''[float] Fraction of particulate COD [-].''' @@ -1235,52 +1224,111 @@ def dy_dt(t, QC_ins, QC, dQC_ins): } default_F_BM.update(default_WWTpump_F_BM) -class PrimaryClarifier(SanUnit): +class PrimaryClarifier(IdealClarifier): """ - Primary clarifier adapted from the design of thickener as defined in BSM-2. [1] + Primary clarifier with an ideal settling process model. + + Parameters ---------- - ID : str - ID for the Primary Clarifier. The default is ''. - ins : class:`WasteStream` - Influent to the clarifier. Expected number of influent is 1. - outs : class:`WasteStream` - Sludge and treated effluent. - thickener_perc : float - The percentage of solids in the underflow of the clarifier.[1] - TSS_removal_perc : float - The percentage of suspended solids removed in the clarifier.[1] surface_overflow_rate : float - Surface overflow rate in the clarifier in [(m3/day)/m2]. [3] + Surface overflow rate in the clarifier in [(m3/day)/m2]. [1] Design SOR value for clarifier is 41 (m3/day)/m2 if it does not receive WAS. Design SOR value for clarifier is 29 (m3/day)/m2 if it receives WAS. Typically SOR lies between 30-50 (m3/day)/m2. Here default value of 41 (m3/day)/m2 is used. depth_clarifier : float - Depth of clarifier. Typical depths range from 3 m to 4.9 m [2, 3]. + Depth of clarifier. Typical depths range from 3 m to 4.9 m [1,2]. Default value of 4.5 m would be used here. downward_flow_velocity : float, optional - Speed on the basis of which center feed diameter is designed [m/hr]. [4] + Speed on the basis of which center feed diameter is designed [m/hr]. [3] The default is 36 m/hr. (10 mm/sec) F_BM : dict Equipment bare modules. + Examples + -------- + >>> from qsdsan import set_thermo, Components, WasteStream + >>> cmps = Components.load_default() + >>> cmps_test = cmps.subgroup(['S_F', 'S_NH4', 'X_OHO', 'H2O']) + >>> set_thermo(cmps_test) + >>> ws = WasteStream('ws', S_F = 10, S_NH4 = 20, X_OHO = 15, H2O=1000) + >>> from qsdsan.sanunits import PrimaryClarifier + >>> PC = PrimaryClarifier(ID='IC', ins=ws, outs=('effluent', 'sludge'), + ... solids_removal_efficiency=0.6, + ... sludge_flow_rate=ws.F_vol*24*0.3) + >>> PC.simulate() + >>> effluent, sludge = PC.outs + >>> sludge.imass['X_OHO']/ws.imass['X_OHO'] + 0.6 + >>> PC.show() # doctest: +ELLIPSIS + PrimaryClarifier: IC + ins... + [0] ws + phase: 'l', T: 298.15 K, P: 101325 Pa + flow (g/hr): S_F 1e+04 + S_NH4 2e+04 + X_OHO 1.5e+04 + H2O 1e+06 + WasteStream-specific properties: + pH : 7.0 + Alkalinity : 2.5 mg/L + COD : 23873.0 mg/L + BOD : 14963.2 mg/L + TC : 8298.3 mg/L + TOC : 8298.3 mg/L + TN : 20363.2 mg/L + TP : 367.6 mg/L + TK : 68.3 mg/L + TSS : 11124.4 mg/L + outs... + [0] effluent + phase: 'l', T: 298.15 K, P: 101325 Pa + flow (g/hr): S_F 7e+03 + S_NH4 1.4e+04 + X_OHO 6e+03 + H2O 7e+05 + WasteStream-specific properties: + pH : 7.0 + COD : 17804.5 mg/L + BOD : 11530.2 mg/L + TC : 6075.4 mg/L + TOC : 6075.4 mg/L + TN : 20022.9 mg/L + TP : 252.0 mg/L + TK : 39.2 mg/L + TSS : 6382.0 mg/L + [1] sludge + phase: 'l', T: 298.15 K, P: 101325 Pa + flow (g/hr): S_F 3e+03 + S_NH4 6e+03 + X_OHO 9e+03 + H2O 3e+05 + WasteStream-specific properties: + pH : 7.0 + COD : 37848.4 mg/L + BOD : 22869.1 mg/L + TC : 13417.3 mg/L + TOC : 13417.3 mg/L + TN : 21146.9 mg/L + TP : 634.0 mg/L + TK : 135.3 mg/L + TSS : 22045.9 mg/L + References ---------- - .. [1] Gernaey, Krist V., Ulf Jeppsson, Peter A. Vanrolleghem, and John B. Copp. - Benchmarking of control strategies for wastewater treatment plants. IWA publishing, 2014. + [1] Chapter-10: Primary Treatment. Design of water resource recovery facilities. + WEF Manual of Practice No. 8. 6th Edition. Virginia: McGraw-Hill, 2018. [2] Metcalf, Leonard, Harrison P. Eddy, and Georg Tchobanoglous. Wastewater engineering: treatment, disposal, and reuse. Vol. 4. New York: McGraw-Hill, 1991. - [3] Chapter-10: Primary Treatment. Design of water resource recovery facilities. - WEF Manual of Practice No. 8. 6th Edition. Virginia: McGraw-Hill, 2018. - [4] Introduction to Wastewater Clarifier Design by Nikolay Voutchkov, PE, BCEE. + [3] Introduction to Wastewater Clarifier Design by Nikolay Voutchkov, PE, BCEE. """ _N_ins = 1 _N_outs = 2 _ins_size_is_fixed = False - _outs_size_is_fixed = False + _outs_size_is_fixed = True # Costs wall_concrete_unit_cost = 1081.73 # $/m3 (Hydromantis. CapdetWorks 4.0. https://www.hydromantis.com/CapdetWorks.html) @@ -1289,34 +1337,23 @@ class PrimaryClarifier(SanUnit): pumps = ('sludge',) - def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, - init_with='WasteStream', thickener_perc=7, - TSS_removal_perc=98, surface_overflow_rate = 41, depth_clarifier=4.5, - downward_flow_velocity=36, F_BM=default_F_BM, **kwargs): - SanUnit.__init__(self, ID, ins, outs, thermo, isdynamic=isdynamic, + def __init__(self, ID='', ins=None, outs=(), + sludge_flow_rate=2000, solids_removal_efficiency=0.6, + thermo=None, isdynamic=False, init_with='WasteStream', + surface_overflow_rate = 41, depth_clarifier=4.5, + downward_flow_velocity=36, F_BM=default_F_BM, **kwargs): + super().__init__(ID, ins, outs, thermo, + sludge_flow_rate=sludge_flow_rate, + solids_removal_efficiency=solids_removal_efficiency, + # thermo=thermo, + isdynamic=isdynamic, init_with=init_with) - self.thickener_perc = thickener_perc - self.TSS_removal_perc = TSS_removal_perc + self.surface_overflow_rate = surface_overflow_rate self.depth_clarifier = depth_clarifier self.downward_flow_velocity = downward_flow_velocity self.F_BM.update(F_BM) - self._mixed = WasteStream(f'{ID}_mixed') - self._sludge = self.outs[0].copy(f'{ID}_sludge') - - @property - def thickener_perc(self): - '''tp is the percentage of Suspended Sludge in the underflow of the clarifier''' - return self._tp - - @thickener_perc.setter - def thickener_perc(self, tp): - if tp is not None: - if tp>=100 or tp<=0: - raise ValueError(f'should be between 0 and 100 not {tp}') - self._tp = tp - else: - raise ValueError('percentage of SS in the underflow of the thickener expected from user') + self._sludge = WasteStream(f'{ID}_sludge') # @property # def solids_loading_rate(self): @@ -1330,214 +1367,10 @@ def thickener_perc(self, tp): # else: # raise ValueError('solids_loading_rate of the clarifier expected from user') - @property - def TSS_removal_perc(self): - '''The percentage of suspended solids removed in the clarifier''' - return self._TSS_rmv - - @TSS_removal_perc.setter - def TSS_removal_perc(self, TSS_rmv): - if TSS_rmv is not None: - if TSS_rmv>=100 or TSS_rmv<=0: - raise ValueError(f'should be between 0 and 100 not {TSS_rmv}') - self._TSS_rmv = TSS_rmv - else: - raise ValueError('percentage of suspended solids removed in the clarifier expected from user') - - @property - def thickener_factor(self): - self._mixed.mix_from(self.ins) - inf = self._mixed - _cal_thickener_factor = self._cal_thickener_factor - if not self.ins: return - elif inf.isempty(): return - else: - TSS_in = inf.get_TSS() - thickener_factor = _cal_thickener_factor(TSS_in) - return thickener_factor - - @property - def thinning_factor(self): - self._mixed.mix_from(self.ins) - inf = self._mixed - TSS_in = inf.get_TSS() - _cal_thickener_factor = self._cal_thickener_factor - thickener_factor = _cal_thickener_factor(TSS_in) - _cal_parameters = self._cal_parameters - Qu_factor, thinning_factor = _cal_parameters(thickener_factor) - return thinning_factor - - def _cal_thickener_factor(self, TSS_in): - if TSS_in > 0: - thickener_factor = self._tp*10000/TSS_in - if thickener_factor<1: - thickener_factor=1 - return thickener_factor - else: return None - - def _cal_parameters(self, thickener_factor): - if thickener_factor<1: - Qu_factor = 1 - thinning_factor=0 - else: - Qu_factor = self._TSS_rmv/(100*thickener_factor) - thinning_factor = (1 - (self._TSS_rmv/100))/(1 - Qu_factor) - return Qu_factor, thinning_factor - - def _update_parameters(self): - - # Thickener_factor, Thinning_factor, and Qu_factor need to be - # updated again and again. while dynamic simulations - - cmps = self.components - - TSS_in = np.sum(self._state[:-1]*cmps.i_mass*cmps.x) - _cal_thickener_factor = self._cal_thickener_factor - self.updated_thickener_factor = _cal_thickener_factor(TSS_in) - _cal_parameters = self._cal_parameters - - updated_thickener_factor = self.updated_thickener_factor - self.updated_Qu_factor, self.updated_thinning_factor = _cal_parameters(updated_thickener_factor) - - def _run(self): - self._mixed.mix_from(self.ins) - inf = self._mixed - sludge, eff = self.outs - cmps = self.components - - TSS_rmv = self._TSS_rmv - thinning_factor = self.thinning_factor - thickener_factor = self.thickener_factor - - # The following are splits by mass of particulates and solubles - - # Note: (1 - thinning_factor)/(thickener_factor - thinning_factor) = Qu_factor - Zs = (1 - thinning_factor)/(thickener_factor - thinning_factor)*inf.mass*cmps.s - Ze = (thickener_factor - 1)/(thickener_factor - thinning_factor)*inf.mass*cmps.s - - Xe = (1 - TSS_rmv/100)*inf.mass*cmps.x - Xs = (TSS_rmv/100)*inf.mass*cmps.x - - # e stands for effluent, s stands for sludge - Ce = Ze + Xe - Cs = Zs + Xs - - eff.set_flow(Ce,'kg/hr') - sludge.set_flow(Cs,'kg/hr') - - def _init_state(self): - - # This function is run only once during dynamic simulations - - # Since there could be multiple influents, the state of the unit is - # obtained assuming perfect mixing - Qs = self._ins_QC[:,-1] - Cs = self._ins_QC[:,:-1] - self._state = np.append(Qs @ Cs / Qs.sum(), Qs.sum()) - self._dstate = self._state * 0. - - # To initialize the updated_thickener_factor, updated_thinning_factor - # and updated_Qu_factor for dynamic simulation - self._update_parameters() - - def _update_state(self): - '''updates conditions of output stream based on conditions of the Thickener''' - - # This function is run multiple times during dynamic simulation - - # Remember that here we are updating the state array of size n, which is made up - # of component concentrations in the first (n-1) cells and the last cell is flowrate. - - # So, while in the run function the effluent and sludge are split by mass, - # here they are split by concentration. Therefore, the split factors are different. - - # Updated intrinsic modelling parameters are used for dynamic simulation - thickener_factor = self.updated_thickener_factor - thinning_factor = self.updated_thinning_factor - Qu_factor = self.updated_Qu_factor - cmps = self.components - - # For sludge, the particulate concentrations are multiplied by thickener factor, and - # flowrate is multiplied by Qu_factor. The soluble concentrations remains same. - uf, of = self.outs - if uf.state is None: uf.state = np.zeros(len(cmps)+1) - uf.state[:-1] = self._state[:-1]*cmps.s*1 + self._state[:-1]*cmps.x*thickener_factor - uf.state[-1] = self._state[-1]*Qu_factor - - # For effluent, the particulate concentrations are multiplied by thinning factor, and - # flowrate is multiplied by Qu_factor. The soluble concentrations remains same. - if of.state is None: of.state = np.zeros(len(cmps)+1) - of.state[:-1] = self._state[:-1]*cmps.s*1 + self._state[:-1]*cmps.x*thinning_factor - of.state[-1] = self._state[-1]*(1 - Qu_factor) - - def _update_dstate(self): - '''updates rates of change of output stream from rates of change of the Thickener''' - - # This function is run multiple times during dynamic simulation - - # Remember that here we are updating the state array of size n, which is made up - # of component concentrations in the first (n-1) cells and the last cell is flowrate. - - # So, while in the run function the effluent and sludge are split by mass, - # here they are split by concentration. Therefore, the split factors are different. - - # Updated intrinsic modelling parameters are used for dynamic simulation - thickener_factor = self.updated_thickener_factor - thinning_factor = self.updated_thinning_factor - Qu_factor = self.updated_Qu_factor - cmps = self.components - - # For sludge, the particulate concentrations are multiplied by thickener factor, and - # flowrate is multiplied by Qu_factor. The soluble concentrations remains same. - uf, of = self.outs - if uf.dstate is None: uf.dstate = np.zeros(len(cmps)+1) - uf.dstate[:-1] = self._dstate[:-1]*cmps.s*1 + self._dstate[:-1]*cmps.x*thickener_factor - uf.dstate[-1] = self._dstate[-1]*Qu_factor - - # For effluent, the particulate concentrations are multiplied by thinning factor, and - # flowrate is multiplied by Qu_factor. The soluble concentrations remains same. - if of.dstate is None: of.dstate = np.zeros(len(cmps)+1) - of.dstate[:-1] = self._dstate[:-1]*cmps.s*1 + self._dstate[:-1]*cmps.x*thinning_factor - of.dstate[-1] = self._dstate[-1]*(1 - Qu_factor) - - @property - def AE(self): - if self._AE is None: - self._compile_AE() - return self._AE - - def _compile_AE(self): - - # This function is run multiple times during dynamic simulation - - _state = self._state - _dstate = self._dstate - _update_state = self._update_state - _update_dstate = self._update_dstate - _update_parameters = self._update_parameters - def yt(t, QC_ins, dQC_ins): - Q_ins = QC_ins[:, -1] - C_ins = QC_ins[:, :-1] - dQ_ins = dQC_ins[:, -1] - dC_ins = dQC_ins[:, :-1] - Q = Q_ins.sum() - C = Q_ins @ C_ins / Q - _state[-1] = Q - _state[:-1] = C - Q_dot = dQ_ins.sum() - C_dot = (dQ_ins @ C_ins + Q_ins @ dC_ins - Q_dot * C)/Q - _dstate[-1] = Q_dot - _dstate[:-1] = C_dot - - _update_parameters() - _update_state() - _update_dstate() - self._AE = yt - def _design_pump(self): ID, pumps = self.ID, self.pumps - self._sludge.copy_like(self.outs[0]) sludge = self._sludge + sludge.copy_like(self.outs[1]) ins_dct = { 'sludge': sludge, @@ -1604,8 +1437,8 @@ def _design_pump(self): def _design(self): - self._mixed.mix_from(self.ins) mixed = self._mixed + mixed.mix_from(self.ins) D = self.design_results # Number of clarifiers based on tentative suggestions by Jeremy @@ -1707,9 +1540,6 @@ def _design(self): D['Number of pumps'] = D['Number of clarifiers'] def _cost(self): - - self._mixed.mix_from(self.ins) - D = self.design_results C = self.baseline_purchase_costs @@ -1759,10 +1589,11 @@ def _cost(self): opex_o += p_add_opex['Pump operating'] opex_m += p_add_opex['Pump maintenance'] - C['Pumps'] = pump_cost*D['Number of clarifiers'] - C['Pump building'] = building_cost*D['Number of clarifiers'] - add_OPEX['Pump operating'] = opex_o*D['Number of clarifiers'] - add_OPEX['Pump maintenance'] = opex_m*D['Number of clarifiers'] + N = D['Number of clarifiers'] + C['Pumps'] = pump_cost*N + C['Pump building'] = building_cost*N + add_OPEX['Pump operating'] = opex_o*N + add_OPEX['Pump maintenance'] = opex_m*N # Power pumping = 0. @@ -1771,8 +1602,6 @@ def _cost(self): if p is None: continue pumping += p.power_utility.rate - - pumping = pumping*D['Number of clarifiers'] - - self.power_utility.rate += pumping + + self.power_utility.rate += pumping*N # self.power_utility.rate += scraper_power From 8e27f89d0e1a3657a39afc29c35386434d0f2432 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Fri, 10 May 2024 16:23:46 -0700 Subject: [PATCH 333/424] clean up `ADM1_p_extension` --- .../data/process_data/_adm1_p_extension.tsv | 7 +- qsdsan/processes/__init__.py | 1 + qsdsan/processes/_adm1.py | 4 +- qsdsan/processes/_adm1_p_extension.py | 704 +++++------------- qsdsan/sanunits/_junction.py | 120 ++- 5 files changed, 248 insertions(+), 588 deletions(-) diff --git a/qsdsan/data/process_data/_adm1_p_extension.tsv b/qsdsan/data/process_data/_adm1_p_extension.tsv index 64b881ff..5b91bb98 100644 --- a/qsdsan/data/process_data/_adm1_p_extension.tsv +++ b/qsdsan/data/process_data/_adm1_p_extension.tsv @@ -17,5 +17,10 @@ decay_Xc4 ? ? ? f_sI_xb f_ch_xb f_pr_xb f_li_xb -1 f_xI_xb decay_Xpro ? ? ? f_sI_xb f_ch_xb f_pr_xb f_li_xb -1 f_xI_xb decay_Xac ? ? ? f_sI_xb f_ch_xb f_pr_xb f_li_xb -1 f_xI_xb decay_Xh2 ? ? ? f_sI_xb f_ch_xb f_pr_xb f_li_xb -1 f_xI_xb +storage_Sva_in_XPHA -1 ? ? ? 1 -Y_PO4 Y_PO4*K_XPP Y_PO4*Mg_XPP +storage_Sbu_in_XPHA -1 ? ? ? 1 -Y_PO4 Y_PO4*K_XPP Y_PO4*Mg_XPP +storage_Spro_in_XPHA -1 ? ? ? 1 -Y_PO4 Y_PO4*K_XPP Y_PO4*Mg_XPP +storage_Sac_in_XPHA -1 ? ? ? 1 -Y_PO4 Y_PO4*K_XPP Y_PO4*Mg_XPP lysis_XPAO ? ? ? f_sI_xb f_ch_xb f_pr_xb f_li_xb f_xI_xb -1 -lysis_XPHA f_va_PHA f_bu_PHA f_pro_PHA f_ac_PHA ? ? ? -1 \ No newline at end of file +lysis_XPP ? ? ? -1 K_XPP Mg_XPP +lysis_XPHA f_va_PHA f_bu_PHA f_pro_PHA f_ac_PHA ? ? ? -1 diff --git a/qsdsan/processes/__init__.py b/qsdsan/processes/__init__.py index 394a97f8..3566c3ed 100644 --- a/qsdsan/processes/__init__.py +++ b/qsdsan/processes/__init__.py @@ -32,6 +32,7 @@ _madm1, _decay, _kinetic_reaction, + _pm2 ) __all__ = ( diff --git a/qsdsan/processes/_adm1.py b/qsdsan/processes/_adm1.py index d7cbfbd4..fb940b13 100644 --- a/qsdsan/processes/_adm1.py +++ b/qsdsan/processes/_adm1.py @@ -24,10 +24,10 @@ __all__ = ('create_adm1_cmps', 'ADM1', 'non_compet_inhibit', 'substr_inhibit', - 'T_correction_factor', + 'mass2mol_conversion', 'T_correction_factor', 'pH_inhibit', 'Hill_inhibit', 'rhos_adm1', - 'solve_pH', + 'solve_pH', 'TempState', 'dydt_Sh2_AD', 'grad_dydt_Sh2_AD') _path = ospath.join(data_path, 'process_data/_adm1.tsv') diff --git a/qsdsan/processes/_adm1_p_extension.py b/qsdsan/processes/_adm1_p_extension.py index 2bbcedf2..5c6d757c 100644 --- a/qsdsan/processes/_adm1_p_extension.py +++ b/qsdsan/processes/_adm1_p_extension.py @@ -19,15 +19,25 @@ from thermosteam import settings from chemicals.elements import molecular_weight as get_mw from qsdsan import Component, Components, Process, Processes, CompiledProcesses -import numpy as np +from qsdsan.processes import ( + create_adm1_cmps, + create_asm2d_cmps, + T_correction_factor, + non_compet_inhibit, + substr_inhibit, + mass2mol_conversion, + Hill_inhibit, + ADM1, + TempState + # _rhos_adm1, + ) from qsdsan.utils import ospath, data_path from scipy.optimize import brenth from warnings import warn +import numpy as np -__all__ = ('create_adm1_p_extension_cmps', 'ADM1_p_extension', - 'non_compet_inhibit', 'substr_inhibit', - 'T_correction_factor', - 'pH_inhibit', 'Hill_inhibit', +__all__ = ('create_adm1_p_extension_cmps', + 'ADM1_p_extension', 'rhos_adm1_p_extension') _path = ospath.join(data_path, 'process_data/_adm1_p_extension.tsv') @@ -43,157 +53,34 @@ P_mw = get_mw({'P':1}) def create_adm1_p_extension_cmps(set_thermo=True): - cmps_all = Components.load_default() - - # varies - # X_c = cmps_all.X_OHO.copy('X_c') - # X_c.description = 'Composite' - # X_c.i_C = 0.02786 * C_mw - # X_c.i_N = 0.0376 + c1 = create_adm1_cmps(False) + c2d = create_asm2d_cmps(False) - X_ch = Component.from_chemical('X_ch', chemical='glycogen', Tc=1011.4, # glucan - description='Carbohydrates', - measured_as='COD', - particle_size='Particulate', - degradability='Slowly', - organic=True) - # X_ch = cmps_all.X_B_Subst.copy('X_ch') - # X_ch.i_N = 0 - # X_ch.i_C = 0.0313 * C_mw - - # varies - X_pr = cmps_all.X_B_Subst.copy('X_pr') - X_pr.i_N = 0.007 * N_mw - X_pr.i_C = 0.03 * C_mw - - X_li = Component.from_chemical('X_li', chemical='tripalmitin', - description='Lipids', - measured_as='COD', - particle_size='Particulate', - degradability='Slowly', - organic=True) - - # both varies - X_I = cmps_all.X_U_Inf.copy('X_I') - S_I = cmps_all.S_U_Inf.copy('S_I') - X_I.i_C = S_I.i_C = 0.03 * C_mw - X_I.i_N = S_I.i_N = 0.06 - - S_su = Component.from_chemical('S_su', chemical='glucose', - description='Monosaccharides', - measured_as='COD', - particle_size='Soluble', - degradability='Readily', - organic=True) - # S_su = cmps_all.S_F.copy('S_su') - # S_su.i_N = 0 - # S_su.i_C = 0.0313 * 12 - - # varies - S_aa = cmps_all.S_F.copy('S_aa') - S_aa.i_N = 0.007 * N_mw - S_aa.i_P = 0 - S_aa.i_C = 0.03 * C_mw - - S_fa = Component.from_chemical('S_fa', chemical='palmitate', - description='Total long-chain fatty acids', - measured_as='COD', - particle_size='Soluble', - degradability='Readily', - organic=True) - - S_va = Component.from_chemical('S_va', chemical='valerate', - description='Total valerate', - measured_as='COD', - particle_size='Soluble', - degradability='Readily', - organic=True) - - S_bu = Component.from_chemical('S_bu', chemical='butyrate', - description='Total butyrate', - measured_as='COD', - particle_size='Soluble', - degradability='Readily', - organic=True) - - S_pro = cmps_all.S_Prop.copy('S_pro') - S_ac = cmps_all.S_Ac.copy('S_ac') - S_h2 = cmps_all.S_H2.copy('S_h2') - S_ch4 = cmps_all.S_CH4.copy('S_ch4') - - S_IC = Component.from_chemical('S_IC', chemical='CO2', - measured_as='C', - description='Inorganic carbon', - particle_size='Dissolved gas', - degradability='Undegradable', - organic=False) - S_IC.copy_models_from(S_ch4, ('Cn',)) - - S_IN = Component.from_chemical('S_IN', chemical='NH3', - measured_as='N', - description='Inorganic nitrogen', - particle_size='Soluble', - degradability='Undegradable', - organic=False) - - S_IP = cmps_all.S_PO4.copy('S_IP') - - X_su = cmps_all.X_FO.copy('X_su') - X_su.description = 'Biomass uptaking sugars' - - X_aa = cmps_all.X_FO.copy('X_aa') - X_aa.description = 'Biomass uptaking amino acids' - - X_fa = cmps_all.X_FO.copy('X_fa') - X_fa.description = 'Biomass uptaking long chain fatty acids' - - X_c4 = cmps_all.X_FO.copy('X_c4') - X_c4.description = 'Biomass uptaking c4 fatty acids' - - X_pro = cmps_all.X_PRO.copy('X_pro') - X_ac = cmps_all.X_ACO.copy('X_ac') - X_h2 = cmps_all.X_HMO.copy('X_h2') - - X_PHA = cmps_all.X_PAO_PHA.copy('X_PHA') - - X_PP = cmps_all.X_PAO_PP_Lo.copy('X_PP') - - X_PAO = cmps_all.X_PAO.copy('X_PAO') + S_IP = c2d.S_PO4.copy('S_IP') S_K = Component.from_chemical('S_K', chemical='K', - measured_as='K', - description='Potassium', - particle_size='Soluble', - degradability='Undegradable', - organic=False) + measured_as='K', + description='Potassium', + particle_size='Soluble', + degradability='Undegradable', + organic=False) S_Mg = Component.from_chemical('S_Mg', chemical='Mg', - measured_as='Mg', - description='Magnesium', - particle_size='Soluble', - degradability='Undegradable', - organic=False) + measured_as='Mg', + description='Magnesium', + particle_size='Soluble', + degradability='Undegradable', + organic=False) - # X_MeOH and X_MeP are added at a later iteration of p_extension ADM1. - # They do not participate in any ADM1 process, and would be directly - # mapped to ASM2d components at the ASM-ADM-ASM interface. - X_MeOH = cmps_all.X_FeOH.copy('X_MeOH') - X_MeP = cmps_all.X_FePO4.copy('X_MeP') - - for bio in (X_su, X_aa, X_fa, X_c4, X_pro, X_ac, X_h2): - # bio.formula = 'C5H7O2N' - bio.i_C = 0.0313 * C_mw - bio.i_N = 0.08 - - S_cat = cmps_all.S_CAT.copy('S_cat') - S_an = cmps_all.S_AN.copy('S_an') - S_cat.i_mass = S_an.i_mass = 1 - - cmps_adm1_p_extension = Components([S_su, S_aa, S_fa, S_va, S_bu, S_pro, S_ac, S_h2, - S_ch4, S_IC, S_IN, S_IP, S_I, X_ch, X_pr, X_li, - X_su, X_aa, X_fa, X_c4, X_pro, X_ac, X_h2, X_I, - X_PHA, X_PP, X_PAO, S_K, S_Mg, X_MeOH, X_MeP, - S_cat, S_an, cmps_all.H2O]) + c = [*c1] + Ss = c[:11] + Xs = c[13:-3] # X_c is excluded + others = c[-3:] + + cmps_adm1_p_extension = Components([*Ss, S_IP, c1.S_I, + *Xs, c2d.X_PHA, c2d.X_PP, c2d.X_PAO, + S_K, S_Mg, c2d.X_MeOH, c2d.X_MeP, + *others]) cmps_adm1_p_extension.default_compile() if set_thermo: settings.set_thermo(cmps_adm1_p_extension) return cmps_adm1_p_extension @@ -208,67 +95,47 @@ def create_adm1_p_extension_cmps(set_thermo=True): R = 8.3145e-2 # Universal gas constant, [bar/M/K] -def non_compet_inhibit(Si, Ki): - return Ki/(Ki+Si) - -def substr_inhibit(Si, Ki): - return Si/(Ki+Si) - -def mass2mol_conversion(cmps): - '''conversion factor from kg[measured_as]/m3 to mol[component]/L''' - return cmps.i_mass / cmps.chem_MW - -# def T_correction_factor(T1, T2, theta): -# return np.exp(theta * (T2-T1)) - -def T_correction_factor(T1, T2, delta_H): - return np.exp(delta_H/(R*100) * (1/T1 - 1/T2)) # R converted to SI - -# def calc_Kas(pKas, T_base, T_op, theta): -# pKas = np.asarray(pKas) -# return 10**(-pKas) * T_correction_factor(T_base, T_op, theta) def acid_base_rxn(h_ion, weak_acids_tot, Kas): - # h, nh4, hco3, ac, pr, bu, va = mols # S_cat, S_K, S_Mg, S_an, S_IN, S_IP, S_IC, S_ac, S_pro, S_bu, S_va = weak_acids_tot # in M S_cat, S_K, S_Mg, S_an, S_IN, S_IP = weak_acids_tot[:6] # Kw, Ka_nh, Ka_h2po4, Ka_co2, Ka_ac, Ka_pr, Ka_bu, Ka_va = Kas Kw = Kas[0] oh_ion = Kw/h_ion - nh3, h2po4, hco3, ac, pro, bu, va = Kas[1:] * weak_acids_tot[4:] / (Kas[1:] + h_ion) - return S_cat + S_K + 2*S_Mg + h_ion + (S_IN - nh3) - S_an - oh_ion - hco3 - ac - pro - bu - va - (3*S_IP + h2po4) + nh3, hpo4, hco3, ac, pro, bu, va = Kas[1:] * weak_acids_tot[4:] / (Kas[1:] + h_ion) + return S_cat + S_K + 2*S_Mg + h_ion + (S_IN - nh3) - S_an - oh_ion - hco3 - ac - pro - bu - va - 2*hpo4 - (S_IP - hpo4) # The function 'fprime_abr' is not used in the code def fprime_abr(h_ion, weak_acids_tot, Kas): - S_cat, S_K, S_Mg, S_an, S_IN, S_IP = weak_acids_tot[:6] + # S_cat, S_K, S_Mg, S_an, S_IN, S_IP = weak_acids_tot[:6] Kw = Kas[0] doh_ion = - Kw / h_ion ** 2 - dnh3, dh2po4, dhco3, dac, dpro, dbu, dva = - Kas[1:] * weak_acids_tot[4:] / (Kas[1:] + h_ion)**2 - return 1 + (-dnh3) - dh2po4 - doh_ion - dhco3 - dhco3 - dac - dpro - dbu - dva - -def pH_inhibit(pH, ul, ll, lower_only=True): - if lower_only: - # if pH >= ul: return 1 - # else: return exp(-3 * ((pH-ul)/(ul-ll))**2) - low_by = np.minimum(pH-ul, 0) - return np.exp(-3 * (low_by/(ul-ll))**2) - else: - return (1+2*10**(0.5*(ll-ul)))/(1+10**(pH-ul)+10**(ll-pH)) + dnh3, dhpo4, dhco3, dac, dpro, dbu, dva = - Kas[1:] * weak_acids_tot[4:] / (Kas[1:] + h_ion)**2 + return 1 + (-dnh3) - doh_ion - dhco3 - dac - dpro - dbu - dva - dhpo4 -def Hill_inhibit(H_ion, ul, ll): - n = 3/(ul-ll) - K = 10**(-(ul+ll)/2) - return 1/(1+(H_ion/K) ** n) rhos = np.zeros(28) # 28 kinetic processes (25 as defined in modified ADM1 + 3 for gases) Cs = np.empty(25) # 25 processes as defined in modified ADM1 -def rhos_adm1_p_extension(state_arr, params): +def solve_pH(state_arr, Ka, unit_conversion): + cmps_in_M = state_arr[:34] * unit_conversion + # S_cat, S_K, S_Mg, S_an, S_IN, S_IP, S_IC, S_ac, S_pro, S_bu, S_va + # Kw, Ka_nh, Ka_h2po4, Ka_co2, Ka_ac, Ka_pr, Ka_bu, Ka_va = Kas + weak_acids = cmps_in_M[[31, 27, 28, 32, 10, 11, 9, 6, 5, 4, 3]] + h = brenth(acid_base_rxn, 1e-14, 1.0, + args=(weak_acids, Ka), + xtol=1e-12, maxiter=100) + nh3 = Ka[1] * weak_acids[4] / (Ka[1] + h) + co2 = weak_acids[6] - Ka[3] * weak_acids[6] / (Ka[3] + h) + return h, nh3, co2 + +rhos_adm1_p_extension = lambda state_arr, params: _rhos_adm1_p_extension(state_arr, params, h=None) + +def _rhos_adm1_p_extension(state_arr, params, h=None): ks = params['rate_constants'] Ks = params['half_sat_coeffs'] cmps = params['components'] - # n = len(cmps) pH_ULs = params['pH_ULs'] pH_LLs = params['pH_LLs'] KS_IN = params['KS_IN'] @@ -283,101 +150,122 @@ def rhos_adm1_p_extension(state_arr, params): T_base = params['T_base'] root = params['root'] - # state_arr_cmps stated just for readability of code - # state_arr_cmps = [S_su, S_aa, S_fa, S_va, S_bu, S_pro, S_ac, S_h2, S_ch4, S_IC, S_IN, - # S_IP, S_I, X_ch, X_pr, X_li, X_su, X_aa, X_fa, X_c4, X_pro, X_ac, - # X_h2, X_I, X_PHA, X_PP, X_PAO, S_K, S_Mg, X_MeOH, X_MeP] + if 'unit_conv' not in params: params['unit_conv'] = mass2mol_conversion(cmps) + unit_conversion = params['unit_conv'] - # Cs_ids = cmps.indices(['X_ch', 'X_pr', 'X_li', 'X_su', 'X_aa', - # 'X_fa', 'X_c4', 'X_c4', 'X_pro', 'X_ac', 'X_h2', - # 'X_su', 'X_aa', 'X_fa', 'X_c4', 'X_pro', 'X_ac', 'X_h2', - # 'X_PAO', 'X_PAO', 'X_PAO', 'X_PAO', 'X_PAO', 'X_PP', 'X_PHA']) - # Cs = state_arr[Cs_ids] + # state_arr_cmps stated just for readability of code + # {0: 'S_su', + # 1: 'S_aa', + # 2: 'S_fa', + # 3: 'S_va', + # 4: 'S_bu', + # 5: 'S_pro', + # 6: 'S_ac', + # 7: 'S_h2', + # 8: 'S_ch4', + # 9: 'S_IC', + # 10: 'S_IN', + # 11: 'S_IP', + # 12: 'S_I', + # 13: 'X_ch', + # 14: 'X_pr', + # 15: 'X_li', + # 16: 'X_su', + # 17: 'X_aa', + # 18: 'X_fa', + # 19: 'X_c4', + # 20: 'X_pro', + # 21: 'X_ac', + # 22: 'X_h2', + # 23: 'X_I', + # 24: 'X_PHA', + # 25: 'X_PP', + # 26: 'X_PAO', + # 27: 'S_K', + # 28: 'S_Mg', + # 29: 'X_MeOH', + # 30: 'X_MeP', + # 31: 'S_cat', + # 32: 'S_an', + # 33: 'H2O'} + Cs[:7] = state_arr[13:20] Cs[7:11] = state_arr[19:23] Cs[11:18] = state_arr[16:23] - Cs[18:23] = state_arr[26] - Cs[23] = state_arr[25] + Cs[18:23] = X_PAO = state_arr[26] + Cs[23] = X_PP = state_arr[25] Cs[24] = state_arr[24] - # substrates_ids = cmps.indices(['S_su', 'S_aa', 'S_fa', 'S_va', - # 'S_bu', 'S_pro', 'S_ac', 'S_h2']) - # substrates = state_arr[substrates_ids] substrates = state_arr[:8] - # S_va, S_bu, S_h2, S_IN = state_arr[cmps.indices(['S_va', 'S_bu', 'S_h2', 'S_IN'])] - # S_va, S_bu, S_h2, S_ch4, S_IC, S_IN = state_arr[[3,4,7,8,9,10]] S_va, S_bu, S_h2, S_IN, S_IP = state_arr[[3,4,7,10,11]] - unit_conversion = mass2mol_conversion(cmps) - # Should I let X_MeOH, and X_MeP unit convert? (ask Joy) - cmps_in_M = state_arr[:34] * unit_conversion - # weak acids (ADM1) = [S_ca, S_an, S_IN, S_IC, S_ac, S_pro, S_bu, S_va] - # weak acids (modified_ADM1) = [S_ca, S_K, S_Mg, S_an, S_IN, S_IP, S_IC, S_ac, S_pro, S_bu, S_va] - weak_acids = cmps_in_M[[31, 27, 28, 32, 10, 11, 9, 6, 5, 4, 3]] - + T_op = state_arr[-1] - biogas_S = state_arr[7:10].copy() - biogas_p = R * T_op * state_arr[34:37] - Kas = Kab * T_correction_factor(T_base, T_op, Ka_dH) - KH = KHb * T_correction_factor(T_base, T_op, KH_dH) / unit_conversion[7:10] + if T_op == T_base: + Ka = Kab + KH = KHb / unit_conversion[7:10] + else: + T_temp = params.pop('T_op', None) + if T_op != T_temp: + params['Ka'] = Kab * T_correction_factor(T_base, T_op, Ka_dH) + params['KH'] = KHb * T_correction_factor(T_base, T_op, KH_dH) / unit_conversion[7:10] + params['T_op'] = T_op + Ka = params['Ka'] + KH = params['KH'] rhos[:-3] = ks * Cs - rhos[3:11] *= substr_inhibit(substrates, Ks[0:8]) + rhos[3:11] *= substr_inhibit(substrates, Ks[:8]) if S_va > 0: rhos[6] *= 1/(1+S_bu/S_va) if S_bu > 0: rhos[7] *= 1/(1+S_va/S_bu) - # substrates_ids = cmps.indices(['S_va', 'S_bu', 'S_pro', 'S_ac']) - # substrates_modified = state_arr[substrates_ids] - substrates_modified = state_arr[3:7] - - - K_a = Ks[-2] - rhos[18:22] *= substr_inhibit(substrates_modified, K_a) + vfas = state_arr[3:7] - K_pp = Ks[-1] - S_conc = state_arr[25] - K_half_sat = K_pp*state_arr[26] + if X_PAO > 0: + K_A, K_PP = Ks[-2:] + rhos[18:22] *= substr_inhibit(vfas, K_A) * substr_inhibit(X_PP/X_PAO, K_PP) - rhos[18:22] *= substr_inhibit(S_conc, K_half_sat) + if sum(vfas) > 0: + rhos[18:22] *= vfas/sum(vfas) - # Multiplication by {Sva, Sbu, Spro, Sac}/(Sva + Sbu + Spro + Sac) - transformation_array = state_arr[3:7]/sum(state_arr[3:7]) - rhos[18:22] *= transformation_array - - h = brenth(acid_base_rxn, 1e-14, 1.0, - args=(weak_acids, Kas), - xtol=1e-12, maxiter=100) + biogas_S = state_arr[7:10].copy() + biogas_p = R * T_op * state_arr[34:37] - nh3 = Kas[1] * weak_acids[4] / (Kas[1] + h) - co2 = weak_acids[6] - Kas[2] * weak_acids[6] / (Kas[2] + h) + if h is None: + h, nh3, co2 = solve_pH(state_arr, Ka, unit_conversion) + else: + nh3 = Ka[1] * S_IN * unit_conversion[10] / (Ka[1] + h) + S_IC = state_arr[9] * unit_conversion[9] + co2 = S_IC - Ka[3] * S_IC / (Ka[3] + h) biogas_S[-1] = co2 / unit_conversion[9] Iph = Hill_inhibit(h, pH_ULs, pH_LLs) + Iin = substr_inhibit(S_IN, KS_IN) + Iip = substr_inhibit(S_IP, KS_IP) Ih2 = non_compet_inhibit(S_h2, KIs_h2) - root.data = [-np.log10(h), Iph, Ih2] - rhos[3:11] *= Iph * substr_inhibit(S_IN, KS_IN) * substr_inhibit(S_IP, KS_IP) + Inh3 = non_compet_inhibit(nh3, KI_nh3) + root.data = { + 'pH':-np.log10(h), + 'Iph':Iph, + 'Ih2':Ih2, + 'Iin':Iin, + 'Inh3':Inh3, + } + rhos[3:11] *= Iph * Iin * Iip rhos[5:9] *= Ih2 - # rhos[4:12] *= Hill_inhibit(h, pH_ULs, pH_LLs) * substr_inhibit(S_IN, KS_IN) - # rhos[6:10] *= non_compet_inhibit(S_h2, KIs_h2) - rhos[9] *= non_compet_inhibit(nh3, KI_nh3) + rhos[9] *= Inh3 rhos[-3:] = kLa * (biogas_S - KH * biogas_p) + # print(rhos) return rhos #%% # ============================================================================= # ADM1_p_extension class # ============================================================================= -class TempState: - def __init__(self): - self.data = [] - - # def append(self, value): - # self.data += [value] @chemicals_user -class ADM1_p_extension(CompiledProcesses): +class ADM1_p_extension(ADM1): """ - Anaerobic Digestion Model No.1. [1]_, [2]_, [3]_ + Anaerobic Digestion Model No.1 with P extension. [1]_, [2]_, [3]_ Parameters ---------- @@ -386,10 +274,6 @@ class ADM1_p_extension(CompiledProcesses): defaults to thermosteam.settings.chemicals. path : str, optional Alternative file path for the Petersen matrix. The default is None. - N_I : float, optional - Nitrogen content of inert organics [kmol N/kg COD]. The default is 4.286e-3. - N_aa : float, optional - Nitrogen content of amino acids [kmol N/kg COD]. The default is 7e-3. f_sI_xb : float, optional fraction of soluble inerts from biomass. The default is 0. f_ch_xb : float, optional @@ -400,33 +284,6 @@ class ADM1_p_extension(CompiledProcesses): fraction of lipids from biomass. The default is 0.35. f_xI_xb : float, optional fraction of particulate inerts from biomass. The default is 0.1. - f_fa_li : float, optional - Fraction of long chain fatty acids (LCFAs) from hydrolysis of lipids - [kg COD/kg COD]. The default is 0.95. - f_bu_su : float, optional - Fraction of butyrate from sugars [kg COD/kg COD]. The default is 0.13. - f_pro_su : float, optional - Fraction of propionate from sugars [kg COD/kg COD]. The default is 0.27. - f_ac_su : float, optional - Fraction of acetate from sugars [kg COD/kg COD]. The default is 0.41. - f_va_aa : float, optional - Fraction of valerate from amino acids [kg COD/kg COD]. The default is 0.23. - f_bu_aa : float, optional - Fraction of butyrate from amino acids [kg COD/kg COD]. The default is 0.26. - f_pro_aa : float, optional - Fraction of propionate from amino acids [kg COD/kg COD]. The default is 0.05. - f_ac_aa : float, optional - Fraction of acetate from amino acids [kg COD/kg COD]. The default is 0.4. - f_ac_fa : float, optional - Fraction of acetate from LCFAs [kg COD/kg COD]. The default is 0.7. - f_pro_va : float, optional - Fraction of propionate from LCFAs [kg COD/kg COD]. The default is 0.54. - f_ac_va : float, optional - Fraction of acetate from valerate [kg COD/kg COD]. The default is 0.31. - f_ac_bu : float, optional - Fraction of acetate from butyrate [kg COD/kg COD]. The default is 0.8. - f_ac_pro : float, optional - Fraction of acetate from propionate [kg COD/kg COD]. The default is 0.57. f_ac_PHA : float, optional Yield of acetate on PHA [kg COD/kg COD]. The default is 0.4. f_bu_PHA : float, optional @@ -435,92 +292,13 @@ class ADM1_p_extension(CompiledProcesses): Yield of propionate on PHA [kg COD/kg COD]. The default is 0.4. f_va_PHA : float, optional Yield of valerate on PHA [kg COD/kg COD]. The default is 0.1. - Y_su : float, optional - Biomass yield of sugar uptake [kg COD/kg COD]. The default is 0.1. - Y_aa : float, optional - Biomass yield of amino acid uptake [kg COD/kg COD]. The default is 0.08. - Y_fa : float, optional - Biomass yield of LCFA uptake [kg COD/kg COD]. The default is 0.06. - Y_c4 : float, optional - Biomass yield of butyrate or valerate uptake [kg COD/kg COD]. - The default is 0.06. - Y_pro : float, optional - Biomass yield of propionate uptake [kg COD/kg COD]. The default is 0.04. - Y_ac : float, optional - Biomass yield of acetate uptake [kg COD/kg COD]. The default is 0.05. - Y_h2 : float, optional - Biomass yield of H2 uptake [kg COD/kg COD]. The default is 0.06. - Y_po4 : float, optional + Y_PO4 : float, optional Yield of biomass on phosphate [kmol P/kg COD]. The default is 0.013. - q_dis : float, optional - Composites disintegration rate constant [d^(-1)]. The default is 0.5. - q_ch_hyd : float, optional - Carbohydrate hydrolysis rate constant [d^(-1)]. The default is 10. - q_pr_hyd : float, optional - Protein hydrolysis rate constant [d^(-1)]. The default is 10. - q_li_hyd : float, optional - Lipid hydrolysis rate constant [d^(-1)]. The default is 10. - k_su : float, optional - Sugar uptake rate constant [d^(-1)]. The default is 30. - k_aa : float, optional - Amino acid uptake rate constant [d^(-1)]. The default is 50. - k_fa : float, optional - LCFA uptake rate constant [d^(-1)]. The default is 6. - k_c4 : float, optional - Butyrate or valerate uptake rate constant [d^(-1)]. The default is 20. - k_pro : float, optional - Propionate uptake rate constant [d^(-1)]. The default is 13. - k_ac : float, optional - Acetate uptake rate constant [d^(-1)]. The default is 8. - k_h2 : float, optional - H2 uptake rate constant [d^(-1)]. The default is 35. - K_su : float, optional - Half saturation coefficient of sugar uptake [kg COD/m3]. - The default is 0.5. - K_aa : float, optional - Half saturation coefficient of amino acid uptake [kg COD/m3]. - The default is 0.3. - K_fa : float, optional - Half saturation coefficient of LCFA uptake [kg COD/m3]. - The default is 0.4. - K_c4 : float, optional - Half saturation coefficient of butyrate or valerate uptake [kg COD/m3]. - The default is 0.2. - K_pro : float, optional - Half saturation coefficient of propionate uptake [kg COD/m3]. - The default is 0.1. - K_ac : float, optional - Half saturation coefficient of acetate uptake [kg COD/m3]. - The default is 0.15. - K_h2 : float, optional - Half saturation coefficient of H2 uptake [kg COD/m3]. - The default is 7e-6. - K_a : float, optional - Saturation coefficient for acetate [kg COD/m3]. - The default is 0.004. - K_pp : float, optional - Saturation coefficient for polyphosphate [kg COD/m3]. + K_A : float, optional + VFAs half saturation coefficient for PHA storage [kg COD/m3]. The default is 0.004. + K_PP : float, optional + Half saturation coefficient for polyphosphate [kmol PP/kg PAO COD]. The default is 0.00032. - b_su : float, optional - Decay rate constant of sugar-uptaking biomass [d^(-1)]. - The default is 0.02. - b_aa : float, optional - Decay rate constant of amino-acid-uptaking biomass [d^(-1)]. - The default is 0.02. - b_fa : float, optional - Decay rate constant of LCFA-uptaking biomass [d^(-1)]. - The default is 0.02. - b_c4 : float, optional - Decay rate constant of valerate- or butyrate-uptaking biomass [d^(-1)]. - The default is 0.02. - b_pro : float, optional - Decay rate constant of propionate-uptaking biomass [d^(-1)]. - The default is 0.02. - b_ac : float, optional - Decay rate constant of acetate-uptaking biomass [d^(-1)]. - The default is 0.02. - b_h2 : float, optional - Decay rate constant of H2-uptaking biomass [d^(-1)]. The default is 0.02. q_PHA : float, optional Rate constant for storage of PHA [d^(-1)]. The default is 3. b_PAO : float, optional @@ -529,54 +307,21 @@ class ADM1_p_extension(CompiledProcesses): Lysis rate of polyphosphates [d^(-1)]. The default is 0.2. b_PHA : float, optional Lysis rate of PHAs [d^(-1)]. The default is 0.2. - KI_h2_fa : float, optional - H2 inhibition coefficient for LCFA uptake [kg COD/m3]. The default is 5e-6. - KI_h2_c4 : float, optional - H2 inhibition coefficient for butyrate or valerate uptake [kg COD/m3]. - The default is 1e-5. - KI_h2_pro : float, optional - H2 inhibition coefficient for propionate uptake [kg COD/m3]. - The default is 3.5e-6. - KI_nh3 : float, optional - Free ammonia inhibition coefficient for acetate uptake [M]. - The default is 1.8e-3. - KS_IN : float, optional - Inorganic nitrogen (nutrient) inhibition coefficient for soluble - substrate uptake [M]. The default is 1e-4. KS_IP : float, optional P limitation for inorganic phosphorous [kmol P/m3]. The default is 2e-5. - pH_limits_aa : 2-tuple, optional - Lower and upper limits of pH inhibition for acidogens and acetogens, - unitless. The default is (4,5.5). - pH_limits_ac : 2-tuple, optional - Lower and upper limits of pH inhibition for aceticlastic methanogens, - unitless. The default is (6,7). - pH_limits_h2 : 2-tuple, optional - Lower and upper limits of pH inhibition for H2-utilizing methanogens, - unitless. The default is (5,6). - T_base : float, optional - Base temperature for kinetic parameters [K]. The default is 298.15. pKa_base : iterable[float], optional pKa (equilibrium coefficient) values of acid-base pairs at the base - temperature, unitless, following the order of `ADM1._acid_base_pairs`. + temperature, unitless, following the order of `ADM1_p_extension._acid_base_pairs`. The default is [14, 9.25, 7.20, 6.35, 4.76, 4.88, 4.82, 4.86]. Ka_dH : iterable[float], optional Heat of reaction of each acid-base pair at base temperature [J/mol], - following the order of `ADM1._acid_base_pairs`. The default is + following the order of `ADM1_p_extension._acid_base_pairs`. The default is [55900, 51965, 3600, 7646, 0, 0, 0, 0]. - ('H+', 'OH-'), ('NH4+', 'NH3'), ('H2PO4 2-', 'HPO4 2-'), ('CO2', 'HCO3-'), - ('HAc', 'Ac-'), ('HPr', 'Pr-'), ('HBu', 'Bu-'), ('HVa', 'Va-') - kLa : float, optional - Liquid-gas mass transfer coefficient [d^(-1)]. The default is 200. - K_H_base : iterable[float], optional - Henry's Law coefficients of biogas species at the base temperature - [M dissolved in liquid/bar]. Follows the order of `ADM1._biogas_IDs`. - The default is [7.8e-4, 1.4e-3, 3.5e-2]. - K_H_dH : iterable[float], optional - Heat of reaction of liquid-gas transfer of biogas species [J/mol]. - Follows the order of `ADM1._biogas_IDs`. The default is - [-4180, -14240, -19410]. + + See Also + -------- + :class:`qsdsan.processes.ADM1` Examples -------- @@ -601,21 +346,14 @@ class ADM1_p_extension(CompiledProcesses): 95, 370–382. """ - _stoichio_params = ('f_sI_xb', 'f_ch_xb', 'f_pr_xb', 'f_li_xb', 'f_xI_xb', - 'f_fa_li', 'f_bu_su', 'f_pro_su', 'f_ac_su', 'f_h2_su', - 'f_va_aa', 'f_bu_aa', 'f_pro_aa', 'f_ac_aa', 'f_h2_aa', - 'f_ac_fa', 'f_h2_fa', 'f_pro_va', 'f_ac_va', 'f_h2_va', - 'f_ac_bu', 'f_h2_bu', 'f_ac_pro', 'f_h2_pro', + _stoichio_params = (*ADM1._stoichio_params[5:], + 'f_sI_xb', 'f_ch_xb', 'f_pr_xb', 'f_li_xb', 'f_xI_xb', 'f_ac_PHA', 'f_bu_PHA', 'f_pro_PHA', 'f_va_PHA', - 'Y_su', 'Y_aa', 'Y_fa', 'Y_c4', 'Y_pro', 'Y_ac', 'Y_h2', 'Y_po4', - 'K_XPP', 'Mg_XPP') + 'Y_PO4', 'K_XPP', 'Mg_XPP') - _kinetic_params = ('rate_constants', 'half_sat_coeffs', 'pH_ULs', 'pH_LLs', - 'KS_IN', 'KS_IP', 'KI_nh3', 'KIs_h2', - 'Ka_base', 'Ka_dH', 'K_H_base', 'K_H_dH', 'kLa', - 'T_base', 'components', 'root') + _kinetic_params = (*ADM1._kinetic_params, 'KS_IP', ) - _acid_base_pairs = (('H+', 'OH-'), ('NH4+', 'NH3'), ('H2PO4-', 'HPO4 -2'), + _acid_base_pairs = (('H+', 'OH-'), ('NH4+', 'NH3'), ('H2PO4-', 'HPO4-2'), ('CO2', 'HCO3-'), ('HAc', 'Ac-'), ('HPr', 'Pr-'), ('HBu', 'Bu-'), ('HVa', 'Va-')) @@ -627,10 +365,11 @@ def __new__(cls, components=None, path=None, N_xc=2.686e-3, N_I=4.286e-3, N_aa=7 f_va_aa=0.23, f_bu_aa=0.26, f_pro_aa=0.05, f_ac_aa=0.4, f_ac_fa=0.7, f_pro_va=0.54, f_ac_va=0.31, f_ac_bu=0.8, f_ac_pro=0.57, f_ac_PHA=0.4, f_bu_PHA=0.1, f_pro_PHA=0.4, - Y_su=0.1, Y_aa=0.08, Y_fa=0.06, Y_c4=0.06, Y_pro=0.04, Y_ac=0.05, Y_h2=0.06, Y_po4=0.013, + Y_su=0.1, Y_aa=0.08, Y_fa=0.06, Y_c4=0.06, Y_pro=0.04, Y_ac=0.05, Y_h2=0.06, Y_PO4=0.013, q_dis=0.5, q_ch_hyd=10, q_pr_hyd=10, q_li_hyd=10, k_su=30, k_aa=50, k_fa=6, k_c4=20, k_pro=13, k_ac=8, k_h2=35, - K_su=0.5, K_aa=0.3, K_fa=0.4, K_c4=0.2, K_pro=0.1, K_ac=0.15, K_h2=7e-6, K_a=4e-3, K_pp=32e-5, + K_su=0.5, K_aa=0.3, K_fa=0.4, K_c4=0.2, K_pro=0.1, K_ac=0.15, K_h2=7e-6, + K_A=4e-3, K_PP=32e-5, b_su=0.02, b_aa=0.02, b_fa=0.02, b_c4=0.02, b_pro=0.02, b_ac=0.02, b_h2=0.02, q_PHA=3, b_PAO=0.2, b_PP=0.2, b_PHA=0.2, KI_h2_fa=5e-6, KI_h2_c4=1e-5, KI_h2_pro=3.5e-6, KI_nh3=1.8e-3, KS_IN=1e-4, KS_IP=2e-5, @@ -653,54 +392,6 @@ def __new__(cls, components=None, path=None, N_xc=2.686e-3, N_I=4.286e-3, N_aa=7 conserved_for=('C', 'N', 'P'), parameters=cls._stoichio_params, compile=False) - - # Need to add 5 processes separately for stoichiometry. See ASM2d for reference. - if path == _path: - _p19 = Process('storage_Sva_in_XPHA', - 'S_va + [Y_po4]X_PP -> [?]S_IC + [?]S_IN + [?]S_IP + X_PHA + [Y_po4*K_XPP]S_K + [Y_po4*Mg_XPP]S_Mg', - components=cmps, - ref_component='X_PHA', - rate_equation='q_PHA * S_va/(K_a+S_va) * (X_PP)/((K_PP*X_PAO) + X_PP) * X_PAO * S_va/(S_va+S_bu+S_pro+S_ac)', - parameters=('Y_po4', 'q_PHA', 'K_a', 'K_PP'), - conserved_for=('C', 'N', 'P')) - - _p20 = Process('storage_Sbu_in_XPHA', - 'S_bu + [Y_po4]X_PP -> [?]S_IC + [?]S_IN + [?]S_IP + X_PHA + [Y_po4*K_XPP]S_K + [Y_po4*Mg_XPP]S_Mg', - components=cmps, - ref_component='X_PHA', - rate_equation='q_PHA * S_bu/(K_a+S_bu) * (X_PP)/((K_PP*X_PAO) + X_PP) * X_PAO * S_bu/(S_va+S_bu+S_pro+S_ac)', - parameters=('Y_po4', 'q_PHA', 'K_a', 'K_PP'), - conserved_for=('C', 'N', 'P')) - - _p21 = Process('storage_Spro_in_XPHA', - 'S_pro + [Y_po4]X_PP -> [?]S_IC + [?]S_IN + [?]S_IP + X_PHA + [Y_po4*K_XPP]S_K + [Y_po4*Mg_XPP]S_Mg', - components=cmps, - ref_component='X_PHA', - rate_equation='q_PHA * S_pro/(K_a+S_pro) * (X_PP)/((K_PP*X_PAO) + X_PP) * X_PAO * S_pro/(S_va+S_bu+S_pro+S_ac)', - parameters=('Y_po4', 'q_PHA', 'K_a', 'K_PP'), - conserved_for=('C', 'N', 'P')) - - _p22 = Process('storage_Sac_in_XPHA', - 'S_ac + [Y_po4]X_PP -> [?]S_IC + [?]S_IN + [?]S_IP + X_PHA + [Y_po4*K_XPP]S_K + [Y_po4*Mg_XPP]S_Mg', - components=cmps, - ref_component='X_PHA', - rate_equation='q_PHA * S_ac/(K_a+S_ac) * (X_PP)/((K_PP*X_PAO) + X_PP) * X_PAO * S_ac/(S_va+S_bu+S_pro+S_ac)', - parameters=('Y_po4', 'q_PHA', 'K_a', 'K_PP'), - conserved_for=('C', 'N', 'P')) - - _p24 = Process('lysis_XPP', - 'X_PP -> [?]S_IC + [?]S_IN + [?]S_IP + [K_XPP]S_K + [Mg_XPP]S_Mg', - components=cmps, - ref_component='X_PP', - rate_equation='b_PP*X_PP', - parameters=('b_PP'), - conserved_for=('C', 'N', 'P')) - - self.insert(18, _p19) - self.insert(19, _p20) - self.insert(20, _p21) - self.insert(21, _p22) - self.insert(23, _p24) gas_transfer = [] for i in cls._biogas_IDs: @@ -713,13 +404,15 @@ def __new__(cls, components=None, path=None, N_xc=2.686e-3, N_I=4.286e-3, N_aa=7 self.extend(gas_transfer) self.compile(to_class=cls) - stoichio_vals = (f_sI_xb, f_ch_xb, f_pr_xb, f_li_xb, 1-f_sI_xb-f_ch_xb-f_pr_xb-f_li_xb, - f_fa_li, f_bu_su, f_pro_su, f_ac_su, 1-f_bu_su-f_pro_su-f_ac_su, + stoichio_vals = (f_fa_li, f_bu_su, f_pro_su, f_ac_su, 1-f_bu_su-f_pro_su-f_ac_su, f_va_aa, f_bu_aa, f_pro_aa, f_ac_aa, 1-f_va_aa-f_bu_aa-f_pro_aa-f_ac_aa, f_ac_fa, 1-f_ac_fa, f_pro_va, f_ac_va, 1-f_pro_va-f_ac_va, f_ac_bu, 1-f_ac_bu, f_ac_pro, 1-f_ac_pro, + Y_su, Y_aa, Y_fa, Y_c4, Y_pro, Y_ac, Y_h2, + # new parameters + f_sI_xb, f_ch_xb, f_pr_xb, f_li_xb, 1-f_sI_xb-f_ch_xb-f_pr_xb-f_li_xb, f_ac_PHA, f_bu_PHA, f_pro_PHA, 1-f_ac_PHA-f_bu_PHA-f_pro_PHA, - Y_su, Y_aa, Y_fa, Y_c4, Y_pro, Y_ac, Y_h2, Y_po4, cmps.X_PP.i_K, cmps.X_PP.i_Mg) + Y_PO4*P_mw, cmps.X_PP.i_K, cmps.X_PP.i_Mg) pH_LLs = np.array([pH_limits_aa[0]]*6 + [pH_limits_ac[0], pH_limits_h2[0]]) pH_ULs = np.array([pH_limits_aa[1]]*6 + [pH_limits_ac[1], pH_limits_h2[1]]) @@ -728,7 +421,9 @@ def __new__(cls, components=None, path=None, N_xc=2.686e-3, N_I=4.286e-3, N_aa=7 b_su, b_aa, b_fa, b_c4, b_pro, b_ac, b_h2, q_PHA, q_PHA, q_PHA, q_PHA, b_PAO, b_PP, b_PHA)) - Ks = np.array((K_su, K_aa, K_fa, K_c4, K_c4, K_pro, K_ac, K_h2, K_a, K_pp)) + Ks = np.array((K_su, K_aa, K_fa, K_c4, K_c4, K_pro, K_ac, K_h2, + #!!! new + K_A, K_PP)) KIs_h2 = np.array((KI_h2_fa, KI_h2_c4, KI_h2_c4, KI_h2_pro)) K_H_base = np.array(K_H_base) @@ -736,54 +431,38 @@ def __new__(cls, components=None, path=None, N_xc=2.686e-3, N_I=4.286e-3, N_aa=7 Ka_base = np.array([10**(-pKa) for pKa in pKa_base]) Ka_dH = np.array(Ka_dH) root = TempState() - # root.data = 10**(-7.4655) dct = self.__dict__ dct.update(kwargs) self.set_rate_function(rhos_adm1_p_extension) dct['_parameters'] = dict(zip(cls._stoichio_params, stoichio_vals)) self.rate_function._params = dict(zip(cls._kinetic_params, - [ks, Ks, pH_ULs, pH_LLs, KS_IN*N_mw, KS_IP*P_mw, + [ks, Ks, pH_ULs, pH_LLs, KS_IN*N_mw, KI_nh3, KIs_h2, Ka_base, Ka_dH, K_H_base, K_H_dH, kLa, - T_base, self._components, root])) + T_base, self._components, root, + #!!! new parameter + KS_IP*P_mw])) return self - def set_pKas(self, pKas): - '''Set the pKa values of the acid-base reactions at the base temperature.''' - if len(pKas) != 8: - raise ValueError(f'pKas must be an array of 8 elements, one for each ' - f'acid-base pair, not {len(pKas)} elements.') - dct = self.rate_function._params - dct['Ka_base'] = np.array([10**(-pKa) for pKa in pKas]) - - def _find_index(self, process): - isa = isinstance - if isa(process, int): return process - elif isa(process, str): return self.index(process) - elif isa(process, Process): return self.index(process.ID) - else: raise TypeError(f'must input an int or str or :class:`Process`, ' - f'not {type(process)}') - - def set_rate_constant(self, k, process): - '''Set the reaction rate constant [d^(-1)] for a process given its ID.''' - i = self._find_index(process) - self.rate_function._params['rate_constants'][i] = k - def set_half_sat_K(self, K, process): '''Set the substrate half saturation coefficient [kg/m3] for a process given its ID.''' i = self._find_index(process) - self.rate_function._params['half_sat_coeffs'][i-4] = K + if i < 11: + self.rate_function._params['half_sat_coeffs'][i-3] = K + else: + ValueError('To set "K_A", specify process = -2; to set "K_PP", specify process = -1,' + f'not {process}') def set_pH_inhibit_bounds(self, process, lower=None, upper=None): '''Set the upper and/or lower limit(s) of pH inhibition [unitless] for a process given its ID.''' - i = self._find_index(process) + i = self._find_index(process) - 3 dct = self.rate_function._params - if lower is None: lower = dct['pH_LLs'][i-4] - else: dct['pH_LLs'][i-4] = lower - if upper is None: upper = dct['pH_ULs'][i-4] - else: dct['pH_ULs'][i-4] = upper + if lower is None: lower = dct['pH_LLs'][i] + else: dct['pH_LLs'][i] = lower + if upper is None: upper = dct['pH_ULs'][i] + else: dct['pH_ULs'][i] = upper if lower >= upper: raise ValueError(f'lower limit for pH inhibition of {process} must ' f'be lower than the upper limit, not {[lower, upper]}') @@ -791,35 +470,14 @@ def set_pH_inhibit_bounds(self, process, lower=None, upper=None): def set_h2_inhibit_K(self, KI, process): '''Set the H2 inhibition coefficient [kg/m3] for a process given its ID.''' i = self._find_index(process) - self.rate_function._params['KIs_h2'][i-6] = KI + self.rate_function._params['KIs_h2'][i-5] = KI - def set_KS_IN(self, K): - '''Set inhibition coefficient for inorganic nitrogen as a secondary - substrate [M nitrogen].''' - self.rate_function._params['KS_IN'] = K * N_mw def set_KS_IP(self, K): '''Set inhibition coefficient for inorganic phosphorous as a secondary substrate [M phosphorous].''' self.rate_function._params['KS_IP'] = K * P_mw - def set_KI_nh3(self, K): - '''Set inhibition coefficient for free ammonia [M].''' - self.rate_function._params['KI_nh3'] = K - - def set_parameters(self, **parameters): - '''Set values to stoichiometric parameters in `ADM1._stoichio_params`.''' - non_stoichio = {} - for k,v in parameters.items(): - if k in self._stoichio_params: - if v >= 0 : self._parameters[k] = v - else: raise ValueError(f'{k} must >= 0, not {v}') - else: non_stoichio[k] = v - if non_stoichio: - warn(f'ignored value setting for non-stoichiometric parameters {non_stoichio}') - self.check_stoichiometric_parameters() - if self._stoichio_lambdified is not None: - self.__dict__['_stoichio_lambdified'] = None def check_stoichiometric_parameters(self): '''Check whether product COD fractions sum up to 1 for each process.''' diff --git a/qsdsan/sanunits/_junction.py b/qsdsan/sanunits/_junction.py index c73f20c4..c4770f80 100644 --- a/qsdsan/sanunits/_junction.py +++ b/qsdsan/sanunits/_junction.py @@ -33,7 +33,7 @@ 'mADM1toASM2d', ) -#%% +#%% Junction class Junction(SanUnit): ''' A non-reactive class that serves to convert a stream with one set of components @@ -242,7 +242,7 @@ def reactions(self, i): self._compile_reactions() -# %% +# %% ADMjunction #TODO: add a `rtol` kwargs for error checking class ADMjunction(Junction): @@ -351,84 +351,85 @@ def yt(t, QC_ins, dQC_ins): _update_dstate() self._AE = yt -#%% -class mADMjunction(Junction): +#%% mADMjunction +class mADMjunction(ADMjunction): ''' - An abstract superclass holding common properties of ADM interface classes. - Users should use its subclasses (e.g., ``ASMtoADM``, ``ADMtoASM``) instead. + An abstract superclass holding common properties of modified ADM interface classes. + Users should use its subclasses (e.g., ``ASM2dtomADM1``, ``mADM1toASM2d``) instead. See Also -------- - :class:`qsdsan.sanunits.Junction` + :class:`qsdsan.sanunits.ADMJunction` - :class:`qsdsan.sanunits.ADMtoASM` + :class:`qsdsan.sanunits.mADM1toASM2d` - :class:`qsdsan.sanunits.ASMtoADM` + :class:`qsdsan.sanunits.ASM2dtomADM1` ''' _parse_reactions = Junction._no_parse_reactions rtol = 1e-2 atol = 1e-6 - - def __init__(self, ID='', upstream=None, downstream=(), thermo=None, - init_with='WasteStream', F_BM_default=None, isdynamic=False, - adm1_model=None): - self.adm1_model = adm1_model # otherwise there won't be adm1_model when `_compile_reactions` is called - - if thermo is None: - warn('No `thermo` object is provided and is prone to raise error. ' - 'If you are not sure how to get the `thermo` object, ' - 'use `thermo = qsdsan.set_thermo` after setting thermo with the `Components` object.') - super().__init__(ID=ID, upstream=upstream, downstream=downstream, - thermo=thermo, init_with=init_with, - F_BM_default=F_BM_default, isdynamic=isdynamic) + cod_vfa = np.array([64, 112, 160, 208]) + + # def __init__(self, ID='', upstream=None, downstream=(), thermo=None, + # init_with='WasteStream', F_BM_default=None, isdynamic=False, + # adm1_model=None): + # self.adm1_model = adm1_model # otherwise there won't be adm1_model when `_compile_reactions` is called + + # if thermo is None: + # warn('No `thermo` object is provided and is prone to raise error. ' + # 'If you are not sure how to get the `thermo` object, ' + # 'use `thermo = qsdsan.set_thermo` after setting thermo with the `Components` object.') + # super().__init__(ID=ID, upstream=upstream, downstream=downstream, + # thermo=thermo, init_with=init_with, + # F_BM_default=F_BM_default, isdynamic=isdynamic) - @property - def T(self): - '''[float] Temperature of the upstream/downstream [K].''' - return self.ins[0].T - @T.setter - def T(self, T): - self.ins[0].T = self.outs[0].T = T - - @property - def pH(self): - '''[float] pH of the upstream/downstream.''' - return self.ins[0].pH + # @property + # def T(self): + # '''[float] Temperature of the upstream/downstream [K].''' + # return self.ins[0].T + # @T.setter + # def T(self, T): + # self.ins[0].T = self.outs[0].T = T + + # @property + # def pH(self): + # '''[float] pH of the upstream/downstream.''' + # return self.ins[0].pH @property def adm1_model(self): - '''[qsdsan.Process] ADM process model.''' + '''[qsdsan.CompiledProcesses] mADM1 process model.''' return self._adm1_model @adm1_model.setter def adm1_model(self, model): if not isinstance(model, pc.ADM1_p_extension): - raise ValueError('`adm1_model` must be an `AMD1` object, ' + raise ValueError('`adm1_model` must be an `AMD1` object, ' #!!! update error message f'the given object is {type(model).__name__}.') self._adm1_model = model - @property - def T_base(self): - '''[float] Base temperature in the ADM1 model.''' - return self.adm1_model.rate_function.params['T_base'] + # @property + # def T_base(self): + # '''[float] Base temperature in the ADM1 model.''' + # return self.adm1_model.rate_function.params['T_base'] - @property - def pKa_base(self): - '''[float] pKa of the acid-base pairs at the base temperature in the ADM1 model.''' - Ka_base = self.adm1_model.rate_function.params['Ka_base'] - return -np.log10(Ka_base) + # @property + # def pKa_base(self): + # '''[float] pKa of the acid-base pairs at the base temperature in the ADM1 model.''' + # Ka_base = self.adm1_model.rate_function.params['Ka_base'] + # return -np.log10(Ka_base) - @property - def Ka_dH(self): - '''[float] Heat of reaction for Ka.''' - return self.adm1_model.rate_function.params['Ka_dH'] + # @property + # def Ka_dH(self): + # '''[float] Heat of reaction for Ka.''' + # return self.adm1_model.rate_function.params['Ka_dH'] @property def pKa(self): ''' [numpy.array] pKa array of the following acid-base pairs: - ('H+', 'OH-'), ('NH4+', 'NH3'), ('H3PO4', 'H2PO4 2-'), ('CO2', 'HCO3-'), + ('H+', 'OH-'), ('NH4+', 'NH3'), ('H2PO4-', 'HPO4-2'), ('CO2', 'HCO3-'), ('HAc', 'Ac-'), ('HPr', 'Pr-'), ('HBu', 'Bu-'), ('HVa', 'Va-') ''' return self.pKa_base-np.log10(pc.T_correction_factor(self.T_base, self.T, self.Ka_dH)) @@ -445,7 +446,7 @@ def alpha_IP(self): '''[float] Charge per g of P.''' pH = self.pH pKa_IP = self.pKa[2] - return 10**(pKa_IP-pH)/(1+10**(pKa_IP-pH))/31 + return 10**(pKa_IP-pH)/(1+10**(pKa_IP-pH))/31 #!!! alpha IP should be negative @property def alpha_IC(self): @@ -476,8 +477,7 @@ def yt(t, QC_ins, dQC_ins): self._AE = yt -#%% - +#%% ADMtoASM class ADMtoASM(ADMjunction): ''' Interface unit to convert anaerobic digestion model (ADM) components @@ -735,12 +735,8 @@ def adm2asm(adm_vals): return asm_vals self._reactions = adm2asm - - - -# %% - +#%% ASMtoADM class ASMtoADM(ADMjunction): ''' Interface unit to convert activated sludge model (ASM) components @@ -1048,7 +1044,7 @@ def asm2adm(asm_vals): self._reactions = asm2adm -#%% +#%% ASM2dtoADM1 class ASM2dtoADM1(ADMjunction): ''' @@ -1436,7 +1432,7 @@ def asm2adm(asm_vals): self._reactions = asm2adm -#%% +#%% ADM1toASM2d class ADM1toASM2d(ADMjunction): ''' @@ -1721,7 +1717,7 @@ def adm2asm(adm_vals): def alpha_vfa(self): return 1.0/self.cod_vfa*(-1.0/(1.0 + 10**(self.pKa[3:]-self.pH))) -#%% +#%% mADM1toASM2d class mADM1toASM2d(mADMjunction): ''' @@ -2282,7 +2278,7 @@ def madm12asm2d(adm_vals): def alpha_vfa(self): return 1.0/self.cod_vfa*(-1.0/(1.0 + 10**(self.pKa[4:]-self.pH))) -# %% +#%% ASM2dtomADM1 # While using this interface X_I.i_N in ASM2d should be 0.06, instead of 0.02. class ASM2dtomADM1(mADMjunction): From 2dc3ed8f7a0b8df30e1d795e6307560691e0927e Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Mon, 13 May 2024 17:09:17 -0700 Subject: [PATCH 334/424] debug `ASM2dtomADM1` --- qsdsan/sanunits/_junction.py | 720 +++++++++++++++++------------------ 1 file changed, 358 insertions(+), 362 deletions(-) diff --git a/qsdsan/sanunits/_junction.py b/qsdsan/sanunits/_junction.py index c4770f80..a9a6928f 100644 --- a/qsdsan/sanunits/_junction.py +++ b/qsdsan/sanunits/_junction.py @@ -331,6 +331,12 @@ def alpha_vfa(self): '''[float] charge per g of VFA-COD.''' return 1.0/self.cod_vfa*(-1.0/(1.0 + 10**(self.pKa[3:]-self.pH))) + def isbalanced(self, lhs, rhs_vals, rhs_i): + rhs = sum(rhs_vals*rhs_i) + error = rhs - lhs + tol = max(self.rtol*lhs, self.rtol*rhs, self.atol) + return abs(error) <= tol, error, tol, rhs + def _compile_AE(self): _state = self._state _dstate = self._dstate @@ -398,6 +404,17 @@ class mADMjunction(ADMjunction): # '''[float] pH of the upstream/downstream.''' # return self.ins[0].pH + @property + def asm2d_model(self): + '''[qsdsan.CompiledProcesses] ASM2d process model.''' + return self._asm2d_model + @asm2d_model.setter + def asm2d_model(self, model): + if not isinstance(model, pc.ASM2d): + raise ValueError('`asm2d_model` must be an `ASM2d` object, ' + f'the given object is {type(model).__name__}.') + self._asm2d_model = model + @property def adm1_model(self): '''[qsdsan.CompiledProcesses] mADM1 process model.''' @@ -405,7 +422,7 @@ def adm1_model(self): @adm1_model.setter def adm1_model(self, model): if not isinstance(model, pc.ADM1_p_extension): - raise ValueError('`adm1_model` must be an `AMD1` object, ' #!!! update error message + raise ValueError('`adm1_model` must be an `ADM1_p_extension` object, ' #!!! update error message f'the given object is {type(model).__name__}.') self._adm1_model = model @@ -446,7 +463,7 @@ def alpha_IP(self): '''[float] Charge per g of P.''' pH = self.pH pKa_IP = self.pKa[2] - return 10**(pKa_IP-pH)/(1+10**(pKa_IP-pH))/31 #!!! alpha IP should be negative + return (-1/(1+10**(pKa_IP-pH))-1)/31 #!!! alpha IP should be negative @property def alpha_IC(self): @@ -455,27 +472,26 @@ def alpha_IC(self): pKa_IC = self.pKa[3] return -1/(1+10**(pKa_IC-pH))/12 - def _compile_AE(self): - _state = self._state - _dstate = self._dstate - _cached_state = self._cached_state - _update_state = self._update_state - _update_dstate = self._update_dstate - rxn = self.reactions + # def _compile_AE(self): + # _state = self._state + # _dstate = self._dstate + # _cached_state = self._cached_state + # _update_state = self._update_state + # _update_dstate = self._update_dstate + # rxn = self.reactions - def yt(t, QC_ins, dQC_ins): - before_vals = QC_ins[0,:-1] - _state[:-1] = rxn(before_vals) - _state[-1] = QC_ins[0,-1] - if t > self._cached_t: - _dstate[:] = (_state - _cached_state)/(t-self._cached_t) - _cached_state[:] = _state - self._cached_t = t - _update_state() - _update_dstate() - - self._AE = yt - + # def yt(t, QC_ins, dQC_ins): + # before_vals = QC_ins[0,:-1] + # _state[:-1] = rxn(before_vals) + # _state[-1] = QC_ins[0,-1] + # if t > self._cached_t: + # _dstate[:] = (_state - _cached_state)/(t-self._cached_t) + # _cached_state[:] = _state + # self._cached_t = t + # _update_state() + # _update_dstate() + + # self._AE = yt #%% ADMtoASM class ADMtoASM(ADMjunction): @@ -533,12 +549,12 @@ def pH(self): return self.ins[0].pH - def isbalanced(self, lhs, rhs_vals, rhs_i): - rhs = sum(rhs_vals*rhs_i) - error = rhs - lhs - tol = max(self.rtol*lhs, self.rtol*rhs, self.atol) - return abs(error) <= tol, error, tol, rhs - + # def isbalanced(self, lhs, rhs_vals, rhs_i): + # rhs = sum(rhs_vals*rhs_i) + # error = rhs - lhs + # tol = max(self.rtol*lhs, self.rtol*rhs, self.atol) + # return abs(error) <= tol, error, tol, rhs + def balance_cod_tkn(self, adm_vals, asm_vals): cmps_adm = self.ins[0].components cmps_asm = self.outs[0].components @@ -813,11 +829,11 @@ def pH(self, ph): self._pH = self.outs[0].pH = ph - def isbalanced(self, lhs, rhs_vals, rhs_i): - rhs = sum(rhs_vals*rhs_i) - error = rhs - lhs - tol = max(self.rtol*lhs, self.rtol*rhs, self.atol) - return abs(error) <= tol, error, tol, rhs + # def isbalanced(self, lhs, rhs_vals, rhs_i): + # rhs = sum(rhs_vals*rhs_i) + # error = rhs - lhs + # tol = max(self.rtol*lhs, self.rtol*rhs, self.atol) + # return abs(error) <= tol, error, tol, rhs def balance_cod_tkn(self, asm_vals, adm_vals): cmps_asm = self.ins[0].components @@ -1762,8 +1778,8 @@ class mADM1toASM2d(mADMjunction): # Since we are matching PAOs directly from ASM2d to mADM1, it is important # for PAOs to have identical N/P content across models - adm_X_PAO_i_N = 0.07 - adm_X_PAO_i_P = 0.02 + # adm_X_PAO_i_N = 0.07 + # adm_X_PAO_i_P = 0.02 # Should be constants cod_vfa = np.array([64, 112, 160, 208]) @@ -2283,8 +2299,8 @@ def alpha_vfa(self): # While using this interface X_I.i_N in ASM2d should be 0.06, instead of 0.02. class ASM2dtomADM1(mADMjunction): ''' - Interface unit to convert activated sludge model (ASM) components - to anaerobic digestion model (ADM) components. + Interface unit to convert ASM2d state variables + to ADM1 components, following the A1 scenario in [1]_. Parameters ---------- @@ -2292,8 +2308,8 @@ class ASM2dtomADM1(mADMjunction): Influent stream with ASM components. downstream : stream or str Effluent stream with ADM components. - adm1_model : obj - The anaerobic digestion process model (:class:`qsdsan.processes.ADM1_p_extension`). + adm1_model : :class:`qsdsan.processes.ADM1_p_extension` + The anaerobic digestion process model. xs_to_li : float Split of slowly biodegradable substrate COD to lipid, after all N is mapped into protein. @@ -2328,25 +2344,25 @@ class ASM2dtomADM1(mADMjunction): ''' # User defined values xs_to_li = 0.7 - bio_to_li = 0.4 - frac_deg = 0.68 + # bio_to_li = 0.4 + # frac_deg = 0.68 # Since we are matching PAOs, X_I, S_I, directly from ASM2d to mADM1, it is important # for PAOs to have identical N/P content across models - adm_X_PAO_i_N = 0.07 - adm_X_PAO_i_P = 0.02 + # adm_X_PAO_i_N = 0.07 # should be dependent on component set, not defined here + # adm_X_PAO_i_P = 0.02 - asm_X_I_i_N = 0.0600327162 - asm_X_I_i_P = 0.01 - asm_S_I_i_N = 0.06 - asm_S_I_i_P = 0.01 + # asm_X_I_i_N = 0.0600327162 + # asm_X_I_i_P = 0.01 + # asm_S_I_i_N = 0.06 + # asm_S_I_i_P = 0.01 - def isbalanced(self, lhs, rhs_vals, rhs_i): - rhs = sum(rhs_vals*rhs_i) - error = rhs - lhs - tol = max(self.rtol*lhs, self.rtol*rhs, self.atol) - return abs(error) <= tol, error, tol, rhs + # def isbalanced(self, lhs, rhs_vals, rhs_i): + # rhs = sum(rhs_vals*rhs_i) + # error = rhs - lhs + # tol = max(self.rtol*lhs, self.rtol*rhs, self.atol) + # return abs(error) <= tol, error, tol, rhs def balance_cod_tkn_tp(self, asm_vals, adm_vals): cmps_asm = self.ins[0].components @@ -2428,7 +2444,7 @@ def balance_cod_tkn_tp(self, asm_vals, adm_vals): f'effluent TP is {adm_tp} or {adm_tp*(1+dtp)}. ') return adm_vals else: - warn('cannot balance COD, TKN and TP at the same time. \n' + warn('cannot balance COD, TKN, and TP at the same time. \n' 'Atleast two of the three COD, TKN, and TP are not balanced \n' f'time with rtol={self.rtol} and atol={self.atol}.\n ' f'influent (ASM) COD is {asm_cod}\n ' @@ -2441,62 +2457,90 @@ def balance_cod_tkn_tp(self, asm_vals, adm_vals): def _compile_reactions(self): # Retrieve constants - ins = self.ins[0] - outs = self.outs[0] - rtol = self.rtol - atol = self.atol + # rtol = self.rtol + # atol = self.atol - cmps_asm = ins.components - cmps_adm = outs.components - - # For COD balance - S_NO3_i_COD = cmps_asm.S_NO3.i_COD - - # For N balance - X_H_i_N = cmps_asm.X_H.i_N - X_AUT_i_N = cmps_asm.X_AUT.i_N - S_F_i_N = cmps_asm.S_F.i_N - X_S_i_N = cmps_asm.X_S.i_N - S_A_i_N = cmps_asm.S_A.i_N - - # Due to issue with mapping of X_I across ASM2d and ADM1, making this user dependent is important - if self.asm_X_I_i_N == None: - asm_X_I_i_N = cmps_asm.X_I.i_N - else: - asm_X_I_i_N = self.asm_X_I_i_N - - if self.asm_X_I_i_P == None: - asm_X_I_i_P = cmps_asm.X_I.i_P - else: - asm_X_I_i_P = self.asm_X_I_i_P - - if self.asm_S_I_i_N == None: - asm_S_I_i_N = cmps_asm.S_I.i_N - else: - asm_S_I_i_N = self.asm_X_I_i_N - - if self.asm_S_I_i_P == None: - asm_S_I_i_P = cmps_asm.S_I.i_P - else: - asm_S_I_i_P = self.asm_S_I_i_P - - # For P balance - X_H_i_P = cmps_asm.X_H.i_P - X_AUT_i_P = cmps_asm.X_AUT.i_P - S_F_i_P = cmps_asm.S_F.i_P - X_S_i_P = cmps_asm.X_S.i_P - S_A_i_P = cmps_asm.S_A.i_P - - if cmps_asm.S_A.i_N > 0: - warn(f'S_A in ASM has positive nitrogen content: {cmps_asm.S_S.i_N} gN/gCOD. ' - 'These nitrogen will be ignored by the interface model ' - 'and could lead to imbalance of TKN after conversion.') - - if cmps_asm.S_A.i_P > 0: - warn(f'S_A in ASM has positive phosphorous content: {cmps_asm.S_S.i_P} gN/gCOD. ' - 'These phosphorous will be ignored by the interface model ' - 'and could lead to imbalance of TP after conversion.') + cmps_asm = self.ins[0].components + cmps_adm = self.outs[0].components + # # For COD balance + # S_NO3_i_COD = cmps_asm.S_NO3.i_COD + + # # For N balance + # X_H_i_N = cmps_asm.X_H.i_N + # X_AUT_i_N = cmps_asm.X_AUT.i_N + # S_F_i_N = cmps_asm.S_F.i_N + # X_S_i_N = cmps_asm.X_S.i_N + # S_A_i_N = cmps_asm.S_A.i_N + + # # For P balance + # X_H_i_P = cmps_asm.X_H.i_P + # X_AUT_i_P = cmps_asm.X_AUT.i_P + # S_F_i_P = cmps_asm.S_F.i_P + # X_S_i_P = cmps_asm.X_S.i_P + # S_A_i_P = cmps_asm.S_A.i_P + + # # Due to issue with mapping of X_I across ASM2d and ADM1, making this user dependent is important + # if self.asm_X_I_i_N == None: + # asm_X_I_i_N = cmps_asm.X_I.i_N + # else: + # asm_X_I_i_N = self.asm_X_I_i_N + + # if self.asm_X_I_i_P == None: + # asm_X_I_i_P = cmps_asm.X_I.i_P + # else: + # asm_X_I_i_P = self.asm_X_I_i_P + + # if self.asm_S_I_i_N == None: + # asm_S_I_i_N = cmps_asm.S_I.i_N + # else: + # asm_S_I_i_N = self.asm_X_I_i_N + + # if self.asm_S_I_i_P == None: + # asm_S_I_i_P = cmps_asm.S_I.i_P + # else: + # asm_S_I_i_P = self.asm_S_I_i_P + get = getattr + setv = setattr + for name in ('X_PHA', 'X_PP', 'X_PAO', 'X_MeOH', 'X_MeP'): + casm = get(cmps_asm, name) + cadm = get(cmps_adm, name) + for attr in ('measured_as', 'i_COD', 'i_N', 'i_P'): + vasm = get(casm, attr) + if get(cadm, attr) != vasm: + setv(cadm, attr, vasm) + warn(f"ADM component {name}'s {attr} is changed to match " + "the corresponding ASM component") + + for name in ('S_I', 'X_I'): + casm = get(cmps_asm, name) + cadm = get(cmps_adm, name) + for attr in ('measured_as', 'i_N', 'i_P'): + vadm = get(cadm, attr) + if get(casm, attr) != vadm: + setv(casm, attr, vadm) + warn(f"ASM component {name}'s {attr} is changed to match " + "the corresponding ADM component") + + for attr in ('i_N', 'i_P'): + vadm = get(cmps_adm.S_ac, attr) + if get(cmps_asm.S_A, attr) != vadm: + cmps_asm.S_A.i_N = vadm + warn(f"ASM component S_A's {attr} is changed to match " + "the ADM component S_ac.") + + # if cmps_asm.S_A.i_N > 0: + # warn(f'S_A in ASM has positive nitrogen content: {cmps_asm.S_S.i_N} gN/gCOD. ' + # 'These nitrogen will be ignored by the interface model ' + # 'and could lead to imbalance of TKN after conversion.') + + # if cmps_asm.S_A.i_P > 0: + # warn(f'S_A in ASM has positive phosphorous content: {cmps_asm.S_S.i_P} gP/gCOD. ' + # 'These phosphorous will be ignored by the interface model ' + # 'and could lead to imbalance of TP after conversion.') + + cmps_asm.refresh_constants() + cmps_adm.refresh_constants() # ------------------------------Rai Version--------------------------------------------- # if cmps_asm.S_I.i_P > 0: # warn(f'S_I in ASM has positive phosphorous content: {cmps_asm.S_I.i_P} gN/gCOD. ' @@ -2507,282 +2551,238 @@ def _compile_reactions(self): # We do not need to check if X_S.i_N != 0 since we take care of it using X_ND_asm1 # We do not need to check if S_F.i_N != 0 since we take care of it using S_ND_asm1 + _asm_ids = cmps_asm.indices(['S_F', 'X_S', 'X_H', 'S_I', 'X_I']) + _adm_ids = cmps_adm.indices(['S_aa', 'S_su', 'X_pr', 'X_li', 'X_ch']) + # For carbon balance + C_SF, C_XS, C_XB, C_SI, C_XI = cmps_asm.i_C[_asm_ids] + C_aa, C_su, C_pr, C_li, C_ch = cmps_adm.i_C[_adm_ids] + # For nitrogen balance - S_ac_i_N = cmps_adm.S_ac.i_N - S_aa_i_N = cmps_adm.S_aa.i_N - X_pr_i_N = cmps_adm.X_pr.i_N - adm_S_I_i_N = cmps_adm.S_I.i_N - adm_X_I_i_N = cmps_adm.X_I.i_N + N_SF, N_XS, N_XB, N_SI, N_XI = cmps_asm.i_N[_asm_ids] + N_aa, N_su, N_pr, N_li, N_ch = cmps_adm.i_N[_adm_ids] # For phosphorous balance - S_ac_i_P = cmps_adm.S_ac.i_P - X_pr_i_P = cmps_adm.X_pr.i_P - adm_S_I_i_P = cmps_adm.S_I.i_P - adm_X_I_i_P = cmps_adm.X_I.i_P - + P_SF, P_XS, P_XB, P_SI, P_XI = cmps_asm.i_P[_asm_ids] + P_aa, P_su, P_pr, P_li, P_ch = cmps_adm.i_P[_adm_ids] + # Checks for direct mapping of X_PAO, X_PP, X_PHA # Check for X_PAO (measured as COD so i_COD = 1 in both ASM2d and ADM1) - asm_X_PAO_i_N = cmps_asm.X_PAO.i_N + # asm_X_PAO_i_N = cmps_asm.X_PAO.i_N - if self.adm_X_PAO_i_N == None: - adm_X_PAO_i_N = cmps_adm.X_PAO.i_N - else: - adm_X_PAO_i_N = self.adm_X_PAO_i_N + # if self.adm_X_PAO_i_N == None: + # adm_X_PAO_i_N = cmps_adm.X_PAO.i_N + # else: + # adm_X_PAO_i_N = self.adm_X_PAO_i_N - if asm_X_PAO_i_N != adm_X_PAO_i_N: - raise RuntimeError('X_PAO cannot be directly mapped as N content' - f'in asm2d_X_PAO_i_N = {asm_X_PAO_i_N} is not equal to' - f'adm_X_PAO_i_N = {adm_X_PAO_i_N}') + # if asm_X_PAO_i_N != adm_X_PAO_i_N: + # raise RuntimeError('X_PAO cannot be directly mapped as N content' + # f'in asm2d_X_PAO_i_N = {asm_X_PAO_i_N} is not equal to' + # f'adm_X_PAO_i_N = {adm_X_PAO_i_N}') - asm_X_PAO_i_P = cmps_asm.X_PAO.i_P + # asm_X_PAO_i_P = cmps_asm.X_PAO.i_P - if self.adm_X_PAO_i_P == None: - adm_X_PAO_i_P = cmps_adm.X_PAO.i_P - else: - adm_X_PAO_i_P = self.adm_X_PAO_i_P + # if self.adm_X_PAO_i_P == None: + # adm_X_PAO_i_P = cmps_adm.X_PAO.i_P + # else: + # adm_X_PAO_i_P = self.adm_X_PAO_i_P - if asm_X_PAO_i_P != adm_X_PAO_i_P: - raise RuntimeError('X_PAO cannot be directly mapped as P content' - f'in asm2d_X_PAO_i_P = {asm_X_PAO_i_P} is not equal to' - f'adm_X_PAO_i_P = {adm_X_PAO_i_P}') + # if asm_X_PAO_i_P != adm_X_PAO_i_P: + # raise RuntimeError('X_PAO cannot be directly mapped as P content' + # f'in asm2d_X_PAO_i_P = {asm_X_PAO_i_P} is not equal to' + # f'adm_X_PAO_i_P = {adm_X_PAO_i_P}') # Checks not required for X_PP as measured as P in both, with i_COD = i_N = 0 # Checks not required for X_PHA as measured as COD in both, with i_N = i_P = 0 - if asm_X_I_i_N != adm_X_I_i_N: - raise RuntimeError('X_I cannot be directly mapped as N content' - f'in asm2d_X_I_i_N = {asm_X_I_i_N} is not equal to' - f'adm_X_I_i_N = {adm_X_I_i_N}') - - if asm_X_I_i_P != adm_X_I_i_P: - raise RuntimeError('X_I cannot be directly mapped as P content' - f'in asm2d_X_I_i_P = {asm_X_I_i_P} is not equal to' - f'adm_X_I_i_P = {adm_X_I_i_P}') - - if asm_S_I_i_N != adm_X_I_i_N: - raise RuntimeError('S_I cannot be directly mapped as N content' - f'in asm2d_S_I_i_N = {asm_S_I_i_N} is not equal to' - f'adm_S_I_i_N = {adm_S_I_i_N}') - - if asm_S_I_i_P != adm_S_I_i_P: - raise RuntimeError('S_I cannot be directly mapped as P content' - f'in asm2d_S_I_i_P = {asm_S_I_i_P} is not equal to \n' - f'adm_S_I_i_P = {adm_S_I_i_P}') - - adm_ions_idx = cmps_adm.indices(['S_IN', 'S_IP', 'S_IC', 'S_cat', 'S_an']) - - frac_deg = self.frac_deg - alpha_IP = self.alpha_IP - alpha_IN = self.alpha_IN - alpha_IC = self.alpha_IC - proton_charge = 10**(-self.pKa[0]+self.pH) - 10**(-self.pH) # self.pKa[0] is pKw + # if asm_X_I_i_N != adm_X_I_i_N: + # raise RuntimeError('X_I cannot be directly mapped as N content' + # f'in asm2d_X_I_i_N = {asm_X_I_i_N} is not equal to' + # f'adm_X_I_i_N = {adm_X_I_i_N}') + + # if asm_X_I_i_P != adm_X_I_i_P: + # raise RuntimeError('X_I cannot be directly mapped as P content' + # f'in asm2d_X_I_i_P = {asm_X_I_i_P} is not equal to' + # f'adm_X_I_i_P = {adm_X_I_i_P}') + + # if asm_S_I_i_N != adm_X_I_i_N: + # raise RuntimeError('S_I cannot be directly mapped as N content' + # f'in asm2d_S_I_i_N = {asm_S_I_i_N} is not equal to' + # f'adm_S_I_i_N = {adm_S_I_i_N}') + + # if asm_S_I_i_P != adm_S_I_i_P: + # raise RuntimeError('S_I cannot be directly mapped as P content' + # f'in asm2d_S_I_i_P = {asm_S_I_i_P} is not equal to \n' + # f'adm_S_I_i_P = {adm_S_I_i_P}') + + # adm_ions_idx = cmps_adm.indices(['S_IN', 'S_IP', 'S_IC', 'S_cat', 'S_an']) + S_O2_idx, S_NO3_idx = cmps_asm.indices(['S_O2', 'S_NO3']) + # asm_charged_idx = cmps_asm.indices(['S_NO3', 'S_NH4', 'S_ALK', 'S_PO4', 'S_A', 'X_PP']) + + # frac_deg = self.frac_deg + # alpha_IP = self.alpha_IP + # alpha_IN = self.alpha_IN + # alpha_IC = self.alpha_IC + # proton_charge = 10**(-self.pKa[0]+self.pH) - 10**(-self.pH) # self.pKa[0] is pKw #!!! should be within asm2d2madm1 f_corr = self.balance_cod_tkn_tp + asm = self.asm2d_model + adm = self.adm1_model + p1_stoichio = np.asarray(asm.stoichiometry.loc['hetero_growth_S_A']) + p1_stoichio /= abs(p1_stoichio[S_O2_idx]) + p2_stoichio = np.asarray(asm.stoichiometry.loc['denitri_S_A']) + p2_stoichio /= abs(p2_stoichio[S_NO3_idx]) + p3_stoichio = np.array([adm.parameters[f'f_{k}_xb'] for k in ('sI', 'ch', 'pr', 'li', 'xI')]) + # To convert components from ASM2d to mADM1 (asm2d-2-madm1) def asm2d2madm1(asm_vals): - # S_I, S_S, X_I, X_S, X_BH, X_BA, X_P, S_O, S_NO, S_NH, S_ND, X_ND, S_ALK, S_N2, H2O = asm_vals - S_O2, S_N2, S_NH4, S_NO3, S_PO4, S_F, S_A, S_I, S_ALK, X_I, X_S, X_H, \ - X_PAO, X_PP, X_PHA, X_AUT, X_MeOH, X_MeP, H2O = asm_vals + _asm_vals = asm_vals.copy() # Step 0: charged component snapshot (# pg. 84 of IWA ASM textbook) - _sno3 = S_NO3 - _snh4 = S_NH4 - _salk = S_ALK - _spo4 = S_PO4 - _sa = S_A - _xpp = X_PP - - # Step 1: remove any remaining COD demand - O2_coddm = S_O2 - NO3_coddm = -S_NO3*S_NO3_i_COD - - # cod_spl = S_S + X_S + X_BH + X_BA - # Replacing S_S with S_F + S_A (IWA ASM textbook) - - cod_spl = (S_A + S_F) + X_S + (X_H + X_AUT) - - # bioN = X_BH*X_BH_i_N + X_BA*X_BA_i_N - - bioN = X_H*X_H_i_N + X_AUT*X_AUT_i_N - bioP = X_H*X_H_i_P + X_AUT*X_AUT_i_P + # _sno3, _snh4, _salk, _spo4, _sa, _xpp = asm_vals[asm_charged_idx] + + # PROCESS 1: remove S_O2 with S_A with associated X_H growth (aerobic growth of X_H on S_A) + O2_coddm = _asm_vals[S_O2_idx] + _asm_vals += O2_coddm * p1_stoichio # makes S_O2 = 0 + + # PROCESS 2: remove S_NO3 with S_A with associated X_H growth (denitrification on S_A) + NO3_coddm = _asm_vals[S_NO3_idx] + _asm_vals += NO3_coddm * p2_stoichio # makes S_NO3 = 0 + + S_O2, S_N2, S_NH4, S_NO3, S_PO4, S_F, S_A, S_I, S_ALK, X_I, X_S, X_H, \ + X_PAO, X_PP, X_PHA, X_AUT, X_MeOH, X_MeP, H2O = _asm_vals + + S_IC = S_ALK + S_IN = S_NH4 + S_IP = S_PO4 + # CONV 1: transform S_F into S_aa, S_su, S_fa # To be used in Step 2 - S_ND_asm1 = S_F*S_F_i_N #S_ND (in asm1) equals the N content in S_F - # To be used in Step 3 - X_ND_asm1 = X_S*X_S_i_N #X_ND (in asm1) equals the N content in X_S - # To be used in Step 5 (a) - X_S_P = X_S*X_S_i_P - # To be used in Step 5 (b) - S_F_P = S_F*S_F_i_P - - if cod_spl <= O2_coddm: - S_O2 = O2_coddm - cod_spl - S_F = S_A = X_S = X_H = X_AUT = 0 - elif cod_spl <= O2_coddm + NO3_coddm: - S_O2 = 0 - S_NO3 = -(O2_coddm + NO3_coddm - cod_spl)/S_NO3_i_COD - S_A = S_F = X_S = X_H = X_AUT = 0 - else: - S_A -= O2_coddm + NO3_coddm - if S_A < 0: - S_F += S_A - S_A = 0 - if S_F < 0: - X_S += S_F - S_F = 0 - if X_S < 0: - X_H += X_S - X_S = 0 - if X_H < 0: - X_AUT += X_H - X_H = 0 - S_O2 = S_NO3 = 0 - - # Step 2: convert any readily biodegradable - # COD and TKN into amino acids and sugars - - # Directly map acetate - if S_ac_i_N == S_A_i_N and S_ac_i_P == S_A_i_P: - S_ac = S_A - else: - raise RuntimeError('N and P content should be the same in S_A and S_ac for direct translation') - - # First we calculate the amount of amino acid required in ADM1 - # if all available soluble organic N can be mapped to amino acid - req_scod = S_ND_asm1 / S_aa_i_N - - # if available S_F is not enough to fulfill that amino acid requirement - if S_F < req_scod: - # then all available S_F is mapped to amino acids + S_ND = S_F*N_SF # N in S_F + req_scod = S_ND / N_aa + + if S_F < req_scod: # if S_F cod is not enough to convert all organic soluble N into aa S_aa = S_F - # and no S_S would be available for conversion to sugars S_su = 0 - # This needs to be followed by a corresponding loss in soluble organic N - S_ND_asm1 -= S_aa * S_aa_i_N - # if available S_F is more than enough to fulfill that amino acid requirement - else: - # All soluble organic N will be mapped to amino acid - S_aa = req_scod - # The line above implies that a certain portion of S_S would also be consumed to form amino acid - # The S_S which is left would form sugar - # In simpler terms; S_F = S_F - S_aa; S_su = S_F + else: # if S_F cod is more than enough to convert all organic soluble N into aa + S_aa = req_scod # All soluble organic N will be mapped to amino acid S_su = S_F - S_aa - # All soluble organic N would thus be consumed in amino acid formation - S_ND_asm1 = 0 - - # Step 3: convert slowly biodegradable COD and TKN - # into proteins, lipids, and carbohydrates - # First we calculate the amount of protein required in ADM1 - # if all available particulate organic N can be mapped to amino acid - req_xcod = X_ND_asm1 / X_pr_i_N + S_IN += S_ND - S_aa*N_aa + S_IC += S_F*C_SF - (S_aa*C_aa + S_su*C_su) + S_IP += S_F*P_SF + + # PROCESS 3: biomass decay (X_H, X_AUT lysis) anaerobic + bio = X_H + X_AUT + _si, _ch, _pr, _li, _xi = bio * p3_stoichio + S_IC += bio*C_XB - (_si*C_SI + _ch*C_ch + _pr*C_pr + _li*C_li + _xi*C_XI) + S_IN += bio*N_XB - (_si*N_SI + _ch*N_ch + _pr*N_pr + _li*N_li + _xi*N_XI) + S_IP += bio*P_XB - (_si*P_SI + _ch*P_ch + _pr*P_pr + _li*P_li + _xi*P_XI) + + # CONV 2: transform asm X_S into X_pr, X_li, X_ch + X_ND = X_S*N_XS + req_xcod = X_ND / N_pr # Since X_pr_i_N >> X_pr_i_P there's no need to check req_xcod for N and P separately (CONFIRM LATER 05/16) # if available X_S is not enough to fulfill that protein requirement - if X_S < req_xcod: - # then all available X_S is mapped to amino acids + if X_S < req_xcod: # if X_S cod is not enough to convert all organic particulate N into pr X_pr = X_S - # and no X_S would be available for conversion to lipid or carbohydrates X_li = X_ch = 0 - # This needs to be followed by a corresponding loss in particulate organic N - X_ND_asm1 -= X_pr * X_pr_i_N - - # For P balance - # This needs to be followed by a corresponding loss in particulate organic P - X_S_P -= X_pr * X_pr_i_P - - # if available X_S is more than enough to fulfill that protein requirement else: - # All particulate organic N will be mapped to amino acid - X_pr = req_xcod - # The line above implies that a certain portion of X_S would also be consumed to form protein - # The X_S which is left would form lipid and carbohydrates in a percentage define by the user + X_pr = req_xcod X_li = self.xs_to_li * (X_S - X_pr) X_ch = (X_S - X_pr) - X_li - # All particulate organic N would thus be consumed in amino acid formation - X_ND_asm1 = 0 - - # For P balance - # This needs to be followed by a corresponding loss in particulate organic P - X_S_P -= X_pr * X_pr_i_P - - # Step 4: convert active biomass into protein, lipids, - # carbohydrates and potentially particulate TKN + + S_IN += X_ND - X_pr*N_pr + S_IC += X_S*C_XS - (X_pr*C_pr + X_li*C_li + X_ch*C_ch) + S_IP += X_S*P_XS - (X_pr*P_pr + X_li*P_li + X_ch*P_ch) + + X_pr += _pr + X_li += _li + X_ch += _ch + S_I += _si + X_I += _xi + + # PROCESS 4-5: omitted, PAO related components mapped directly + # CONV 3-5: convert S_A, S_I, X_I; conversion is immediate because identical component composition is enforced + S_ac = S_A + + # # Step 4: convert active biomass into protein, lipids, + # # carbohydrates and potentially particulate TKN - # First the amount of biomass N/P available for protein, lipid etc is determined - # For this calculation, from total biomass N available the amount - # of particulate inert N/P expected in ADM1 is subtracted + # # First the amount of biomass N/P available for protein, lipid etc is determined + # # For this calculation, from total biomass N available the amount + # # of particulate inert N/P expected in ADM1 is subtracted - available_bioN = bioN - (X_H + X_AUT) * (1-frac_deg) * adm_X_I_i_N - if available_bioN < 0: - raise RuntimeError('Not enough N in X_H and X_AUT to fully convert ' - 'the non-biodegradable portion into X_I in ADM1.') + # available_bioN = bioN - (X_H + X_AUT) * (1-frac_deg) * adm_X_I_i_N + # if available_bioN < 0: + # raise RuntimeError('Not enough N in X_H and X_AUT to fully convert ' + # 'the non-biodegradable portion into X_I in ADM1.') - available_bioP = bioP - (X_H + X_AUT) * (1-frac_deg) * adm_X_I_i_P - if available_bioP < 0: - raise RuntimeError('Not enough P in X_H and X_AUT to fully convert ' - 'the non-biodegradable portion into X_I in ADM1.') + # available_bioP = bioP - (X_H + X_AUT) * (1-frac_deg) * adm_X_I_i_P + # if available_bioP < 0: + # raise RuntimeError('Not enough P in X_H and X_AUT to fully convert ' + # 'the non-biodegradable portion into X_I in ADM1.') - # Then the amount of biomass N/P required for biomass conversion to protein is determined - req_bioN = (X_H + X_AUT) * frac_deg * X_pr_i_N - req_bioP = (X_H + X_AUT) * frac_deg * X_pr_i_P - - # Case I: if both available biomass N/P and particulate organic N/P is greater than - # required biomass N/P for conversion to protein - if available_bioN + X_ND_asm1 >= req_bioN and available_bioP + X_S_P >= req_bioP: - # then all biodegradable biomass N/P (corrsponding to protein demand) is converted to protein - X_pr += (X_H + X_AUT) * frac_deg - # the remaining biomass N/P is transfered as organic N/P - X_ND_asm1 += available_bioN - req_bioN - X_S_P += available_bioP - req_bioP + # # Then the amount of biomass N/P required for biomass conversion to protein is determined + # req_bioN = (X_H + X_AUT) * frac_deg * X_pr_i_N + # req_bioP = (X_H + X_AUT) * frac_deg * X_pr_i_P + + # # Case I: if both available biomass N/P and particulate organic N/P is greater than + # # required biomass N/P for conversion to protein + # if available_bioN + X_ND_asm1 >= req_bioN and available_bioP + X_S_P >= req_bioP: + # # then all biodegradable biomass N/P (corrsponding to protein demand) is converted to protein + # X_pr += (X_H + X_AUT) * frac_deg + # # the remaining biomass N/P is transfered as organic N/P + # X_ND_asm1 += available_bioN - req_bioN + # X_S_P += available_bioP - req_bioP - # Case II: if available biomass N and particulate organic N is less than - # required biomass N for conversion to protein, but available biomass P and - # particulate organic P is greater than required biomass P for conversion to protein + # # Case II: if available biomass N and particulate organic N is less than + # # required biomass N for conversion to protein, but available biomass P and + # # particulate organic P is greater than required biomass P for conversion to protein - # Case III: if available biomass P and particulate organic P is less than - # required biomass P for conversion to protein, but available biomass N and - # particulate organic N is greater than required biomass N for conversion to protein + # # Case III: if available biomass P and particulate organic P is less than + # # required biomass P for conversion to protein, but available biomass N and + # # particulate organic N is greater than required biomass N for conversion to protein - # Case IV: if both available biomass N/P and particulate organic N/P is less than - # required biomass N/P for conversion to protein - else: + # # Case IV: if both available biomass N/P and particulate organic N/P is less than + # # required biomass N/P for conversion to protein + # else: - if (available_bioP + X_S_P)/X_pr_i_P < (available_bioN + X_ND_asm1)/X_pr_i_N: - # all available P and particulate organic P is converted to protein - bio2pr = (available_bioP + X_S_P)/X_pr_i_P - X_pr += bio2pr - # Biodegradable biomass available after conversion to protein is calculated - bio_to_split = (X_H + X_AUT) * frac_deg - bio2pr - # Part of the remaining biomass is mapped to lipid based on user defined value - bio_split_to_li = bio_to_split * self.bio_to_li - X_li += bio_split_to_li - # The other portion of the remanining biomass is mapped to carbohydrates - X_ch += (bio_to_split - bio_split_to_li) - # Since all organic P has been mapped to protein, none is left - X_S_P = 0 + # if (available_bioP + X_S_P)/X_pr_i_P < (available_bioN + X_ND_asm1)/X_pr_i_N: + # # all available P and particulate organic P is converted to protein + # bio2pr = (available_bioP + X_S_P)/X_pr_i_P + # X_pr += bio2pr + # # Biodegradable biomass available after conversion to protein is calculated + # bio_to_split = (X_H + X_AUT) * frac_deg - bio2pr + # # Part of the remaining biomass is mapped to lipid based on user defined value + # bio_split_to_li = bio_to_split * self.bio_to_li + # X_li += bio_split_to_li + # # The other portion of the remanining biomass is mapped to carbohydrates + # X_ch += (bio_to_split - bio_split_to_li) + # # Since all organic P has been mapped to protein, none is left + # X_S_P = 0 - # the remaining biomass N is transfered as organic N - X_ND_asm1 += available_bioN - (bio2pr*X_pr_i_N) + # # the remaining biomass N is transfered as organic N + # X_ND_asm1 += available_bioN - (bio2pr*X_pr_i_N) - else: - # all available N and particulate organic N is converted to protein - bio2pr = (available_bioN + X_ND_asm1)/X_pr_i_N - X_pr += bio2pr - # Biodegradable biomass available after conversion to protein is calculated - bio_to_split = (X_H + X_AUT) * frac_deg - bio2pr - # Part of the remaining biomass is mapped to lipid based on user defined value - bio_split_to_li = bio_to_split * self.bio_to_li - X_li += bio_split_to_li - # The other portion of the remanining biomass is mapped to carbohydrates - X_ch += (bio_to_split - bio_split_to_li) - # Since all organic N has been mapped to protein, none is left - X_ND_asm1 = 0 + # else: + # # all available N and particulate organic N is converted to protein + # bio2pr = (available_bioN + X_ND_asm1)/X_pr_i_N + # X_pr += bio2pr + # # Biodegradable biomass available after conversion to protein is calculated + # bio_to_split = (X_H + X_AUT) * frac_deg - bio2pr + # # Part of the remaining biomass is mapped to lipid based on user defined value + # bio_split_to_li = bio_to_split * self.bio_to_li + # X_li += bio_split_to_li + # # The other portion of the remanining biomass is mapped to carbohydrates + # X_ch += (bio_to_split - bio_split_to_li) + # # Since all organic N has been mapped to protein, none is left + # X_ND_asm1 = 0 - # the remaining biomass P is transfered as organic P - X_S_P += available_bioP - (bio2pr*X_pr_i_P) + # # the remaining biomass P is transfered as organic P + # X_S_P += available_bioP - (bio2pr*X_pr_i_P) # Step 5: map particulate inerts @@ -2838,7 +2838,7 @@ def asm2d2madm1(asm_vals): # --------------------------Flores Alsina et al. 2016 version-------------------------------------- # X_I = X_I # COD balance - X_I += (X_H+X_AUT) * (1-frac_deg) + # X_I += (X_H+X_AUT) * (1-frac_deg) # --------------------------Flores Alsina et al. 2016 version-------------------------------------- # # 5(b) @@ -2955,15 +2955,10 @@ def asm2d2madm1(asm_vals): # S_IN = S_ND_asm1 + X_ND_asm1 + S_NH4 + supply_inert_n_asm2d # --------------------------Rai version-------------------------------------- - # --------------------------Flores Alsina et al. 2016 version-------------------------------------- - S_IN = S_ND_asm1 + X_ND_asm1 + S_NH4 - # --------------------------Flores Alsina et al. 2016 version-------------------------------------- - - S_IP = S_F_P + X_S_P + S_PO4 # Step 8: check COD and TKN balance # has TKN: S_aa, S_IN, S_I, X_pr, X_I - S_IC = S_cat = S_an = 0 + # S_IC = S_cat = S_an = 0 # When mapping components directly in Step 9 ensure the values of # cmps.i_N, cmps.i_P, and cmps.i_COD are same in both ASM2d and ADM1 @@ -2987,33 +2982,34 @@ def asm2d2madm1(asm_vals): X_I, X_PHA, X_PP, X_PAO, 0, 0, # S_K, S_Mg, X_MeOH, X_MeP, - S_cat, S_an, H2O]) + 0, 0, H2O]) # S_cat, S_an - adm_vals = f_corr(asm_vals, adm_vals) + # adm_vals = f_corr(asm_vals, adm_vals) + adm_vals = f_corr(_asm_vals, adm_vals) - # Step 7: charge balance - asm_charge_tot = - _sa/64 + _snh4/14 - _sno3/14 - 1.5*_spo4/31 - _salk - _xpp/31 #Based on page 84 of IWA ASM handbook + # # Step 7: charge balance + # asm_charge_tot = - _sa/64 + _snh4/14 - _sno3/14 - 1.5*_spo4/31 - _salk - _xpp/31 #Based on page 84 of IWA ASM handbook - #!!! charge balance should technically include VFAs, S_K, S_Mg, - # but since their concentrations are assumed zero it is acceptable. + # #!!! charge balance should technically include VFAs, S_K, S_Mg, + # # but since their concentrations are assumed zero it is acceptable. - S_IN = adm_vals[adm_ions_idx[0]] - S_IP = adm_vals[adm_ions_idx[1]] + # S_IN = adm_vals[adm_ions_idx[0]] + # S_IP = adm_vals[adm_ions_idx[1]] - S_IC = (asm_charge_tot -S_IN*alpha_IN -S_IP*alpha_IP)/alpha_IC + # S_IC = (asm_charge_tot -S_IN*alpha_IN -S_IP*alpha_IP)/alpha_IC - # proton_charge = (OH)^-1 - (H)^+1 - # net_Scat = Scat - San - net_Scat = asm_charge_tot + proton_charge + # # proton_charge = (OH)^-1 - (H)^+1 + # # net_Scat = Scat - San + # net_Scat = asm_charge_tot + proton_charge - if net_Scat > 0: - S_cat = net_Scat - S_an = 0 - else: - S_cat = 0 - S_an = -net_Scat + # if net_Scat > 0: + # S_cat = net_Scat + # S_an = 0 + # else: + # S_cat = 0 + # S_an = -net_Scat - adm_vals[adm_ions_idx[2:]] = [S_IC, S_cat, S_an] + # adm_vals[adm_ions_idx[2:]] = [S_IC, S_cat, S_an] return adm_vals From 17158f61ca3d0bf069faa5a9ec8cc83bc5fc5a62 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Tue, 14 May 2024 14:40:08 -0700 Subject: [PATCH 335/424] debug `mADM1toASM2d` --- qsdsan/sanunits/_junction.py | 840 ++++++++++++++++++----------------- 1 file changed, 434 insertions(+), 406 deletions(-) diff --git a/qsdsan/sanunits/_junction.py b/qsdsan/sanunits/_junction.py index a9a6928f..925b4fe6 100644 --- a/qsdsan/sanunits/_junction.py +++ b/qsdsan/sanunits/_junction.py @@ -242,7 +242,7 @@ def reactions(self, i): self._compile_reactions() -# %% ADMjunction +#%% ADMjunction #TODO: add a `rtol` kwargs for error checking class ADMjunction(Junction): @@ -442,14 +442,14 @@ def adm1_model(self, model): # '''[float] Heat of reaction for Ka.''' # return self.adm1_model.rate_function.params['Ka_dH'] - @property - def pKa(self): - ''' - [numpy.array] pKa array of the following acid-base pairs: - ('H+', 'OH-'), ('NH4+', 'NH3'), ('H2PO4-', 'HPO4-2'), ('CO2', 'HCO3-'), - ('HAc', 'Ac-'), ('HPr', 'Pr-'), ('HBu', 'Bu-'), ('HVa', 'Va-') - ''' - return self.pKa_base-np.log10(pc.T_correction_factor(self.T_base, self.T, self.Ka_dH)) + # @property + # def pKa(self): + # ''' + # [numpy.array] pKa array of the following acid-base pairs: + # ('H+', 'OH-'), ('NH4+', 'NH3'), ('H2PO4-', 'HPO4-2'), ('CO2', 'HCO3-'), + # ('HAc', 'Ac-'), ('HPr', 'Pr-'), ('HBu', 'Bu-'), ('HVa', 'Va-') + # ''' + # return self.pKa_base-np.log10(pc.T_correction_factor(self.T_base, self.T, self.Ka_dH)) @property def alpha_IN(self): @@ -471,7 +471,11 @@ def alpha_IC(self): pH = self.pH pKa_IC = self.pKa[3] return -1/(1+10**(pKa_IC-pH))/12 - + + @property + def alpha_vfa(self): + return 1.0/self.cod_vfa*(-1.0/(1.0 + 10**(self.pKa[4:]-self.pH))) + # def _compile_AE(self): # _state = self._state # _dstate = self._dstate @@ -492,6 +496,46 @@ def alpha_IC(self): # _update_dstate() # self._AE = yt + + def check_component_properties(self, cmps_asm, cmps_adm): + get = getattr + setv = setattr + for name in ('X_PHA', 'X_PP', 'X_PAO', 'X_MeOH', 'X_MeP'): + casm = get(cmps_asm, name) + cadm = get(cmps_adm, name) + for attr in ('measured_as', 'i_COD', 'i_N', 'i_P'): + vasm = get(casm, attr) + if get(cadm, attr) != vasm: + setv(cadm, attr, vasm) + warn(f"ADM component {name}'s {attr} is changed to match " + "the corresponding ASM component") + + for name in ('S_I', 'X_I'): + casm = get(cmps_asm, name) + cadm = get(cmps_adm, name) + for attr in ('measured_as', 'i_N', 'i_P'): + vadm = get(cadm, attr) + if get(casm, attr) != vadm: + setv(casm, attr, vadm) + warn(f"ASM component {name}'s {attr} is changed to match " + "the corresponding ADM component") + + for attr in ('i_N', 'i_P'): + vadm = get(cmps_adm.S_ac, attr) + if get(cmps_asm.S_A, attr) != vadm: + cmps_asm.S_A.i_N = vadm + warn(f"ASM component S_A's {attr} is changed to match " + "the ADM component S_ac.") + + if cmps_asm.S_ALK.measured_as != cmps_adm.S_IC.measured_as: + raise RuntimeError('S_ALK in ASM and S_IC in ADM must both be measured as "C".') + if cmps_asm.S_NH4.measured_as != cmps_adm.S_IN.measured_as: + raise RuntimeError('S_NH4 in ASM and S_IN in ADM must both be measured as "N".') + if cmps_asm.S_PO4.measured_as != cmps_adm.S_IP.measured_as: + raise RuntimeError('S_PO4 in ASM and S_IP in ADM must both be measured as "P".') + + cmps_asm.refresh_constants() + cmps_adm.refresh_constants() #%% ADMtoASM class ADMtoASM(ADMjunction): @@ -548,13 +592,6 @@ def pH(self): '''[float] pH of the upstream/downstream.''' return self.ins[0].pH - - # def isbalanced(self, lhs, rhs_vals, rhs_i): - # rhs = sum(rhs_vals*rhs_i) - # error = rhs - lhs - # tol = max(self.rtol*lhs, self.rtol*rhs, self.atol) - # return abs(error) <= tol, error, tol, rhs - def balance_cod_tkn(self, adm_vals, asm_vals): cmps_adm = self.ins[0].components cmps_asm = self.outs[0].components @@ -828,13 +865,6 @@ def pH(self): def pH(self, ph): self._pH = self.outs[0].pH = ph - - # def isbalanced(self, lhs, rhs_vals, rhs_i): - # rhs = sum(rhs_vals*rhs_i) - # error = rhs - lhs - # tol = max(self.rtol*lhs, self.rtol*rhs, self.atol) - # return abs(error) <= tol, error, tol, rhs - def balance_cod_tkn(self, asm_vals, adm_vals): cmps_asm = self.ins[0].components cmps_adm = self.outs[0].components @@ -1428,10 +1458,7 @@ def asm2adm(asm_vals): # Step 7: charge balance asm_charge_tot = - _sa/64 + _snh4/14 - _sno3/14 - 1.5*_spo4/31 - _salk - _xpp/31 #Based on page 84 of IWA ASM handbook - #!!! charge balance should technically include VFAs, - # but VFAs concentrations are assumed zero per previous steps?? - S_IN = adm_vals[adm_ions_idx[0]] - + S_IN = adm_vals[adm_ions_idx[0]] S_IC = (asm_charge_tot -S_IN*alpha_IN)/alpha_IC net_Scat = asm_charge_tot + proton_charge @@ -1762,34 +1789,15 @@ class mADM1toASM2d(mADMjunction): [2] Flores-Alsina, X., Solon, K., Kazadi Mbamba, C., Tait, S., Gernaey, K. V., Jeppsson, U., & Batstone, D. J. (2016). Modelling phosphorus (P), sulfur (S) and iron (FE) interactions for dynamic simulations of anaerobic digestion processes. - Water Research, 95, 370–382. + Water Research, 95, 370–382. + See Also -------- - :class:`qsdsan.sanunits.ADMjunction` + :class:`qsdsan.sanunits.mADMjunction` - :class:`qsdsan.sanunits.ASMtoADM` - - `math.isclose ` + :class:`qsdsan.sanunits.ASM2dtomADM1` ''' - # User defined values - bio_to_xs = 0.9 - - # Since we are matching PAOs directly from ASM2d to mADM1, it is important - # for PAOs to have identical N/P content across models - - # adm_X_PAO_i_N = 0.07 - # adm_X_PAO_i_P = 0.02 - - # Should be constants - cod_vfa = np.array([64, 112, 160, 208]) - - def isbalanced(self, lhs, rhs_vals, rhs_i): - rhs = sum(rhs_vals*rhs_i) - error = rhs - lhs - tol = max(self.rtol*lhs, self.rtol*rhs, self.atol) - return abs(error) <= tol, error, tol, rhs - def balance_cod_tkn(self, adm_vals, asm_vals): cmps_adm = self.ins[0].components cmps_asm = self.outs[0].components @@ -1883,369 +1891,430 @@ def balance_cod_tkn(self, adm_vals, asm_vals): def _compile_reactions(self): # Retrieve constants - ins = self.ins[0] - outs = self.outs[0] - rtol = self.rtol - atol = self.atol + # ins = self.ins[0] + # outs = self.outs[0] + # rtol = self.rtol + # atol = self.atol - cmps_adm = ins.components - # N balance - X_pr_i_N = cmps_adm.X_pr.i_N - S_aa_i_N = cmps_adm.S_aa.i_N - adm_X_I_i_N = cmps_adm.X_I.i_N - adm_S_I_i_N = cmps_adm.S_I.i_N - adm_i_N = cmps_adm.i_N - adm_bio_N_indices = cmps_adm.indices(('X_su', 'X_aa', 'X_fa', - 'X_c4', 'X_pro', 'X_ac', 'X_h2')) + cmps_asm = self.ins[0].components + cmps_adm = self.outs[0].components + self.check_component_properties(cmps_asm, cmps_adm) - # P balance - X_pr_i_P = cmps_adm.X_pr.i_P - adm_X_I_i_P = cmps_adm.X_I.i_P - adm_S_I_i_P = cmps_adm.S_I.i_P - S_aa_i_P = cmps_adm.S_aa.i_P - adm_i_P = cmps_adm.i_P - adm_bio_P_indices = cmps_adm.indices(('X_su', 'X_aa', 'X_fa', - 'X_c4', 'X_pro', 'X_ac', 'X_h2')) + _asm_ids = cmps_asm.indices(['S_F', 'X_S', 'S_A']) + _adm_ids = cmps_adm.indices(['S_su', 'S_aa', 'S_fa', + 'S_va', 'S_bu', 'S_pro', 'S_ac', + 'X_pr', 'X_li', 'X_ch']) + + # For carbon balance + C_SF, C_XS, C_SA = cmps_asm.i_C[_asm_ids] + C_su, C_aa, C_fa, C_va, C_bu, C_pro, C_ac, C_pr, C_li, C_ch = cmps_adm.i_C[_adm_ids] - cmps_asm = outs.components + # For nitrogen balance + N_SF, N_XS, N_SA = cmps_asm.i_N[_asm_ids] + N_su, N_aa, N_fa, N_va, N_bu, N_pro, N_ac, N_pr, N_li, N_ch = cmps_adm.i_N[_adm_ids] - # N balance - X_S_i_N = cmps_asm.X_S.i_N - S_F_i_N = cmps_asm.S_F.i_N - S_A_i_N = cmps_asm.S_A.i_N + # For phosphorous balance + P_SF, P_XS, P_SA = cmps_asm.i_P[_asm_ids] + P_su, P_aa, P_fa, P_va, P_bu, P_pro, P_ac, P_pr, P_li, P_ch = cmps_adm.i_P[_adm_ids] + + adm = self.adm1_model + adm_p1_idx = cmps_adm.indices(('X_su', 'X_aa', 'X_fa', 'X_c4', + 'X_pro', 'X_ac', 'X_h2', + 'X_PAO', 'X_PP', 'X_PHA')) + decay_idx = [i for i in adm.IDs if i.startswith(('decay', 'lysis'))] + decay_stoichio = np.asarray(adm.stoichiometry.loc[decay_idx]) + + # # cmps_adm = ins.components + # # N balance + # X_pr_i_N = cmps_adm.X_pr.i_N + # S_aa_i_N = cmps_adm.S_aa.i_N + # adm_X_I_i_N = cmps_adm.X_I.i_N + # adm_S_I_i_N = cmps_adm.S_I.i_N + # adm_i_N = cmps_adm.i_N + # adm_bio_N_indices = cmps_adm.indices(('X_su', 'X_aa', 'X_fa', + # 'X_c4', 'X_pro', 'X_ac', 'X_h2')) + + # # P balance + # X_pr_i_P = cmps_adm.X_pr.i_P + # adm_X_I_i_P = cmps_adm.X_I.i_P + # adm_S_I_i_P = cmps_adm.S_I.i_P + # S_aa_i_P = cmps_adm.S_aa.i_P + # adm_i_P = cmps_adm.i_P + # adm_bio_P_indices = cmps_adm.indices(('X_su', 'X_aa', 'X_fa', + # 'X_c4', 'X_pro', 'X_ac', 'X_h2')) + + # # cmps_asm = outs.components + + # # N balance + # X_S_i_N = cmps_asm.X_S.i_N + # S_F_i_N = cmps_asm.S_F.i_N + # S_A_i_N = cmps_asm.S_A.i_N - asm_X_I_i_N = cmps_asm.X_I.i_N - asm_S_I_i_N = cmps_asm.S_I.i_N - asm_ions_idx = cmps_asm.indices(('S_NH4', 'S_A', 'S_NO3', 'S_PO4', 'X_PP', 'S_ALK')) + # asm_X_I_i_N = cmps_asm.X_I.i_N + # asm_S_I_i_N = cmps_asm.S_I.i_N + # asm_ions_idx = cmps_asm.indices(('S_NH4', 'S_A', 'S_NO3', 'S_PO4', 'X_PP', 'S_ALK')) - # P balance - X_S_i_P = cmps_asm.X_S.i_P - S_F_i_P = cmps_asm.S_F.i_P - S_A_i_P = cmps_asm.S_A.i_P - asm_X_I_i_P = cmps_asm.X_I.i_P - asm_S_I_i_P = cmps_asm.S_I.i_P + # # P balance + # X_S_i_P = cmps_asm.X_S.i_P + # S_F_i_P = cmps_asm.S_F.i_P + # S_A_i_P = cmps_asm.S_A.i_P + # asm_X_I_i_P = cmps_asm.X_I.i_P + # asm_S_I_i_P = cmps_asm.S_I.i_P # Checks for direct mapping of X_PAO, X_PP, X_PHA # Check for X_PAO (measured as COD so i_COD = 1 in both ASM2d and ADM1) - asm_X_PAO_i_N = cmps_asm.X_PAO.i_N - adm_X_PAO_i_N = cmps_adm.X_PAO.i_N + # asm_X_PAO_i_N = cmps_asm.X_PAO.i_N + # adm_X_PAO_i_N = cmps_adm.X_PAO.i_N - if self.adm_X_PAO_i_N == None: - adm_X_PAO_i_N = cmps_adm.X_PAO.i_N - else: - adm_X_PAO_i_N = self.adm_X_PAO_i_N + # if self.adm_X_PAO_i_N == None: + # adm_X_PAO_i_N = cmps_adm.X_PAO.i_N + # else: + # adm_X_PAO_i_N = self.adm_X_PAO_i_N - if asm_X_PAO_i_N != adm_X_PAO_i_N: - raise RuntimeError('X_PAO cannot be directly mapped as N content' - f'in asm2d_X_PAO_i_N = {asm_X_PAO_i_N} is not equal to' - f'adm_X_PAO_i_N = {adm_X_PAO_i_N}') + # if asm_X_PAO_i_N != adm_X_PAO_i_N: + # raise RuntimeError('X_PAO cannot be directly mapped as N content' + # f'in asm2d_X_PAO_i_N = {asm_X_PAO_i_N} is not equal to' + # f'adm_X_PAO_i_N = {adm_X_PAO_i_N}') - asm_X_PAO_i_P = cmps_asm.X_PAO.i_P - adm_X_PAO_i_P = cmps_adm.X_PAO.i_P + # asm_X_PAO_i_P = cmps_asm.X_PAO.i_P + # adm_X_PAO_i_P = cmps_adm.X_PAO.i_P - if self.adm_X_PAO_i_P == None: - adm_X_PAO_i_P = cmps_adm.X_PAO.i_P - else: - adm_X_PAO_i_P = self.adm_X_PAO_i_P + # if self.adm_X_PAO_i_P == None: + # adm_X_PAO_i_P = cmps_adm.X_PAO.i_P + # else: + # adm_X_PAO_i_P = self.adm_X_PAO_i_P - if asm_X_PAO_i_P != adm_X_PAO_i_P: - raise RuntimeError('X_PAO cannot be directly mapped as P content' - f'in asm2d_X_PAO_i_P = {asm_X_PAO_i_P} is not equal to' - f'adm_X_PAO_i_P = {adm_X_PAO_i_P}') + # if asm_X_PAO_i_P != adm_X_PAO_i_P: + # raise RuntimeError('X_PAO cannot be directly mapped as P content' + # f'in asm2d_X_PAO_i_P = {asm_X_PAO_i_P} is not equal to' + # f'adm_X_PAO_i_P = {adm_X_PAO_i_P}') # Checks not required for X_PP as measured as P in both, with i_COD = i_N = 0 # Checks not required for X_PHA as measured as COD in both, with i_N = i_P = 0 - alpha_IN = self.alpha_IN - alpha_IC = self.alpha_IC - alpha_IP = self.alpha_IP - alpha_vfa = self.alpha_vfa + # alpha_IN = self.alpha_IN + # alpha_IC = self.alpha_IC + # alpha_IP = self.alpha_IP + # alpha_vfa = self.alpha_vfa f_corr = self.balance_cod_tkn # To convert components from mADM1 to ASM2d (madm1-2-asm2d) def madm12asm2d(adm_vals): + _adm_vals = adm_vals.copy() + + # PROCESS 1: decay of biomas, X_PP, X_PHA + bio_xpp_pha = _adm_vals[adm_p1_idx] + _adm_vals += bio_xpp_pha * decay_stoichio + + # PROCESS 2: strip biogas. Omitted because no S_ch4 or S_h2 in ASM2d components + S_su, S_aa, S_fa, S_va, S_bu, S_pro, S_ac, S_h2, S_ch4, S_IC, S_IN, S_IP, S_I, \ X_ch, X_pr, X_li, X_su, X_aa, X_fa, X_c4, X_pro, X_ac, X_h2, X_I, \ - X_PHA, X_PP, X_PAO, S_K, S_Mg, X_MeOH, X_MeP, S_cat, S_an, H2O = adm_vals - - # print(f'adm_vals = {adm_vals}') + X_PHA, X_PP, X_PAO, S_K, S_Mg, X_MeOH, X_MeP, S_cat, S_an, H2O = _adm_vals + + if S_h2 > 0 or S_ch4 > 0: warn('Ignored dissolved H2 or CH4.') + + S_ALK = S_IC + S_NH4 = S_IN + S_PO4 = S_IP + + # CONV 1: convert X_pr, X_li, X_ch to X_S + X_S = X_pr + X_li + X_ch + S_ALK += X_pr*C_pr + X_li*C_li + X_ch*C_ch - X_S*C_XS + S_NH4 += X_pr*N_pr + X_li*N_li + X_ch*N_ch - X_S*N_XS + S_PO4 += X_pr*P_pr + X_li*P_li + X_ch*P_ch - X_S*P_XS + + # CONV 2: convert S_su, S_aa, S_fa to S_F + S_F = S_su + S_aa + S_fa + S_ALK += S_su*C_su + S_aa*C_aa + S_fa*C_fa - S_F*C_SF + S_NH4 += S_su*N_su + S_aa*N_aa + S_fa*N_fa - S_F*N_SF + S_PO4 += S_su*P_su + S_aa*P_aa + S_fa*P_fa - S_F*P_SF + + # CONV 3: convert VFAs to S_A + S_A = S_va + S_bu + S_pro + S_ac + S_ALK += S_va*C_va + S_bu*C_bu + S_pro*C_pro + S_ac*C_ac - S_A*C_SA + # S_NH4 += S_va*N_va + S_bu*N_bu + S_pro*N_pro + S_ac*N_ac - S_A*N_SA + # S_PO4 += S_va*P_va + S_bu*P_bu + S_pro*P_pro + S_ac*P_ac - S_A*P_SA + + + # # print(f'adm_vals = {adm_vals}') - # Step 0: snapshot of charged components - # Not sure about charge on X_PP, S_Mg, S_K (PHA and PAO would have zero charge) - # Step 0: snapshot of charged components - # _ions = np.array([S_IN, S_IC, S_ac, S_pro, S_bu, S_va]) - _ions = np.array([S_IN, S_IC, S_IP, X_PP, S_Mg, S_K, S_ac, S_pro, S_bu, S_va]) + # # Step 0: snapshot of charged components + # # Not sure about charge on X_PP, S_Mg, S_K (PHA and PAO would have zero charge) + # # Step 0: snapshot of charged components + # # _ions = np.array([S_IN, S_IC, S_ac, S_pro, S_bu, S_va]) + # _ions = np.array([S_IN, S_IC, S_IP, X_PP, S_Mg, S_K, S_ac, S_pro, S_bu, S_va]) - # Step 1a: convert biomass and inert particulates into X_S and X_I + # # Step 1a: convert biomass and inert particulates into X_S and X_I - # What is available - bio_cod = X_su + X_aa + X_fa + X_c4 + X_pro + X_ac + X_h2 - bio_n = sum((adm_vals*adm_i_N)[adm_bio_N_indices]) - bio_p = sum((adm_vals*adm_i_P)[adm_bio_P_indices]) + # # What is available + # bio_cod = X_su + X_aa + X_fa + X_c4 + X_pro + X_ac + X_h2 + # bio_n = sum((adm_vals*adm_i_N)[adm_bio_N_indices]) + # bio_p = sum((adm_vals*adm_i_P)[adm_bio_P_indices]) - #!!! In default ASM2d stoichiometry, biomass decay (cell lysis) - #!!! yields 90% particulate substrate + 10% X_I - #!!! so: convert both biomass and X_I in adm to X_S and X_I in asm + # #!!! In default ASM2d stoichiometry, biomass decay (cell lysis) + # #!!! yields 90% particulate substrate + 10% X_I + # #!!! so: convert both biomass and X_I in adm to X_S and X_I in asm - # What is available - xi_n = X_I*adm_X_I_i_N - xi_p = X_I*adm_X_I_i_P + # # What is available + # xi_n = X_I*adm_X_I_i_N + # xi_p = X_I*adm_X_I_i_P - # What would be formed by X_S - xs_cod = bio_cod * self.bio_to_xs - xs_ndm = xs_cod * X_S_i_N - xs_pdm = xs_cod * X_S_i_P + # # What would be formed by X_S + # xs_cod = bio_cod * self.bio_to_xs + # xs_ndm = xs_cod * X_S_i_N + # xs_pdm = xs_cod * X_S_i_P - # What would be formed by X_I (ASM2d) - xi_cod = bio_cod * (1 - self.bio_to_xs) + X_I - xi_ndm = xi_cod * asm_X_I_i_N - xi_pdm = xi_cod * asm_X_I_i_P + # # What would be formed by X_I (ASM2d) + # xi_cod = bio_cod * (1 - self.bio_to_xs) + X_I + # xi_ndm = xi_cod * asm_X_I_i_N + # xi_pdm = xi_cod * asm_X_I_i_P - # MAPPING OF X_S + # # MAPPING OF X_S - # Case I: Both bio_N and bio_P are sufficient - if xs_ndm <= bio_n and xs_pdm <= bio_p: - X_S = xs_cod - xs_cod = 0 - bio_n -= xs_ndm - bio_p -= xs_pdm - else: - # Case II, III, and, IV: At least one of the two biological N/P is not sufficient - if bio_p / X_S_i_P > bio_n / X_S_i_N: - warn('Not enough biomass N to map the specified proportion of ' - 'biomass COD into X_S. Rest of the biomass COD goes to S_A in last step') - X_S = bio_n / X_S_i_N - xs_cod -= X_S - bio_n = 0 - bio_p -= X_S*X_S_i_P #mathematically, bio_p can become negative at this point - if bio_p < 0: - S_IP += bio_p - bio_p = 0 - else: - warn('Not enough biomass P to map the specified proportion of ' - 'biomass COD into X_S. Rest of the biomass COD goes to S_A in last step') - X_S = bio_p / X_S_i_P - xs_cod -= X_S - bio_p = 0 - bio_n -= X_S*X_S_i_N #mathematically, bio_n can become negative at this point - if bio_n < 0: - S_IN += bio_n - bio_n = 0 + # # Case I: Both bio_N and bio_P are sufficient + # if xs_ndm <= bio_n and xs_pdm <= bio_p: + # X_S = xs_cod + # xs_cod = 0 + # bio_n -= xs_ndm + # bio_p -= xs_pdm + # else: + # # Case II, III, and, IV: At least one of the two biological N/P is not sufficient + # if bio_p / X_S_i_P > bio_n / X_S_i_N: + # warn('Not enough biomass N to map the specified proportion of ' + # 'biomass COD into X_S. Rest of the biomass COD goes to S_A in last step') + # X_S = bio_n / X_S_i_N + # xs_cod -= X_S + # bio_n = 0 + # bio_p -= X_S*X_S_i_P #mathematically, bio_p can become negative at this point + # if bio_p < 0: + # S_IP += bio_p + # bio_p = 0 + # else: + # warn('Not enough biomass P to map the specified proportion of ' + # 'biomass COD into X_S. Rest of the biomass COD goes to S_A in last step') + # X_S = bio_p / X_S_i_P + # xs_cod -= X_S + # bio_p = 0 + # bio_n -= X_S*X_S_i_N #mathematically, bio_n can become negative at this point + # if bio_n < 0: + # S_IN += bio_n + # bio_n = 0 - # MAPPING OF X_I + # # MAPPING OF X_I - if xi_ndm < bio_n + xi_n + S_IN and xi_pdm < bio_p + xi_p + S_IP: + # if xi_ndm < bio_n + xi_n + S_IN and xi_pdm < bio_p + xi_p + S_IP: - X_I = xi_cod - xi_cod = 0 + # X_I = xi_cod + # xi_cod = 0 - xi_n -= xi_ndm - if xi_n < 0: - bio_n += xi_n - xi_n = 0 - if bio_n < 0: - S_IN += bio_n - bio_n = 0 + # xi_n -= xi_ndm + # if xi_n < 0: + # bio_n += xi_n + # xi_n = 0 + # if bio_n < 0: + # S_IN += bio_n + # bio_n = 0 - xi_p -= xi_pdm - if xi_p < 0: - bio_p += xi_p - xi_p = 0 - if bio_p < 0: - S_IP += bio_p - bio_p = 0 + # xi_p -= xi_pdm + # if xi_p < 0: + # bio_p += xi_p + # xi_p = 0 + # if bio_p < 0: + # S_IP += bio_p + # bio_p = 0 - else: - if (bio_p + xi_p + S_IP) / asm_X_I_i_P > (bio_n + xi_n + S_IN) / asm_X_I_i_N: + # else: + # if (bio_p + xi_p + S_IP) / asm_X_I_i_P > (bio_n + xi_n + S_IN) / asm_X_I_i_N: - warn('Not enough N in biomass and X_I to map the specified proportion of' - 'biomass COD into X_I. Rest of the biomass COD goes to S_A') - X_I = (bio_n + xi_n + S_IN) / asm_X_I_i_N - xi_cod -= X_I + # warn('Not enough N in biomass and X_I to map the specified proportion of' + # 'biomass COD into X_I. Rest of the biomass COD goes to S_A') + # X_I = (bio_n + xi_n + S_IN) / asm_X_I_i_N + # xi_cod -= X_I - bio_n = xi_n = S_IN = 0 + # bio_n = xi_n = S_IN = 0 - xi_p -= X_I*asm_X_I_i_P - if xi_p < 0: - bio_p += xi_p - xi_p = 0 - if bio_p < 0: - S_IP += bio_p - bio_p = 0 + # xi_p -= X_I*asm_X_I_i_P + # if xi_p < 0: + # bio_p += xi_p + # xi_p = 0 + # if bio_p < 0: + # S_IP += bio_p + # bio_p = 0 - else: + # else: - warn('Not enough P in biomass and X_I to map the specified proportion of' - 'biomass COD into X_I. Rest of the biomass COD goes to S_A') - X_I = (bio_p + xi_p + S_IP) / asm_X_I_i_P - xi_cod -= X_I + # warn('Not enough P in biomass and X_I to map the specified proportion of' + # 'biomass COD into X_I. Rest of the biomass COD goes to S_A') + # X_I = (bio_p + xi_p + S_IP) / asm_X_I_i_P + # xi_cod -= X_I - bio_p = xi_p = S_IP = 0 + # bio_p = xi_p = S_IP = 0 - xi_n -= X_I*asm_X_I_i_N - if xi_n < 0: - bio_n += xi_n - xi_n = 0 - if bio_n < 0: - S_IN += bio_n - bio_n = 0 - - # Step 1b: convert particulate substrates into X_S + # xi_n -= X_I*asm_X_I_i_N + # if xi_n < 0: + # bio_n += xi_n + # xi_n = 0 + # if bio_n < 0: + # S_IN += bio_n + # bio_n = 0 + + # # Step 1b: convert particulate substrates into X_S - xsub_cod = X_ch + X_pr + X_li - xsub_n = X_pr*X_pr_i_N - xsub_p = X_pr*X_pr_i_P - - xs_ndm = xsub_cod * X_S_i_N - xs_pdm = xsub_cod * X_S_i_P - - if xs_ndm <= xsub_n + bio_n and xs_pdm <= xsub_p + bio_p: - X_S += xsub_cod - xsub_cod = 0 - xsub_n -= xs_ndm - xsub_p -= xs_pdm - else: - if (xsub_n + bio_n)/X_S_i_N < (xsub_p + bio_p)/X_S_i_P: - X_S_temp = (xsub_n + bio_n)/X_S_i_N - X_S += X_S_temp - xsub_cod -= X_S_temp - xsub_n = bio_n = 0 + # xsub_cod = X_ch + X_pr + X_li + # xsub_n = X_pr*X_pr_i_N + # xsub_p = X_pr*X_pr_i_P + + # xs_ndm = xsub_cod * X_S_i_N + # xs_pdm = xsub_cod * X_S_i_P + + # if xs_ndm <= xsub_n + bio_n and xs_pdm <= xsub_p + bio_p: + # X_S += xsub_cod + # xsub_cod = 0 + # xsub_n -= xs_ndm + # xsub_p -= xs_pdm + # else: + # if (xsub_n + bio_n)/X_S_i_N < (xsub_p + bio_p)/X_S_i_P: + # X_S_temp = (xsub_n + bio_n)/X_S_i_N + # X_S += X_S_temp + # xsub_cod -= X_S_temp + # xsub_n = bio_n = 0 - xsub_p -= X_S_temp*X_S_i_P - if xsub_p < 0: - bio_p += xsub_p - xsub_p = 0 + # xsub_p -= X_S_temp*X_S_i_P + # if xsub_p < 0: + # bio_p += xsub_p + # xsub_p = 0 - else: - X_S_temp = (xsub_p + bio_p)/X_S_i_P - X_S += X_S_temp - xsub_cod -= X_S_temp - xsub_p = bio_p = 0 + # else: + # X_S_temp = (xsub_p + bio_p)/X_S_i_P + # X_S += X_S_temp + # xsub_cod -= X_S_temp + # xsub_p = bio_p = 0 - xsub_n -= X_S_temp*X_S_i_N - if xsub_n < 0: - bio_n += xsub_n - xsub_n = 0 + # xsub_n -= X_S_temp*X_S_i_N + # if xsub_n < 0: + # bio_n += xsub_n + # xsub_n = 0 - # P balance not required as S_su, S_aa, S_fa do not have P - ssub_cod = S_su + S_aa + S_fa - ssub_n = S_aa * S_aa_i_N - ssub_p = S_aa * S_aa_i_P # which would be 0 + # # P balance not required as S_su, S_aa, S_fa do not have P + # ssub_cod = S_su + S_aa + S_fa + # ssub_n = S_aa * S_aa_i_N + # ssub_p = S_aa * S_aa_i_P # which would be 0 - sf_ndm = ssub_cod * S_F_i_N - sf_pdm = ssub_cod * S_F_i_P + # sf_ndm = ssub_cod * S_F_i_N + # sf_pdm = ssub_cod * S_F_i_P - if sf_ndm <= ssub_n + xsub_n + bio_n and sf_pdm <= ssub_p + xsub_p + bio_p: + # if sf_ndm <= ssub_n + xsub_n + bio_n and sf_pdm <= ssub_p + xsub_p + bio_p: - S_F = ssub_cod - ssub_cod = 0 + # S_F = ssub_cod + # ssub_cod = 0 - ssub_n -= sf_ndm - if ssub_n < 0: - xsub_n += ssub_n - ssub_n = 0 - if xsub_n < 0: - bio_n += xsub_n - xsub_n = 0 + # ssub_n -= sf_ndm + # if ssub_n < 0: + # xsub_n += ssub_n + # ssub_n = 0 + # if xsub_n < 0: + # bio_n += xsub_n + # xsub_n = 0 - ssub_p -= sf_pdm - if ssub_p < 0: - xsub_p += ssub_p - ssub_p = 0 - if xsub_p < 0: - bio_p += xsub_p - xsub_p = 0 + # ssub_p -= sf_pdm + # if ssub_p < 0: + # xsub_p += ssub_p + # ssub_p = 0 + # if xsub_p < 0: + # bio_p += xsub_p + # xsub_p = 0 - else: - if (ssub_n + xsub_n + bio_n) / S_F_i_N < (ssub_p + xsub_p + bio_p) / S_F_i_P: + # else: + # if (ssub_n + xsub_n + bio_n) / S_F_i_N < (ssub_p + xsub_p + bio_p) / S_F_i_P: - S_F = (ssub_n + xsub_n + bio_n) / S_F_i_N - ssub_cod -= S_F - ssub_n = xsub_n = bio_n = 0 + # S_F = (ssub_n + xsub_n + bio_n) / S_F_i_N + # ssub_cod -= S_F + # ssub_n = xsub_n = bio_n = 0 - ssub_p -= S_F*S_F_i_P + # ssub_p -= S_F*S_F_i_P - if ssub_p < 0: - xsub_p += ssub_p - ssub_p = 0 - if xsub_p < 0: - bio_p += xsub_p - xsub_p = 0 + # if ssub_p < 0: + # xsub_p += ssub_p + # ssub_p = 0 + # if xsub_p < 0: + # bio_p += xsub_p + # xsub_p = 0 - else: + # else: - S_F = (ssub_p + xsub_p + bio_p) / S_F_i_P - ssub_cod -= S_F - ssub_p = xsub_p = bio_p = 0 + # S_F = (ssub_p + xsub_p + bio_p) / S_F_i_P + # ssub_cod -= S_F + # ssub_p = xsub_p = bio_p = 0 - ssub_n -= S_F*S_F_i_N + # ssub_n -= S_F*S_F_i_N - if ssub_n < 0: - xsub_n += ssub_n - ssub_n = 0 - if xsub_n < 0: - bio_n += xsub_n - xsub_n = 0 + # if ssub_n < 0: + # xsub_n += ssub_n + # ssub_n = 0 + # if xsub_n < 0: + # bio_n += xsub_n + # xsub_n = 0 - # N and P balance not required as S_su, S_aa, S_fa do not have N and P - S_A = S_ac + S_pro + S_bu + S_va - - si_cod = S_I - si_n = S_I * adm_S_I_i_N - si_p = S_I * adm_S_I_i_P - - si_ndm = si_cod * asm_S_I_i_N - si_pdm = si_cod * asm_S_I_i_P - - if si_ndm <= si_n + xi_n + S_IN and si_pdm <= si_p + xi_p + S_IP: - S_I = si_cod - si_cod = 0 - si_n -= si_ndm - if si_n < 0: - xi_n += si_n - si_n = 0 - if xi_n < 0: - S_IN += xi_n - xi_n = 0 - si_p -= si_pdm - if si_p < 0: - xi_p += si_p - si_p = 0 - if xi_p < 0: - S_IP += xi_p - xi_p = 0 - else: - if (si_n + xi_n + S_IN) / asm_S_I_i_N < (si_p + xi_p + S_IP) / asm_S_I_i_P: - S_I = (si_n + xi_n + S_IN) / asm_S_I_i_N - si_cod -= S_I - si_n = xi_n = S_IN = 0 - si_p -= S_I * asm_S_I_i_P - if si_p < 0: - xi_p += si_p - si_p = 0 - if xi_p < 0: - S_IP += xi_p - xi_p = 0 - else: - S_I = (si_p + xi_p + S_IP) / asm_S_I_i_P - si_cod -= S_I - si_p = xi_p = S_IP = 0 - si_n -= S_I * asm_S_I_i_N - if si_n < 0: - xi_n += si_n - si_n = 0 - if xi_n < 0: - S_IN += xi_n - xi_n = 0 - - S_NH4 = S_IN + si_n + ssub_n + xsub_n + xi_n + bio_n - S_PO4 = S_IP + si_p + ssub_p + xsub_p + xi_p + bio_p - S_A += si_cod + ssub_cod + xsub_cod + xi_cod + xs_cod + # # N and P balance not required as S_su, S_aa, S_fa do not have N and P + # S_A = S_ac + S_pro + S_bu + S_va + + # si_cod = S_I + # si_n = S_I * adm_S_I_i_N + # si_p = S_I * adm_S_I_i_P + + # si_ndm = si_cod * asm_S_I_i_N + # si_pdm = si_cod * asm_S_I_i_P + + # if si_ndm <= si_n + xi_n + S_IN and si_pdm <= si_p + xi_p + S_IP: + # S_I = si_cod + # si_cod = 0 + # si_n -= si_ndm + # if si_n < 0: + # xi_n += si_n + # si_n = 0 + # if xi_n < 0: + # S_IN += xi_n + # xi_n = 0 + # si_p -= si_pdm + # if si_p < 0: + # xi_p += si_p + # si_p = 0 + # if xi_p < 0: + # S_IP += xi_p + # xi_p = 0 + # else: + # if (si_n + xi_n + S_IN) / asm_S_I_i_N < (si_p + xi_p + S_IP) / asm_S_I_i_P: + # S_I = (si_n + xi_n + S_IN) / asm_S_I_i_N + # si_cod -= S_I + # si_n = xi_n = S_IN = 0 + # si_p -= S_I * asm_S_I_i_P + # if si_p < 0: + # xi_p += si_p + # si_p = 0 + # if xi_p < 0: + # S_IP += xi_p + # xi_p = 0 + # else: + # S_I = (si_p + xi_p + S_IP) / asm_S_I_i_P + # si_cod -= S_I + # si_p = xi_p = S_IP = 0 + # si_n -= S_I * asm_S_I_i_N + # if si_n < 0: + # xi_n += si_n + # si_n = 0 + # if xi_n < 0: + # S_IN += xi_n + # xi_n = 0 + + # S_NH4 = S_IN + si_n + ssub_n + xsub_n + xi_n + bio_n + # S_PO4 = S_IP + si_p + ssub_p + xsub_p + xi_p + bio_p + # S_A += si_cod + ssub_cod + xsub_cod + xi_cod + xs_cod # Step 6: check COD and TKN balance asm_vals = np.array(([ @@ -2253,54 +2322,47 @@ def madm12asm2d(adm_vals): S_NH4, 0, # S_NO3 S_PO4, S_F, S_A, S_I, - 0, # S_ALK(for now) + S_ALK, X_I, X_S, 0, # X_H, - X_PAO, X_PP, X_PHA, # directly mapped + 0,0,0,# X_PAO, X_PP, X_PHA, # directly mapped 0, # X_AUT, X_MeOH, X_MeP, H2O])) # directly mapped - - if S_h2 > 0 or S_ch4 > 0: - warn('Ignored dissolved H2 or CH4.') - + asm_vals = f_corr(adm_vals, asm_vals) - # Step 5: charge balance for alkalinity + # # Step 5: charge balance for alkalinity - # asm_ions_idx = cmps_asm.indices(('S_NH4', 'S_A', 'S_NO3', 'S_PO4', 'X_PP', 'S_ALK')) + # # asm_ions_idx = cmps_asm.indices(('S_NH4', 'S_A', 'S_NO3', 'S_PO4', 'X_PP', 'S_ALK')) - S_NH4 = asm_vals[asm_ions_idx[0]] - S_A = asm_vals[asm_ions_idx[1]] - S_NO3 = asm_vals[asm_ions_idx[2]] - S_PO4 = asm_vals[asm_ions_idx[3]] - X_PP = asm_vals[asm_ions_idx[4]] + # S_NH4 = asm_vals[asm_ions_idx[0]] + # S_A = asm_vals[asm_ions_idx[1]] + # S_NO3 = asm_vals[asm_ions_idx[2]] + # S_PO4 = asm_vals[asm_ions_idx[3]] + # X_PP = asm_vals[asm_ions_idx[4]] - # Need to include S_K, S_Mg in the charge balance + # # Need to include S_K, S_Mg in the charge balance - # _ions = np.array([S_IN, S_IC, S_ac, S_pro, S_bu, S_va]) - # S_ALK = (sum(_ions * np.append([alpha_IN, alpha_IC], alpha_vfa)) - S_NH/14)*(-12) + # # _ions = np.array([S_IN, S_IC, S_ac, S_pro, S_bu, S_va]) + # # S_ALK = (sum(_ions * np.append([alpha_IN, alpha_IC], alpha_vfa)) - S_NH/14)*(-12) - # _ions = np.array([S_IN, S_IC, S_IP, X_PP, S_Mg, S_K, S_ac, S_pro, S_bu, S_va]) + # # _ions = np.array([S_IN, S_IC, S_IP, X_PP, S_Mg, S_K, S_ac, S_pro, S_bu, S_va]) - S_ALK = (sum(_ions * np.append([alpha_IN, alpha_IC, alpha_IP, -1/31, 2, 1], alpha_vfa)) - (S_NH4/14 - S_A/64 - S_NO3/14 -1.5*S_PO4/31 - X_PP/31))*(-12) + # S_ALK = (sum(_ions * np.append([alpha_IN, alpha_IC, alpha_IP, -1/31, 2, 1], alpha_vfa)) - (S_NH4/14 - S_A/64 - S_NO3/14 -1.5*S_PO4/31 - X_PP/31))*(-12) - asm_vals[asm_ions_idx[5]] = S_ALK + # asm_vals[asm_ions_idx[5]] = S_ALK return asm_vals self._reactions = madm12asm2d - @property - def alpha_vfa(self): - return 1.0/self.cod_vfa*(-1.0/(1.0 + 10**(self.pKa[4:]-self.pH))) #%% ASM2dtomADM1 -# While using this interface X_I.i_N in ASM2d should be 0.06, instead of 0.02. class ASM2dtomADM1(mADMjunction): ''' Interface unit to convert ASM2d state variables - to ADM1 components, following the A1 scenario in [1]_. + to ADM1 components, following the A1 scenario in [2]_. Parameters ---------- @@ -2313,11 +2375,6 @@ class ASM2dtomADM1(mADMjunction): xs_to_li : float Split of slowly biodegradable substrate COD to lipid, after all N is mapped into protein. - bio_to_li : float - Split of biomass COD to lipid, after all biomass N is - mapped into protein. - frac_deg : float - Biodegradable fraction of biomass COD. rtol : float Relative tolerance for COD and TKN balance. atol : float @@ -2336,11 +2393,10 @@ class ASM2dtomADM1(mADMjunction): See Also -------- - :class:`qsdsan.sanunits.ADMjunction` + :class:`qsdsan.sanunits.mADMjunction` - :class:`qsdsan.sanunits.ADMtoASM` + :class:`qsdsan.sanunits.mADM1toASM2d` - `math.isclose ` ''' # User defined values xs_to_li = 0.7 @@ -2462,6 +2518,7 @@ def _compile_reactions(self): cmps_asm = self.ins[0].components cmps_adm = self.outs[0].components + self.check_component_properties(cmps_asm, cmps_adm) # # For COD balance # S_NO3_i_COD = cmps_asm.S_NO3.i_COD @@ -2500,34 +2557,6 @@ def _compile_reactions(self): # asm_S_I_i_P = cmps_asm.S_I.i_P # else: # asm_S_I_i_P = self.asm_S_I_i_P - get = getattr - setv = setattr - for name in ('X_PHA', 'X_PP', 'X_PAO', 'X_MeOH', 'X_MeP'): - casm = get(cmps_asm, name) - cadm = get(cmps_adm, name) - for attr in ('measured_as', 'i_COD', 'i_N', 'i_P'): - vasm = get(casm, attr) - if get(cadm, attr) != vasm: - setv(cadm, attr, vasm) - warn(f"ADM component {name}'s {attr} is changed to match " - "the corresponding ASM component") - - for name in ('S_I', 'X_I'): - casm = get(cmps_asm, name) - cadm = get(cmps_adm, name) - for attr in ('measured_as', 'i_N', 'i_P'): - vadm = get(cadm, attr) - if get(casm, attr) != vadm: - setv(casm, attr, vadm) - warn(f"ASM component {name}'s {attr} is changed to match " - "the corresponding ADM component") - - for attr in ('i_N', 'i_P'): - vadm = get(cmps_adm.S_ac, attr) - if get(cmps_asm.S_A, attr) != vadm: - cmps_asm.S_A.i_N = vadm - warn(f"ASM component S_A's {attr} is changed to match " - "the ADM component S_ac.") # if cmps_asm.S_A.i_N > 0: # warn(f'S_A in ASM has positive nitrogen content: {cmps_asm.S_S.i_N} gN/gCOD. ' @@ -2539,8 +2568,7 @@ def _compile_reactions(self): # 'These phosphorous will be ignored by the interface model ' # 'and could lead to imbalance of TP after conversion.') - cmps_asm.refresh_constants() - cmps_adm.refresh_constants() + # ------------------------------Rai Version--------------------------------------------- # if cmps_asm.S_I.i_P > 0: # warn(f'S_I in ASM has positive phosphorous content: {cmps_asm.S_I.i_P} gN/gCOD. ' From 8d9070b319482ac86f75d93385f42a9db138ea9b Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Tue, 14 May 2024 15:01:37 -0700 Subject: [PATCH 336/424] fix typo --- qsdsan/sanunits/_junction.py | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/qsdsan/sanunits/_junction.py b/qsdsan/sanunits/_junction.py index 925b4fe6..30a0116f 100644 --- a/qsdsan/sanunits/_junction.py +++ b/qsdsan/sanunits/_junction.py @@ -377,20 +377,15 @@ class mADMjunction(ADMjunction): atol = 1e-6 cod_vfa = np.array([64, 112, 160, 208]) - # def __init__(self, ID='', upstream=None, downstream=(), thermo=None, - # init_with='WasteStream', F_BM_default=None, isdynamic=False, - # adm1_model=None): - # self.adm1_model = adm1_model # otherwise there won't be adm1_model when `_compile_reactions` is called - - # if thermo is None: - # warn('No `thermo` object is provided and is prone to raise error. ' - # 'If you are not sure how to get the `thermo` object, ' - # 'use `thermo = qsdsan.set_thermo` after setting thermo with the `Components` object.') - # super().__init__(ID=ID, upstream=upstream, downstream=downstream, - # thermo=thermo, init_with=init_with, - # F_BM_default=F_BM_default, isdynamic=isdynamic) + def __init__(self, ID='', upstream=None, downstream=(), thermo=None, + init_with='WasteStream', F_BM_default=None, isdynamic=False, + adm1_model=None, asm2d_model=None): + self.asm2d_model = asm2d_model + super().__init__(ID=ID, upstream=upstream, downstream=downstream, + thermo=thermo, init_with=init_with, + F_BM_default=F_BM_default, isdynamic=isdynamic, + adm1_model=adm1_model) - # @property # def T(self): # '''[float] Temperature of the upstream/downstream [K].''' @@ -1896,8 +1891,8 @@ def _compile_reactions(self): # rtol = self.rtol # atol = self.atol - cmps_asm = self.ins[0].components - cmps_adm = self.outs[0].components + cmps_adm = self.ins[0].components + cmps_asm = self.outs[0].components self.check_component_properties(cmps_asm, cmps_adm) _asm_ids = cmps_asm.indices(['S_F', 'X_S', 'S_A']) From 76af338faa6811f8f3e587c408dcb39720dad299 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Tue, 14 May 2024 16:45:54 -0700 Subject: [PATCH 337/424] minor bug fix --- qsdsan/sanunits/_junction.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qsdsan/sanunits/_junction.py b/qsdsan/sanunits/_junction.py index 30a0116f..cf3bc2da 100644 --- a/qsdsan/sanunits/_junction.py +++ b/qsdsan/sanunits/_junction.py @@ -2002,7 +2002,7 @@ def madm12asm2d(adm_vals): # PROCESS 1: decay of biomas, X_PP, X_PHA bio_xpp_pha = _adm_vals[adm_p1_idx] - _adm_vals += bio_xpp_pha * decay_stoichio + _adm_vals += np.dot(bio_xpp_pha, decay_stoichio) # PROCESS 2: strip biogas. Omitted because no S_ch4 or S_h2 in ASM2d components From bbccb55bc04edeef81bb2d43eeeb8a1576be8001 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Fri, 17 May 2024 13:31:19 -0700 Subject: [PATCH 338/424] A1 algorithm made separate subclasses --- qsdsan/sanunits/_junction.py | 462 ++++++++++++++++++++++++++++++++++- 1 file changed, 460 insertions(+), 2 deletions(-) diff --git a/qsdsan/sanunits/_junction.py b/qsdsan/sanunits/_junction.py index cf3bc2da..4fb9d3f1 100644 --- a/qsdsan/sanunits/_junction.py +++ b/qsdsan/sanunits/_junction.py @@ -2320,7 +2320,7 @@ def madm12asm2d(adm_vals): S_ALK, X_I, X_S, 0, # X_H, - 0,0,0,# X_PAO, X_PP, X_PHA, # directly mapped + X_PAO, X_PP, X_PHA, # directly mapped 0, # X_AUT, X_MeOH, X_MeP, H2O])) # directly mapped @@ -3036,4 +3036,462 @@ def asm2d2madm1(asm_vals): return adm_vals - self._reactions = asm2d2madm1 \ No newline at end of file + self._reactions = asm2d2madm1 + +#%% ADM1ptoASM2d_A1 +class ADM1ptoASM2d_A1(mADMjunction): + ''' + Interface unit to convert ADM1 state variables + to ASM2d components, following the A1 algorithm in [1]_. + + Parameters + ---------- + upstream : stream or str + Influent stream with ADM components. + downstream : stream or str + Effluent stream with ASM components. + adm1_model : obj + The anaerobic digestion process model (:class:`qsdsan.processes.ADM1_p_extension`). + rtol : float + Relative tolerance for COD and TKN balance. + atol : float + Absolute tolerance for COD and TKN balance. + + References + ---------- + [1] Flores-Alsina, X., Solon, K., Kazadi Mbamba, C., Tait, S., Gernaey, K. V., + Jeppsson, U., & Batstone, D. J. (2016). Modelling phosphorus (P), sulfur (S) + and iron (FE) interactions for dynamic simulations of anaerobic digestion processes. + Water Research, 95, 370–382. + + See Also + -------- + :class:`qsdsan.sanunits.mADMjunction` + + :class:`qsdsan.sanunits.ASM2dtoADM1p_A1` + ''' + + def balance_cod_tkn(self, adm_vals, asm_vals): + cmps_adm = self.ins[0].components + cmps_asm = self.outs[0].components + asm_i_COD = cmps_asm.i_COD + adm_i_COD = cmps_adm.i_COD + gas_idx = cmps_adm.indices(('S_h2', 'S_ch4')) + + asm_i_N = cmps_asm.i_N + adm_i_N = cmps_adm.i_N + asm_i_P = cmps_asm.i_P + adm_i_P = cmps_adm.i_P + adm_cod = sum(adm_vals*adm_i_COD) - sum(adm_vals[gas_idx]) + adm_tkn = sum(adm_vals*adm_i_N) + adm_tp = sum(adm_vals*adm_i_P) + + cod_bl, cod_err, cod_tol, asm_cod = self.isbalanced(adm_cod, asm_vals, asm_i_COD) + tkn_bl, tkn_err, tkn_tol, asm_tkn = self.isbalanced(adm_tkn, asm_vals, asm_i_N) + tp_bl, tp_err, tp_tol, asm_tp = self.isbalanced(adm_tp, asm_vals, asm_i_P) + + if tkn_bl and tp_bl: + if cod_bl: + return asm_vals + else: + if cod_err > 0: dcod = -(cod_err - cod_tol)/asm_cod + else: dcod = -(cod_err + cod_tol)/asm_cod + _asm_vals = asm_vals * (1 + (asm_i_COD>0)*dcod) + _tkn_bl, _tkn_err, _tkn_tol, _asm_tkn = self.isbalanced(adm_tkn, _asm_vals, asm_i_N) + _tp_bl, _tp_err, _tp_tol, _asm_tp = self.isbalanced(adm_tp, _asm_vals, asm_i_P) + if _tkn_bl and _tp_bl: return _asm_vals + else: + warn('cannot balance COD, TKN, and TP at the same \n' + f'time with rtol={self.rtol} and atol={self.atol}.\n ' + f'influent (ADM) TKN is {adm_tkn}\n ' + f'effluent (ASM) TKN is {asm_tkn} or {_asm_tkn}\n ' + f'influent (ADM) TP is {adm_tp}\n ' + f'effluent (ASM) TP is {asm_tp} or {_asm_tp}. ' + f'influent (ADM) COD is {adm_cod}\n ' + f'effluent (ASM) COD is {asm_cod} or {asm_cod*(1+dcod)}. ') + return asm_vals + elif cod_bl and tp_bl: + if tkn_bl: + return asm_vals + else: + if tkn_err > 0: dtkn = -(tkn_err - tkn_tol)/asm_tkn + else: dtkn = -(tkn_err + tkn_tol)/asm_tkn + _asm_vals = asm_vals * (1 + (asm_i_N>0)*dtkn) + _cod_bl, _cod_err, _cod_tol, _asm_cod = self.isbalanced(adm_cod, _asm_vals, asm_i_COD) + _tp_bl, _tp_err, _tp_tol, _asm_tp = self.isbalanced(adm_tp, _asm_vals, asm_i_P) + if _cod_bl and _tp_bl: return _asm_vals + else: + warn('cannot balance COD, TKN, and TP at the same time' + f'time with rtol={self.rtol} and atol={self.atol}.\n ' + f'influent (ADM) COD is {adm_cod}\n ' + f'effluent (ASM) COD is {asm_cod} or {_asm_cod}\n ' + f'influent (ADM) TP is {adm_tp}\n ' + f'effluent (ASM) TP is {asm_tp} or {_asm_tp}. ' + f'influent (ADM) TKN is {adm_tkn}\n ' + f'effluent (ASM) TKN is {asm_tkn} or {asm_tkn*(1+dtkn)}. ') + return asm_vals + elif cod_bl and tkn_bl: + if tp_bl: + return asm_vals + else: + if tp_err > 0: dtp = -(tp_err - tp_tol)/asm_tp + else: dtp = -(tp_err + tp_tol)/asm_tp + _asm_vals = asm_vals * (1 + (asm_i_P>0)*dtp) + _cod_bl, _cod_err, _cod_tol, _asm_cod = self.isbalanced(adm_cod, _asm_vals, asm_i_COD) + _tkn_bl, _tkn_err, _tkn_tol, _asm_tkn = self.isbalanced(adm_tkn, _asm_vals, asm_i_N) + if _cod_bl and _tkn_bl: return _asm_vals + else: + warn('cannot balance COD, TKN, and TP at the same time' + f'time with rtol={self.rtol} and atol={self.atol}.\n ' + f'influent (ADM) COD is {adm_cod}\n ' + f'effluent (ASM) COD is {asm_cod} or {_asm_cod}\n ' + f'influent (ADM) TKN is {adm_tkn}\n ' + f'effluent (ASM) TKN is {asm_tkn} or {_asm_tkn}. ' + f'influent (ADM) TP is {adm_tp}\n ' + f'effluent (ASM) TP is {asm_tp} or {asm_tp*(1+dtp)}. ') + return asm_vals + else: + warn('cannot balance COD, TKN and TP at the same time. \n' + 'Atleast two of the three COD, TKN, and TP are not balanced \n' + f'time with rtol={self.rtol} and atol={self.atol}.\n ' + f'influent (ADM) COD is {adm_cod}\n ' + f'effluent (ASM) COD is {asm_cod}\n ' + f'influent (ADM) TP is {adm_tp}\n ' + f'effluent (ASM) TP is {asm_tp}' + f'influent (ADM) TKN is {adm_tkn}\n ' + f'effluent (ASM) TKN is {asm_tkn}. ') + return asm_vals + + def _compile_reactions(self): + cmps_adm = self.ins[0].components + cmps_asm = self.outs[0].components + self.check_component_properties(cmps_asm, cmps_adm) + + _asm_ids = cmps_asm.indices(['S_F', 'X_S', 'S_A']) + _adm_ids = cmps_adm.indices(['S_su', 'S_aa', 'S_fa', + 'S_va', 'S_bu', 'S_pro', 'S_ac', + 'X_pr', 'X_li', 'X_ch']) + + # For carbon balance + C_SF, C_XS, C_SA = cmps_asm.i_C[_asm_ids] + C_su, C_aa, C_fa, C_va, C_bu, C_pro, C_ac, C_pr, C_li, C_ch = cmps_adm.i_C[_adm_ids] + + # For nitrogen balance + N_SF, N_XS, N_SA = cmps_asm.i_N[_asm_ids] + N_su, N_aa, N_fa, N_va, N_bu, N_pro, N_ac, N_pr, N_li, N_ch = cmps_adm.i_N[_adm_ids] + + # For phosphorous balance + P_SF, P_XS, P_SA = cmps_asm.i_P[_asm_ids] + P_su, P_aa, P_fa, P_va, P_bu, P_pro, P_ac, P_pr, P_li, P_ch = cmps_adm.i_P[_adm_ids] + + adm = self.adm1_model + adm_p1_idx = cmps_adm.indices(('X_su', 'X_aa', 'X_fa', 'X_c4', + 'X_pro', 'X_ac', 'X_h2', + 'X_PAO', 'X_PP', 'X_PHA')) + decay_idx = [i for i in adm.IDs if i.startswith(('decay', 'lysis'))] + decay_stoichio = np.asarray(adm.stoichiometry.loc[decay_idx]) + f_corr = self.balance_cod_tkn + + # To convert components from ADM1p to ASM2d (A1) + def madm12asm2d(adm_vals): + + _adm_vals = adm_vals.copy() + + # PROCESS 1: decay of biomas, X_PP, X_PHA + bio_xpp_pha = _adm_vals[adm_p1_idx] + _adm_vals += np.dot(bio_xpp_pha, decay_stoichio) + + # PROCESS 2: strip biogas. Omitted because no S_ch4 or S_h2 in ASM2d components + + S_su, S_aa, S_fa, S_va, S_bu, S_pro, S_ac, S_h2, S_ch4, S_IC, S_IN, S_IP, S_I, \ + X_ch, X_pr, X_li, X_su, X_aa, X_fa, X_c4, X_pro, X_ac, X_h2, X_I, \ + X_PHA, X_PP, X_PAO, S_K, S_Mg, X_MeOH, X_MeP, S_cat, S_an, H2O = _adm_vals + + if S_h2 > 0 or S_ch4 > 0: warn('Ignored dissolved H2 or CH4.') + + S_ALK = S_IC + S_NH4 = S_IN + S_PO4 = S_IP + + # CONV 1: convert X_pr, X_li, X_ch to X_S + X_S = X_pr + X_li + X_ch + S_ALK += X_pr*C_pr + X_li*C_li + X_ch*C_ch - X_S*C_XS + S_NH4 += X_pr*N_pr + X_li*N_li + X_ch*N_ch - X_S*N_XS + S_PO4 += X_pr*P_pr + X_li*P_li + X_ch*P_ch - X_S*P_XS + + # CONV 2: convert S_su, S_aa, S_fa to S_F + S_F = S_su + S_aa + S_fa + S_ALK += S_su*C_su + S_aa*C_aa + S_fa*C_fa - S_F*C_SF + S_NH4 += S_su*N_su + S_aa*N_aa + S_fa*N_fa - S_F*N_SF + S_PO4 += S_su*P_su + S_aa*P_aa + S_fa*P_fa - S_F*P_SF + + # CONV 3: convert VFAs to S_A + S_A = S_va + S_bu + S_pro + S_ac + S_ALK += S_va*C_va + S_bu*C_bu + S_pro*C_pro + S_ac*C_ac - S_A*C_SA + # S_NH4 += S_va*N_va + S_bu*N_bu + S_pro*N_pro + S_ac*N_ac - S_A*N_SA + # S_PO4 += S_va*P_va + S_bu*P_bu + S_pro*P_pro + S_ac*P_ac - S_A*P_SA + + asm_vals = np.array(([ + 0, 0, # S_O2, S_N2, + S_NH4, + 0, # S_NO3 + S_PO4, S_F, S_A, S_I, + S_ALK, + X_I, X_S, + 0, # X_H, + 0,0,0,# X_PAO, X_PP, X_PHA, + 0, # X_AUT, + X_MeOH, X_MeP, H2O])) # directly mapped + + asm_vals = f_corr(adm_vals, asm_vals) + return asm_vals + + self._reactions = madm12asm2d + +#%% ASM2dtoADM1p_A1 + +class ASM2dtoADM1p_A1(mADMjunction): + ''' + Interface unit to convert ASM2d state variables + to ADM1 components, following the A1 scenario in [1]_. + + Parameters + ---------- + upstream : stream or str + Influent stream with ASM components. + downstream : stream or str + Effluent stream with ADM components. + adm1_model : :class:`qsdsan.processes.ADM1_p_extension` + The anaerobic digestion process model. + xs_to_li : float + Split of slowly biodegradable substrate COD to lipid, + after all N is mapped into protein. + rtol : float + Relative tolerance for COD and TKN balance. + atol : float + Absolute tolerance for COD and TKN balance. + + References + ---------- + [1] Flores-Alsina, X., Solon, K., Kazadi Mbamba, C., Tait, S., Gernaey, K. V., + Jeppsson, U., & Batstone, D. J. (2016). Modelling phosphorus (P), sulfur (S) + and iron (FE) interactions for dynamic simulations of anaerobic digestion processes. + Water Research, 95, 370–382. + + See Also + -------- + :class:`qsdsan.sanunits.mADMjunction` + + :class:`qsdsan.sanunits.ADM1ptoASM2d_A1` + + ''' + # User defined values + xs_to_li = 0.7 + + def balance_cod_tkn_tp(self, asm_vals, adm_vals): + cmps_asm = self.ins[0].components + cmps_adm = self.outs[0].components + asm_i_COD = cmps_asm.i_COD + adm_i_COD = cmps_adm.i_COD + non_tkn_idx = cmps_asm.indices(('S_N2', 'S_NO3')) + asm_i_N = cmps_asm.i_N + + adm_i_N = cmps_adm.i_N + asm_i_P = cmps_asm.i_P + adm_i_P = cmps_adm.i_P + asm_cod = sum(asm_vals*asm_i_COD) + asm_tkn = sum(asm_vals*asm_i_N) - sum(asm_vals[non_tkn_idx]) + asm_tp = sum(asm_vals*asm_i_P) + cod_bl, cod_err, cod_tol, adm_cod = self.isbalanced(asm_cod, adm_vals, adm_i_COD) + tkn_bl, tkn_err, tkn_tol, adm_tkn = self.isbalanced(asm_tkn, adm_vals, adm_i_N) + tp_bl, tp_err, tp_tol, adm_tp = self.isbalanced(asm_tp, adm_vals, adm_i_P) + + if tkn_bl and tp_bl: + if cod_bl: + return adm_vals + else: + if cod_err > 0: dcod = -(cod_err - cod_tol)/adm_cod + else: dcod = -(cod_err + cod_tol)/adm_cod + _adm_vals = adm_vals * (1 + (adm_i_COD>0)*dcod) + _tkn_bl, _tkn_err, _tkn_tol, _adm_tkn = self.isbalanced(asm_tkn, _adm_vals, adm_i_N) + _tp_bl, _tp_err, _tp_tol, _adm_tp = self.isbalanced(asm_tp, _adm_vals, adm_i_P) + if _tkn_bl and _tp_bl: return _adm_vals + else: + warn('cannot balance COD, TKN, and TP at the same \n' + f'time with rtol={self.rtol} and atol={self.atol}.\n ' + f'influent (ASM) TKN is {asm_tkn}\n ' + f'effluent (ADM) TKN is {adm_tkn} or {_adm_tkn}\n ' + f'influent TP is {asm_tp}\n ' + f'effluent TP is {adm_tp} or {_adm_tp}. ' + f'influent COD is {asm_cod}\n ' + f'effluent COD is {adm_cod} or {adm_cod*(1+dcod)}. ') + return adm_vals + elif cod_bl and tp_bl: + if tkn_bl: + return adm_vals + else: + if tkn_err > 0: dtkn = -(tkn_err - tkn_tol)/adm_tkn + else: dtkn = -(tkn_err + tkn_tol)/adm_tkn + _adm_vals = adm_vals * (1 + (adm_i_N>0)*dtkn) + _cod_bl, _cod_err, _cod_tol, _adm_cod = self.isbalanced(asm_cod, _adm_vals, adm_i_COD) + _tp_bl, _tp_err, _tp_tol, _adm_tp = self.isbalanced(asm_tp, _adm_vals, adm_i_P) + if _cod_bl and _tp_bl: return _adm_vals + else: + warn('cannot balance COD, TKN, and TP at the same time' + f'time with rtol={self.rtol} and atol={self.atol}.\n ' + f'influent (ASM) COD is {asm_cod}\n ' + f'effluent (ADM) COD is {adm_cod} or {_adm_cod}\n ' + f'influent TP is {asm_tp}\n ' + f'effluent TP is {adm_tp} or {_adm_tp}. ' + f'influent TKN is {asm_tkn}\n ' + f'effluent TKN is {adm_tkn} or {adm_tkn*(1+dtkn)}. ' + 'To balance TKN please ensure ASM2d(X_I.i_N) = ADM1(X_I.i_N)') + return adm_vals + elif cod_bl and tkn_bl: + if tp_bl: + return adm_vals + else: + if tp_err > 0: dtp = -(tp_err - tp_tol)/adm_tp + else: dtp = -(tp_err + tp_tol)/adm_tp + _adm_vals = adm_vals * (1 + (adm_i_P>0)*dtp) + _cod_bl, _cod_err, _cod_tol, _adm_cod = self.isbalanced(asm_cod, _adm_vals, adm_i_COD) + _tkn_bl, _tkn_err, _tkn_tol, _adm_tkn = self.isbalanced(asm_tkn, _adm_vals, adm_i_N) + if _cod_bl and _tkn_bl: return _adm_vals + else: + warn('cannot balance COD, TKN, and TP at the same time' + f'time with rtol={self.rtol} and atol={self.atol}.\n ' + f'influent (ASM) COD is {asm_cod}\n ' + f'effluent (ADM) COD is {adm_cod} or {_adm_cod}\n ' + f'influent TKN is {asm_tkn}\n ' + f'effluent TKN is {adm_tkn} or {_adm_tkn}. ' + f'influent TP is {asm_tp}\n ' + f'effluent TP is {adm_tp} or {adm_tp*(1+dtp)}. ') + return adm_vals + else: + warn('cannot balance COD, TKN, and TP at the same time. \n' + 'Atleast two of the three COD, TKN, and TP are not balanced \n' + f'time with rtol={self.rtol} and atol={self.atol}.\n ' + f'influent (ASM) COD is {asm_cod}\n ' + f'effluent (ADM) COD is {adm_cod}\n ' + f'influent TP is {asm_tp}\n ' + f'effluent TP is {adm_tp}' + f'influent TKN is {asm_tkn}\n ' + f'effluent TKN is {adm_tkn}. ') + return adm_vals + + def _compile_reactions(self): + cmps_asm = self.ins[0].components + cmps_adm = self.outs[0].components + self.check_component_properties(cmps_asm, cmps_adm) + + _asm_ids = cmps_asm.indices(['S_F', 'X_S', 'X_H', 'S_I', 'X_I']) + _adm_ids = cmps_adm.indices(['S_aa', 'S_su', 'X_pr', 'X_li', 'X_ch']) + # For carbon balance + C_SF, C_XS, C_XB, C_SI, C_XI = cmps_asm.i_C[_asm_ids] + C_aa, C_su, C_pr, C_li, C_ch = cmps_adm.i_C[_adm_ids] + + # For nitrogen balance + N_SF, N_XS, N_XB, N_SI, N_XI = cmps_asm.i_N[_asm_ids] + N_aa, N_su, N_pr, N_li, N_ch = cmps_adm.i_N[_adm_ids] + + # For phosphorous balance + P_SF, P_XS, P_XB, P_SI, P_XI = cmps_asm.i_P[_asm_ids] + P_aa, P_su, P_pr, P_li, P_ch = cmps_adm.i_P[_adm_ids] + + S_O2_idx, S_NO3_idx = cmps_asm.indices(['S_O2', 'S_NO3']) + f_corr = self.balance_cod_tkn_tp + + asm = self.asm2d_model + adm = self.adm1_model + p1_stoichio = np.asarray(asm.stoichiometry.loc['hetero_growth_S_A']) + p1_stoichio /= abs(p1_stoichio[S_O2_idx]) + p2_stoichio = np.asarray(asm.stoichiometry.loc['denitri_S_A']) + p2_stoichio /= abs(p2_stoichio[S_NO3_idx]) + p3_stoichio = np.array([adm.parameters[f'f_{k}_xb'] for k in ('sI', 'ch', 'pr', 'li', 'xI')]) + + # To convert components from ASM2d to mADM1 (asm2d-2-madm1) + def asm2d2madm1(asm_vals): + _asm_vals = asm_vals.copy() + + # PROCESS 1: remove S_O2 with S_A with associated X_H growth (aerobic growth of X_H on S_A) + O2_coddm = _asm_vals[S_O2_idx] + _asm_vals += O2_coddm * p1_stoichio # makes S_O2 = 0 + + # PROCESS 2: remove S_NO3 with S_A with associated X_H growth (denitrification on S_A) + NO3_coddm = _asm_vals[S_NO3_idx] + _asm_vals += NO3_coddm * p2_stoichio # makes S_NO3 = 0 + + S_O2, S_N2, S_NH4, S_NO3, S_PO4, S_F, S_A, S_I, S_ALK, X_I, X_S, X_H, \ + X_PAO, X_PP, X_PHA, X_AUT, X_MeOH, X_MeP, H2O = _asm_vals + + S_IC = S_ALK + S_IN = S_NH4 + S_IP = S_PO4 + + # CONV 1: transform S_F into S_aa, S_su, S_fa + S_ND = S_F*N_SF # N in S_F + req_scod = S_ND / N_aa + + if S_F < req_scod: # if S_F cod is not enough to convert all organic soluble N into aa + S_aa = S_F + S_su = 0 + else: # if S_F cod is more than enough to convert all organic soluble N into aa + S_aa = req_scod # All soluble organic N will be mapped to amino acid + S_su = S_F - S_aa + + S_IN += S_ND - S_aa*N_aa + S_IC += S_F*C_SF - (S_aa*C_aa + S_su*C_su) + S_IP += S_F*P_SF + + # PROCESS 3: biomass decay (X_H, X_AUT lysis) anaerobic + bio = X_H + X_AUT + _si, _ch, _pr, _li, _xi = bio * p3_stoichio + S_IC += bio*C_XB - (_si*C_SI + _ch*C_ch + _pr*C_pr + _li*C_li + _xi*C_XI) + S_IN += bio*N_XB - (_si*N_SI + _ch*N_ch + _pr*N_pr + _li*N_li + _xi*N_XI) + S_IP += bio*P_XB - (_si*P_SI + _ch*P_ch + _pr*P_pr + _li*P_li + _xi*P_XI) + + # CONV 2: transform asm X_S into X_pr, X_li, X_ch + X_ND = X_S*N_XS + req_xcod = X_ND / N_pr + # Since X_pr_i_N >> X_pr_i_P there's no need to check req_xcod for N and P separately (CONFIRM LATER 05/16) + + # if available X_S is not enough to fulfill that protein requirement + if X_S < req_xcod: # if X_S cod is not enough to convert all organic particulate N into pr + X_pr = X_S + X_li = X_ch = 0 + else: + X_pr = req_xcod + X_li = self.xs_to_li * (X_S - X_pr) + X_ch = (X_S - X_pr) - X_li + + S_IN += X_ND - X_pr*N_pr + S_IC += X_S*C_XS - (X_pr*C_pr + X_li*C_li + X_ch*C_ch) + S_IP += X_S*P_XS - (X_pr*P_pr + X_li*P_li + X_ch*P_ch) + + X_pr += _pr + X_li += _li + X_ch += _ch + S_I += _si + X_I += _xi + + # PROCESS 4-5: omitted, PAO related components mapped directly + # CONV 3-5: convert S_A, S_I, X_I; conversion is immediate because identical component composition is enforced + S_ac = S_A + + adm_vals = np.array([ + S_su, S_aa, + 0, 0, 0, 0, S_ac, # S_fa, S_va, S_bu, S_pro, + 0, 0, # S_h2, S_ch4, + S_IC, S_IN, S_IP, S_I, + X_ch, X_pr, X_li, + 0, 0, 0, 0, 0, 0, 0, # X_su, X_aa, X_fa, X_c4, X_pro, X_ac, X_h2, + X_I, X_PHA, X_PP, X_PAO, + 0, 0, # S_K, S_Mg, + X_MeOH, X_MeP, + 0, 0, H2O]) # S_cat, S_an + + # adm_vals = f_corr(asm_vals, adm_vals) + adm_vals = f_corr(_asm_vals, adm_vals) + return adm_vals + + self._reactions = asm2d2madm1 From b46e51cce1e6f190970a24e1572fdd00e6014806 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Fri, 17 May 2024 14:29:59 -0700 Subject: [PATCH 339/424] restore older versions of mADM1-ASM2d interfaces --- qsdsan/sanunits/_junction.py | 972 ++++++++++++++++++----------------- 1 file changed, 490 insertions(+), 482 deletions(-) diff --git a/qsdsan/sanunits/_junction.py b/qsdsan/sanunits/_junction.py index 4fb9d3f1..68271746 100644 --- a/qsdsan/sanunits/_junction.py +++ b/qsdsan/sanunits/_junction.py @@ -31,6 +31,8 @@ 'ADM1toASM2d', 'ASM2dtomADM1', 'mADM1toASM2d', + 'ADM1ptoASM2d_A1', + 'ASM2dtoADM1p_A1' ) #%% Junction @@ -437,14 +439,14 @@ def adm1_model(self, model): # '''[float] Heat of reaction for Ka.''' # return self.adm1_model.rate_function.params['Ka_dH'] - # @property - # def pKa(self): - # ''' - # [numpy.array] pKa array of the following acid-base pairs: - # ('H+', 'OH-'), ('NH4+', 'NH3'), ('H2PO4-', 'HPO4-2'), ('CO2', 'HCO3-'), - # ('HAc', 'Ac-'), ('HPr', 'Pr-'), ('HBu', 'Bu-'), ('HVa', 'Va-') - # ''' - # return self.pKa_base-np.log10(pc.T_correction_factor(self.T_base, self.T, self.Ka_dH)) + @property + def pKa(self): + ''' + [numpy.array] pKa array of the following acid-base pairs: + ('H+', 'OH-'), ('NH4+', 'NH3'), ('H2PO4-', 'HPO4-2'), ('CO2', 'HCO3-'), + ('HAc', 'Ac-'), ('HPr', 'Pr-'), ('HBu', 'Bu-'), ('HVa', 'Va-') + ''' + return self.pKa_base-np.log10(pc.T_correction_factor(self.T_base, self.T, self.Ka_dH)) @property def alpha_IN(self): @@ -471,27 +473,6 @@ def alpha_IC(self): def alpha_vfa(self): return 1.0/self.cod_vfa*(-1.0/(1.0 + 10**(self.pKa[4:]-self.pH))) - # def _compile_AE(self): - # _state = self._state - # _dstate = self._dstate - # _cached_state = self._cached_state - # _update_state = self._update_state - # _update_dstate = self._update_dstate - # rxn = self.reactions - - # def yt(t, QC_ins, dQC_ins): - # before_vals = QC_ins[0,:-1] - # _state[:-1] = rxn(before_vals) - # _state[-1] = QC_ins[0,-1] - # if t > self._cached_t: - # _dstate[:] = (_state - _cached_state)/(t-self._cached_t) - # _cached_state[:] = _state - # self._cached_t = t - # _update_state() - # _update_dstate() - - # self._AE = yt - def check_component_properties(self, cmps_asm, cmps_adm): get = getattr setv = setattr @@ -1793,6 +1774,14 @@ class mADM1toASM2d(mADMjunction): :class:`qsdsan.sanunits.ASM2dtomADM1` ''' + # User defined values + bio_to_xs = 0.9 + + # Since we are matching PAOs directly from ASM2d to mADM1, it is important + # for PAOs to have identical N/P content across models + adm_X_PAO_i_N = 0.07 + adm_X_PAO_i_P = 0.02 + def balance_cod_tkn(self, adm_vals, asm_vals): cmps_adm = self.ins[0].components cmps_asm = self.outs[0].components @@ -1816,6 +1805,8 @@ def balance_cod_tkn(self, adm_vals, asm_vals): if cod_bl: return asm_vals else: + print('COD not balanced') + breakpoint() if cod_err > 0: dcod = -(cod_err - cod_tol)/asm_cod else: dcod = -(cod_err + cod_tol)/asm_cod _asm_vals = asm_vals * (1 + (asm_i_COD>0)*dcod) @@ -1836,6 +1827,8 @@ def balance_cod_tkn(self, adm_vals, asm_vals): if tkn_bl: return asm_vals else: + print('TKN not balanced') + breakpoint() if tkn_err > 0: dtkn = -(tkn_err - tkn_tol)/asm_tkn else: dtkn = -(tkn_err + tkn_tol)/asm_tkn _asm_vals = asm_vals * (1 + (asm_i_N>0)*dtkn) @@ -1856,6 +1849,8 @@ def balance_cod_tkn(self, adm_vals, asm_vals): if tp_bl: return asm_vals else: + print('TP not balanced') + breakpoint() if tp_err > 0: dtp = -(tp_err - tp_tol)/asm_tp else: dtp = -(tp_err + tp_tol)/asm_tp _asm_vals = asm_vals * (1 + (asm_i_P>0)*dtp) @@ -1873,6 +1868,8 @@ def balance_cod_tkn(self, adm_vals, asm_vals): f'effluent (ASM) TP is {asm_tp} or {asm_tp*(1+dtp)}. ') return asm_vals else: + print('At least two of COD, TKN, and TP not balanced') + breakpoint() warn('cannot balance COD, TKN and TP at the same time. \n' 'Atleast two of the three COD, TKN, and TP are not balanced \n' f'time with rtol={self.rtol} and atol={self.atol}.\n ' @@ -1886,8 +1883,6 @@ def balance_cod_tkn(self, adm_vals, asm_vals): def _compile_reactions(self): # Retrieve constants - # ins = self.ins[0] - # outs = self.outs[0] # rtol = self.rtol # atol = self.atol @@ -1895,70 +1890,42 @@ def _compile_reactions(self): cmps_asm = self.outs[0].components self.check_component_properties(cmps_asm, cmps_adm) - _asm_ids = cmps_asm.indices(['S_F', 'X_S', 'S_A']) - _adm_ids = cmps_adm.indices(['S_su', 'S_aa', 'S_fa', - 'S_va', 'S_bu', 'S_pro', 'S_ac', - 'X_pr', 'X_li', 'X_ch']) - - # For carbon balance - C_SF, C_XS, C_SA = cmps_asm.i_C[_asm_ids] - C_su, C_aa, C_fa, C_va, C_bu, C_pro, C_ac, C_pr, C_li, C_ch = cmps_adm.i_C[_adm_ids] - - # For nitrogen balance - N_SF, N_XS, N_SA = cmps_asm.i_N[_asm_ids] - N_su, N_aa, N_fa, N_va, N_bu, N_pro, N_ac, N_pr, N_li, N_ch = cmps_adm.i_N[_adm_ids] - - # For phosphorous balance - P_SF, P_XS, P_SA = cmps_asm.i_P[_asm_ids] - P_su, P_aa, P_fa, P_va, P_bu, P_pro, P_ac, P_pr, P_li, P_ch = cmps_adm.i_P[_adm_ids] - - adm = self.adm1_model - adm_p1_idx = cmps_adm.indices(('X_su', 'X_aa', 'X_fa', 'X_c4', - 'X_pro', 'X_ac', 'X_h2', - 'X_PAO', 'X_PP', 'X_PHA')) - decay_idx = [i for i in adm.IDs if i.startswith(('decay', 'lysis'))] - decay_stoichio = np.asarray(adm.stoichiometry.loc[decay_idx]) - - # # cmps_adm = ins.components - # # N balance - # X_pr_i_N = cmps_adm.X_pr.i_N - # S_aa_i_N = cmps_adm.S_aa.i_N + # N balance + X_pr_i_N = cmps_adm.X_pr.i_N + S_aa_i_N = cmps_adm.S_aa.i_N # adm_X_I_i_N = cmps_adm.X_I.i_N # adm_S_I_i_N = cmps_adm.S_I.i_N - # adm_i_N = cmps_adm.i_N - # adm_bio_N_indices = cmps_adm.indices(('X_su', 'X_aa', 'X_fa', - # 'X_c4', 'X_pro', 'X_ac', 'X_h2')) + adm_i_N = cmps_adm.i_N + adm_bio_indices = cmps_adm.indices(('X_su', 'X_aa', 'X_fa', + 'X_c4', 'X_pro', 'X_ac', 'X_h2')) - # # P balance - # X_pr_i_P = cmps_adm.X_pr.i_P + # P balance + X_pr_i_P = cmps_adm.X_pr.i_P # adm_X_I_i_P = cmps_adm.X_I.i_P # adm_S_I_i_P = cmps_adm.S_I.i_P - # S_aa_i_P = cmps_adm.S_aa.i_P - # adm_i_P = cmps_adm.i_P - # adm_bio_P_indices = cmps_adm.indices(('X_su', 'X_aa', 'X_fa', - # 'X_c4', 'X_pro', 'X_ac', 'X_h2')) - - # # cmps_asm = outs.components + S_aa_i_P = cmps_adm.S_aa.i_P + adm_i_P = cmps_adm.i_P - # # N balance - # X_S_i_N = cmps_asm.X_S.i_N - # S_F_i_N = cmps_asm.S_F.i_N + # N balance + X_S_i_N = cmps_asm.X_S.i_N + S_F_i_N = cmps_asm.S_F.i_N # S_A_i_N = cmps_asm.S_A.i_N # asm_X_I_i_N = cmps_asm.X_I.i_N # asm_S_I_i_N = cmps_asm.S_I.i_N - # asm_ions_idx = cmps_asm.indices(('S_NH4', 'S_A', 'S_NO3', 'S_PO4', 'X_PP', 'S_ALK')) + asm_ions_idx = cmps_asm.indices(('S_NH4', 'S_A', 'S_NO3', 'S_PO4', 'S_ALK')) - # # P balance - # X_S_i_P = cmps_asm.X_S.i_P - # S_F_i_P = cmps_asm.S_F.i_P + # P balance + X_S_i_P = cmps_asm.X_S.i_P + S_F_i_P = cmps_asm.S_F.i_P # S_A_i_P = cmps_asm.S_A.i_P # asm_X_I_i_P = cmps_asm.X_I.i_P # asm_S_I_i_P = cmps_asm.S_I.i_P + #!!! All checks are now done in `check_component_property` # Checks for direct mapping of X_PAO, X_PP, X_PHA - # Check for X_PAO (measured as COD so i_COD = 1 in both ASM2d and ADM1) + # # Check for X_PAO (measured as COD so i_COD = 1 in both ASM2d and ADM1) # asm_X_PAO_i_N = cmps_asm.X_PAO.i_N # adm_X_PAO_i_N = cmps_adm.X_PAO.i_N @@ -1986,9 +1953,10 @@ def _compile_reactions(self): # f'in asm2d_X_PAO_i_P = {asm_X_PAO_i_P} is not equal to' # f'adm_X_PAO_i_P = {adm_X_PAO_i_P}') - # Checks not required for X_PP as measured as P in both, with i_COD = i_N = 0 - # Checks not required for X_PHA as measured as COD in both, with i_N = i_P = 0 + # # Checks not required for X_PP as measured as P in both, with i_COD = i_N = 0 + # # Checks not required for X_PHA as measured as COD in both, with i_N = i_P = 0 + #!!! alpha must be called within the function. # alpha_IN = self.alpha_IN # alpha_IC = self.alpha_IC # alpha_IP = self.alpha_IP @@ -1998,111 +1966,91 @@ def _compile_reactions(self): # To convert components from mADM1 to ASM2d (madm1-2-asm2d) def madm12asm2d(adm_vals): - _adm_vals = adm_vals.copy() - - # PROCESS 1: decay of biomas, X_PP, X_PHA - bio_xpp_pha = _adm_vals[adm_p1_idx] - _adm_vals += np.dot(bio_xpp_pha, decay_stoichio) - - # PROCESS 2: strip biogas. Omitted because no S_ch4 or S_h2 in ASM2d components - S_su, S_aa, S_fa, S_va, S_bu, S_pro, S_ac, S_h2, S_ch4, S_IC, S_IN, S_IP, S_I, \ X_ch, X_pr, X_li, X_su, X_aa, X_fa, X_c4, X_pro, X_ac, X_h2, X_I, \ - X_PHA, X_PP, X_PAO, S_K, S_Mg, X_MeOH, X_MeP, S_cat, S_an, H2O = _adm_vals - - if S_h2 > 0 or S_ch4 > 0: warn('Ignored dissolved H2 or CH4.') - - S_ALK = S_IC - S_NH4 = S_IN - S_PO4 = S_IP - - # CONV 1: convert X_pr, X_li, X_ch to X_S - X_S = X_pr + X_li + X_ch - S_ALK += X_pr*C_pr + X_li*C_li + X_ch*C_ch - X_S*C_XS - S_NH4 += X_pr*N_pr + X_li*N_li + X_ch*N_ch - X_S*N_XS - S_PO4 += X_pr*P_pr + X_li*P_li + X_ch*P_ch - X_S*P_XS - - # CONV 2: convert S_su, S_aa, S_fa to S_F - S_F = S_su + S_aa + S_fa - S_ALK += S_su*C_su + S_aa*C_aa + S_fa*C_fa - S_F*C_SF - S_NH4 += S_su*N_su + S_aa*N_aa + S_fa*N_fa - S_F*N_SF - S_PO4 += S_su*P_su + S_aa*P_aa + S_fa*P_fa - S_F*P_SF - - # CONV 3: convert VFAs to S_A - S_A = S_va + S_bu + S_pro + S_ac - S_ALK += S_va*C_va + S_bu*C_bu + S_pro*C_pro + S_ac*C_ac - S_A*C_SA - # S_NH4 += S_va*N_va + S_bu*N_bu + S_pro*N_pro + S_ac*N_ac - S_A*N_SA - # S_PO4 += S_va*P_va + S_bu*P_bu + S_pro*P_pro + S_ac*P_ac - S_A*P_SA - - - # # print(f'adm_vals = {adm_vals}') + X_PHA, X_PP, X_PAO, S_K, S_Mg, X_MeOH, X_MeP, S_cat, S_an, H2O = adm_vals - # # Step 0: snapshot of charged components - # # Not sure about charge on X_PP, S_Mg, S_K (PHA and PAO would have zero charge) - # # Step 0: snapshot of charged components - # # _ions = np.array([S_IN, S_IC, S_ac, S_pro, S_bu, S_va]) - # _ions = np.array([S_IN, S_IC, S_IP, X_PP, S_Mg, S_K, S_ac, S_pro, S_bu, S_va]) + # Step 0: snapshot of charged components + # Not sure about charge on X_PP, S_Mg, S_K (PHA and PAO would have zero charge) + # X_PP is charge-neutral + _ions = np.array([S_IN, S_IC, S_IP, S_Mg, S_K, S_ac, S_pro, S_bu, S_va]) - # # Step 1a: convert biomass and inert particulates into X_S and X_I + # Step 1a: convert biomass and inert particulates into X_S and X_I - # # What is available - # bio_cod = X_su + X_aa + X_fa + X_c4 + X_pro + X_ac + X_h2 - # bio_n = sum((adm_vals*adm_i_N)[adm_bio_N_indices]) - # bio_p = sum((adm_vals*adm_i_P)[adm_bio_P_indices]) + # What is available + bio_cod = X_su + X_aa + X_fa + X_c4 + X_pro + X_ac + X_h2 + bio_n = sum((adm_vals*adm_i_N)[adm_bio_indices]) + bio_p = sum((adm_vals*adm_i_P)[adm_bio_indices]) - # #!!! In default ASM2d stoichiometry, biomass decay (cell lysis) - # #!!! yields 90% particulate substrate + 10% X_I - # #!!! so: convert both biomass and X_I in adm to X_S and X_I in asm + #!!! In default ASM2d stoichiometry, biomass decay (cell lysis) + #!!! yields 90% particulate substrate + 10% X_I + #!!! so: convert both biomass and X_I in adm to X_S and X_I in asm - # # What is available + # ----------------------------Rai version--------------------------------------------- + # What is available # xi_n = X_I*adm_X_I_i_N # xi_p = X_I*adm_X_I_i_P + # ----------------------------Rai version--------------------------------------------- - # # What would be formed by X_S - # xs_cod = bio_cod * self.bio_to_xs - # xs_ndm = xs_cod * X_S_i_N - # xs_pdm = xs_cod * X_S_i_P + # What would be formed by X_S + xs_cod = bio_cod * self.bio_to_xs + xs_ndm = xs_cod * X_S_i_N + xs_pdm = xs_cod * X_S_i_P - # # What would be formed by X_I (ASM2d) + # What would be formed by X_I (ASM2d) # xi_cod = bio_cod * (1 - self.bio_to_xs) + X_I + X_I += bio_cod * (1 - self.bio_to_xs) + + # ----------------------------Rai version--------------------------------------------- # xi_ndm = xi_cod * asm_X_I_i_N # xi_pdm = xi_cod * asm_X_I_i_P + # ----------------------------Rai version--------------------------------------------- - # # MAPPING OF X_S + # MAPPING OF X_S - # # Case I: Both bio_N and bio_P are sufficient - # if xs_ndm <= bio_n and xs_pdm <= bio_p: - # X_S = xs_cod - # xs_cod = 0 - # bio_n -= xs_ndm - # bio_p -= xs_pdm - # else: - # # Case II, III, and, IV: At least one of the two biological N/P is not sufficient - # if bio_p / X_S_i_P > bio_n / X_S_i_N: - # warn('Not enough biomass N to map the specified proportion of ' - # 'biomass COD into X_S. Rest of the biomass COD goes to S_A in last step') - # X_S = bio_n / X_S_i_N - # xs_cod -= X_S - # bio_n = 0 - # bio_p -= X_S*X_S_i_P #mathematically, bio_p can become negative at this point - # if bio_p < 0: - # S_IP += bio_p - # bio_p = 0 - # else: - # warn('Not enough biomass P to map the specified proportion of ' - # 'biomass COD into X_S. Rest of the biomass COD goes to S_A in last step') - # X_S = bio_p / X_S_i_P - # xs_cod -= X_S - # bio_p = 0 - # bio_n -= X_S*X_S_i_N #mathematically, bio_n can become negative at this point - # if bio_n < 0: - # S_IN += bio_n - # bio_n = 0 - + # Case I: Both bio_N and bio_P are sufficient + if xs_ndm <= bio_n and xs_pdm <= bio_p: + X_S = xs_cod + xs_cod = 0 + bio_n -= xs_ndm + bio_p -= xs_pdm + else: + # Case II, III, and, IV: At least one of the two biological N/P is not sufficient + if bio_p / X_S_i_P > bio_n / X_S_i_N: + warn('Not enough biomass N to map the specified proportion of ' + 'biomass COD into X_S. Rest of the biomass COD goes to S_A in last step') + X_S = bio_n / X_S_i_N + xs_cod -= X_S + bio_n = 0 + bio_p -= X_S*X_S_i_P #mathematically, bio_p can become negative at this point + if bio_p < 0: + S_IP += bio_p + bio_p = 0 + else: + warn('Not enough biomass P to map the specified proportion of ' + 'biomass COD into X_S. Rest of the biomass COD goes to S_A in last step') + X_S = bio_p / X_S_i_P + xs_cod -= X_S + bio_p = 0 + bio_n -= X_S*X_S_i_N #mathematically, bio_n can become negative at this point + if bio_n < 0: + S_IN += bio_n + bio_n = 0 + + # Step 2: MAPPING OF X_I + + # Flores Alsina - # # MAPPING OF X_I + #!!! equality is enforced in `check_component_properties` + # if asm_X_I_i_N == adm_X_I_i_N and asm_X_I_i_P == adm_X_I_i_P: + # X_I = xi_cod + # excess_N = (bio_cod * (1 - self.bio_to_xs))*asm_X_I_i_N + # excess_P = (bio_cod * (1 - self.bio_to_xs))*asm_X_I_i_P + # else: + # raise RuntimeError('N and P content in X_I should be same') + # ----------------------------Rai version--------------------------------------------- # if xi_ndm < bio_n + xi_n + S_IN and xi_pdm < bio_p + xi_p + S_IP: # X_I = xi_cod @@ -2158,108 +2106,119 @@ def madm12asm2d(adm_vals): # if bio_n < 0: # S_IN += bio_n # bio_n = 0 + #------------------------------Rai version--------------------------------------------- - # # Step 1b: convert particulate substrates into X_S + # Step 1(b) - # xsub_cod = X_ch + X_pr + X_li - # xsub_n = X_pr*X_pr_i_N - # xsub_p = X_pr*X_pr_i_P - - # xs_ndm = xsub_cod * X_S_i_N - # xs_pdm = xsub_cod * X_S_i_P - - # if xs_ndm <= xsub_n + bio_n and xs_pdm <= xsub_p + bio_p: - # X_S += xsub_cod - # xsub_cod = 0 - # xsub_n -= xs_ndm - # xsub_p -= xs_pdm - # else: - # if (xsub_n + bio_n)/X_S_i_N < (xsub_p + bio_p)/X_S_i_P: - # X_S_temp = (xsub_n + bio_n)/X_S_i_N - # X_S += X_S_temp - # xsub_cod -= X_S_temp - # xsub_n = bio_n = 0 + xsub_cod = X_ch + X_pr + X_li + xsub_n = X_pr*X_pr_i_N + xsub_p = X_pr*X_pr_i_P + + xs_ndm = xsub_cod * X_S_i_N + xs_pdm = xsub_cod * X_S_i_P + + if xs_ndm <= xsub_n + bio_n and xs_pdm <= xsub_p + bio_p: + X_S += xsub_cod + xsub_cod = 0 + xsub_n -= xs_ndm + xsub_p -= xs_pdm + else: + if (xsub_n + bio_n)/X_S_i_N < (xsub_p + bio_p)/X_S_i_P: - # xsub_p -= X_S_temp*X_S_i_P - # if xsub_p < 0: - # bio_p += xsub_p - # xsub_p = 0 + X_S_temp = (xsub_n + bio_n)/X_S_i_N + X_S += X_S_temp + xsub_cod -= X_S_temp + xsub_n = bio_n = 0 - # else: - # X_S_temp = (xsub_p + bio_p)/X_S_i_P - # X_S += X_S_temp - # xsub_cod -= X_S_temp - # xsub_p = bio_p = 0 + xsub_p -= X_S_temp*X_S_i_P + if xsub_p < 0: + bio_p += xsub_p + xsub_p = 0 - # xsub_n -= X_S_temp*X_S_i_N - # if xsub_n < 0: - # bio_n += xsub_n - # xsub_n = 0 + else: + X_S_temp = (xsub_p + bio_p)/X_S_i_P + X_S += X_S_temp + xsub_cod -= X_S_temp + xsub_p = bio_p = 0 + + xsub_n -= X_S_temp*X_S_i_N + if xsub_n < 0: + bio_n += xsub_n + xsub_n = 0 + + # Step 3(A) - # # P balance not required as S_su, S_aa, S_fa do not have P - # ssub_cod = S_su + S_aa + S_fa - # ssub_n = S_aa * S_aa_i_N - # ssub_p = S_aa * S_aa_i_P # which would be 0 + # P balance not required as S_su, S_aa, S_fa do not have P + ssub_cod = S_su + S_aa + S_fa + ssub_n = S_aa * S_aa_i_N + ssub_p = S_aa * S_aa_i_P # which would be 0 - # sf_ndm = ssub_cod * S_F_i_N - # sf_pdm = ssub_cod * S_F_i_P + sf_ndm = ssub_cod * S_F_i_N + sf_pdm = ssub_cod * S_F_i_P - # if sf_ndm <= ssub_n + xsub_n + bio_n and sf_pdm <= ssub_p + xsub_p + bio_p: + if sf_ndm <= ssub_n + xsub_n + bio_n and sf_pdm <= ssub_p + xsub_p + bio_p: - # S_F = ssub_cod - # ssub_cod = 0 + S_F = ssub_cod + ssub_cod = 0 - # ssub_n -= sf_ndm - # if ssub_n < 0: - # xsub_n += ssub_n - # ssub_n = 0 - # if xsub_n < 0: - # bio_n += xsub_n - # xsub_n = 0 + ssub_n -= sf_ndm + if ssub_n < 0: + xsub_n += ssub_n + ssub_n = 0 + if xsub_n < 0: + bio_n += xsub_n + xsub_n = 0 - # ssub_p -= sf_pdm - # if ssub_p < 0: - # xsub_p += ssub_p - # ssub_p = 0 - # if xsub_p < 0: - # bio_p += xsub_p - # xsub_p = 0 + ssub_p -= sf_pdm + if ssub_p < 0: + xsub_p += ssub_p + ssub_p = 0 + if xsub_p < 0: + bio_p += xsub_p + xsub_p = 0 - # else: - # if (ssub_n + xsub_n + bio_n) / S_F_i_N < (ssub_p + xsub_p + bio_p) / S_F_i_P: + else: + if (ssub_n + xsub_n + bio_n) / S_F_i_N < (ssub_p + xsub_p + bio_p) / S_F_i_P: - # S_F = (ssub_n + xsub_n + bio_n) / S_F_i_N - # ssub_cod -= S_F - # ssub_n = xsub_n = bio_n = 0 + S_F = (ssub_n + xsub_n + bio_n) / S_F_i_N + ssub_cod -= S_F + ssub_n = xsub_n = bio_n = 0 - # ssub_p -= S_F*S_F_i_P + ssub_p -= S_F*S_F_i_P - # if ssub_p < 0: - # xsub_p += ssub_p - # ssub_p = 0 - # if xsub_p < 0: - # bio_p += xsub_p - # xsub_p = 0 + if ssub_p < 0: + xsub_p += ssub_p + ssub_p = 0 + if xsub_p < 0: + bio_p += xsub_p + xsub_p = 0 - # else: + else: - # S_F = (ssub_p + xsub_p + bio_p) / S_F_i_P - # ssub_cod -= S_F - # ssub_p = xsub_p = bio_p = 0 + S_F = (ssub_p + xsub_p + bio_p) / S_F_i_P + ssub_cod -= S_F + ssub_p = xsub_p = bio_p = 0 - # ssub_n -= S_F*S_F_i_N + ssub_n -= S_F*S_F_i_N - # if ssub_n < 0: - # xsub_n += ssub_n - # ssub_n = 0 - # if xsub_n < 0: - # bio_n += xsub_n - # xsub_n = 0 + if ssub_n < 0: + xsub_n += ssub_n + ssub_n = 0 + if xsub_n < 0: + bio_n += xsub_n + xsub_n = 0 - # # N and P balance not required as S_su, S_aa, S_fa do not have N and P - # S_A = S_ac + S_pro + S_bu + S_va + # N and P balance not required as S_ac, S_pro, S_bu, and S_va do not have N and P + S_A = S_ac + S_pro + S_bu + S_va + + # Step 4 + #!!! equality if enforced in `check_component_properties` + # if asm_S_I_i_N == adm_S_I_i_N and asm_S_I_i_P == adm_S_I_i_P: + # S_I = S_I + # else: + # raise RuntimeError('N and P content in S_I should be same') - # si_cod = S_I + #------------------------------Rai version--------------------------------------------- # si_n = S_I * adm_S_I_i_N # si_p = S_I * adm_S_I_i_P @@ -2306,10 +2265,21 @@ def madm12asm2d(adm_vals): # if xi_n < 0: # S_IN += xi_n # xi_n = 0 + #------------------------------Rai version--------------------------------------------- + #------------------------------Rai version--------------------------------------------- # S_NH4 = S_IN + si_n + ssub_n + xsub_n + xi_n + bio_n # S_PO4 = S_IP + si_p + ssub_p + xsub_p + xi_p + bio_p + #------------------------------Rai version--------------------------------------------- + + S_NH4 = S_IN + ssub_n + xsub_n + bio_n #- excess_N + S_PO4 = S_IP + ssub_p + xsub_p + bio_p #- excess_P + + #------------------------------Rai version--------------------------------------------- # S_A += si_cod + ssub_cod + xsub_cod + xi_cod + xs_cod + #------------------------------Rai version--------------------------------------------- + + S_A += ssub_cod + xsub_cod + xs_cod # Step 6: check COD and TKN balance asm_vals = np.array(([ @@ -2317,47 +2287,45 @@ def madm12asm2d(adm_vals): S_NH4, 0, # S_NO3 S_PO4, S_F, S_A, S_I, - S_ALK, + 0, # S_ALK(for now) X_I, X_S, 0, # X_H, X_PAO, X_PP, X_PHA, # directly mapped 0, # X_AUT, X_MeOH, X_MeP, H2O])) # directly mapped - - asm_vals = f_corr(adm_vals, asm_vals) - # # Step 5: charge balance for alkalinity + if S_h2 > 0 or S_ch4 > 0: + warn('Ignored dissolved H2 or CH4.') + + asm_vals = f_corr(adm_vals, asm_vals) - # # asm_ions_idx = cmps_asm.indices(('S_NH4', 'S_A', 'S_NO3', 'S_PO4', 'X_PP', 'S_ALK')) + # Step 5: charge balance for alkalinity - # S_NH4 = asm_vals[asm_ions_idx[0]] - # S_A = asm_vals[asm_ions_idx[1]] - # S_NO3 = asm_vals[asm_ions_idx[2]] - # S_PO4 = asm_vals[asm_ions_idx[3]] - # X_PP = asm_vals[asm_ions_idx[4]] + # asm_ions_idx = cmps_asm.indices(('S_NH4', 'S_A', 'S_NO3', 'S_PO4', 'S_ALK')) - # # Need to include S_K, S_Mg in the charge balance + S_NH4, S_A, S_NO3, S_PO4 = asm_vals[asm_ions_idx[:4]] - # # _ions = np.array([S_IN, S_IC, S_ac, S_pro, S_bu, S_va]) - # # S_ALK = (sum(_ions * np.append([alpha_IN, alpha_IC], alpha_vfa)) - S_NH/14)*(-12) + # Need to include S_K, S_Mg in the charge balance - # # _ions = np.array([S_IN, S_IC, S_IP, X_PP, S_Mg, S_K, S_ac, S_pro, S_bu, S_va]) + # _ions = np.array([S_IN, S_IC, S_IP, S_Mg, S_K, S_ac, S_pro, S_bu, S_va]) - # S_ALK = (sum(_ions * np.append([alpha_IN, alpha_IC, alpha_IP, -1/31, 2, 1], alpha_vfa)) - (S_NH4/14 - S_A/64 - S_NO3/14 -1.5*S_PO4/31 - X_PP/31))*(-12) + adm_alphas = np.array([self.alpha_IN, self.alpha_IC, self.alpha_IP, 2, 1, *self.alpha_vfa]) + adm_charge = np.dot(_ions, adm_alphas) + S_ALK = (adm_charge - (S_NH4/14 - S_A/64 - S_NO3/14 - 1.5*S_PO4/31))*(-12) - # asm_vals[asm_ions_idx[5]] = S_ALK + asm_vals[asm_ions_idx[-1]] = S_ALK return asm_vals self._reactions = madm12asm2d - + #%% ASM2dtomADM1 class ASM2dtomADM1(mADMjunction): ''' - Interface unit to convert ASM2d state variables - to ADM1 components, following the A1 scenario in [2]_. + Interface unit to convert activated sludge model (ASM) components + to anaerobic digestion model (ADM) components. Parameters ---------- @@ -2365,11 +2333,16 @@ class ASM2dtomADM1(mADMjunction): Influent stream with ASM components. downstream : stream or str Effluent stream with ADM components. - adm1_model : :class:`qsdsan.processes.ADM1_p_extension` - The anaerobic digestion process model. + adm1_model : obj + The anaerobic digestion process model (:class:`qsdsan.processes.ADM1_p_extension`). xs_to_li : float Split of slowly biodegradable substrate COD to lipid, after all N is mapped into protein. + bio_to_li : float + Split of biomass COD to lipid, after all biomass N is + mapped into protein. + frac_deg : float + Biodegradable fraction of biomass COD. rtol : float Relative tolerance for COD and TKN balance. atol : float @@ -2388,32 +2361,22 @@ class ASM2dtomADM1(mADMjunction): See Also -------- - :class:`qsdsan.sanunits.mADMjunction` + :class:`qsdsan.sanunits.ADMjunction` - :class:`qsdsan.sanunits.mADM1toASM2d` + :class:`qsdsan.sanunits.ADMtoASM` + `math.isclose ` ''' # User defined values xs_to_li = 0.7 - # bio_to_li = 0.4 - # frac_deg = 0.68 - - # Since we are matching PAOs, X_I, S_I, directly from ASM2d to mADM1, it is important - # for PAOs to have identical N/P content across models - - # adm_X_PAO_i_N = 0.07 # should be dependent on component set, not defined here - # adm_X_PAO_i_P = 0.02 - - # asm_X_I_i_N = 0.0600327162 - # asm_X_I_i_P = 0.01 - # asm_S_I_i_N = 0.06 - # asm_S_I_i_P = 0.01 + bio_to_li = 0.4 + frac_deg = 0.68 - # def isbalanced(self, lhs, rhs_vals, rhs_i): - # rhs = sum(rhs_vals*rhs_i) - # error = rhs - lhs - # tol = max(self.rtol*lhs, self.rtol*rhs, self.atol) - # return abs(error) <= tol, error, tol, rhs + def isbalanced(self, lhs, rhs_vals, rhs_i): + rhs = sum(rhs_vals*rhs_i) + error = rhs - lhs + tol = max(self.rtol*lhs, self.rtol*rhs, self.atol) + return abs(error) <= tol, error, tol, rhs def balance_cod_tkn_tp(self, asm_vals, adm_vals): cmps_asm = self.ins[0].components @@ -2495,7 +2458,7 @@ def balance_cod_tkn_tp(self, asm_vals, adm_vals): f'effluent TP is {adm_tp} or {adm_tp*(1+dtp)}. ') return adm_vals else: - warn('cannot balance COD, TKN, and TP at the same time. \n' + warn('cannot balance COD, TKN and TP at the same time. \n' 'Atleast two of the three COD, TKN, and TP are not balanced \n' f'time with rtol={self.rtol} and atol={self.atol}.\n ' f'influent (ASM) COD is {asm_cod}\n ' @@ -2515,24 +2478,19 @@ def _compile_reactions(self): cmps_adm = self.outs[0].components self.check_component_properties(cmps_asm, cmps_adm) - # # For COD balance - # S_NO3_i_COD = cmps_asm.S_NO3.i_COD + # For COD balance + S_NO3_i_COD = cmps_asm.S_NO3.i_COD - # # For N balance - # X_H_i_N = cmps_asm.X_H.i_N - # X_AUT_i_N = cmps_asm.X_AUT.i_N - # S_F_i_N = cmps_asm.S_F.i_N - # X_S_i_N = cmps_asm.X_S.i_N + # For N balance + X_H_i_N = cmps_asm.X_H.i_N + X_AUT_i_N = cmps_asm.X_AUT.i_N + S_F_i_N = cmps_asm.S_F.i_N + X_S_i_N = cmps_asm.X_S.i_N # S_A_i_N = cmps_asm.S_A.i_N - # # For P balance - # X_H_i_P = cmps_asm.X_H.i_P - # X_AUT_i_P = cmps_asm.X_AUT.i_P - # S_F_i_P = cmps_asm.S_F.i_P - # X_S_i_P = cmps_asm.X_S.i_P - # S_A_i_P = cmps_asm.S_A.i_P - - # # Due to issue with mapping of X_I across ASM2d and ADM1, making this user dependent is important + # Due to issue with mapping of X_I across ASM2d and ADM1, making this user dependent is important + + # # PUT A CHECK ON USER DEFINED N AND P CONTENT IN SI and XI here. # if self.asm_X_I_i_N == None: # asm_X_I_i_N = cmps_asm.X_I.i_N # else: @@ -2553,17 +2511,23 @@ def _compile_reactions(self): # else: # asm_S_I_i_P = self.asm_S_I_i_P + # For P balance + X_H_i_P = cmps_asm.X_H.i_P + X_AUT_i_P = cmps_asm.X_AUT.i_P + S_F_i_P = cmps_asm.S_F.i_P + X_S_i_P = cmps_asm.X_S.i_P + # S_A_i_P = cmps_asm.S_A.i_P + # if cmps_asm.S_A.i_N > 0: # warn(f'S_A in ASM has positive nitrogen content: {cmps_asm.S_S.i_N} gN/gCOD. ' # 'These nitrogen will be ignored by the interface model ' # 'and could lead to imbalance of TKN after conversion.') # if cmps_asm.S_A.i_P > 0: - # warn(f'S_A in ASM has positive phosphorous content: {cmps_asm.S_S.i_P} gP/gCOD. ' + # warn(f'S_A in ASM has positive phosphorous content: {cmps_asm.S_S.i_P} gN/gCOD. ' # 'These phosphorous will be ignored by the interface model ' # 'and could lead to imbalance of TP after conversion.') - - + # ------------------------------Rai Version--------------------------------------------- # if cmps_asm.S_I.i_P > 0: # warn(f'S_I in ASM has positive phosphorous content: {cmps_asm.S_I.i_P} gN/gCOD. ' @@ -2574,20 +2538,19 @@ def _compile_reactions(self): # We do not need to check if X_S.i_N != 0 since we take care of it using X_ND_asm1 # We do not need to check if S_F.i_N != 0 since we take care of it using S_ND_asm1 - _asm_ids = cmps_asm.indices(['S_F', 'X_S', 'X_H', 'S_I', 'X_I']) - _adm_ids = cmps_adm.indices(['S_aa', 'S_su', 'X_pr', 'X_li', 'X_ch']) - # For carbon balance - C_SF, C_XS, C_XB, C_SI, C_XI = cmps_asm.i_C[_asm_ids] - C_aa, C_su, C_pr, C_li, C_ch = cmps_adm.i_C[_adm_ids] - # For nitrogen balance - N_SF, N_XS, N_XB, N_SI, N_XI = cmps_asm.i_N[_asm_ids] - N_aa, N_su, N_pr, N_li, N_ch = cmps_adm.i_N[_adm_ids] + # S_ac_i_N = cmps_adm.S_ac.i_N + S_aa_i_N = cmps_adm.S_aa.i_N + X_pr_i_N = cmps_adm.X_pr.i_N + # adm_S_I_i_N = cmps_adm.S_I.i_N + adm_X_I_i_N = cmps_adm.X_I.i_N # For phosphorous balance - P_SF, P_XS, P_XB, P_SI, P_XI = cmps_asm.i_P[_asm_ids] - P_aa, P_su, P_pr, P_li, P_ch = cmps_adm.i_P[_adm_ids] - + # S_ac_i_P = cmps_adm.S_ac.i_P + X_pr_i_P = cmps_adm.X_pr.i_P + # adm_S_I_i_P = cmps_adm.S_I.i_P + adm_X_I_i_P = cmps_adm.X_I.i_P + # Checks for direct mapping of X_PAO, X_PP, X_PHA # Check for X_PAO (measured as COD so i_COD = 1 in both ASM2d and ADM1) @@ -2615,8 +2578,8 @@ def _compile_reactions(self): # f'in asm2d_X_PAO_i_P = {asm_X_PAO_i_P} is not equal to' # f'adm_X_PAO_i_P = {adm_X_PAO_i_P}') - # Checks not required for X_PP as measured as P in both, with i_COD = i_N = 0 - # Checks not required for X_PHA as measured as COD in both, with i_N = i_P = 0 + # # Checks not required for X_PP as measured as P in both, with i_COD = i_N = 0 + # # Checks not required for X_PHA as measured as COD in both, with i_N = i_P = 0 # if asm_X_I_i_N != adm_X_I_i_N: # raise RuntimeError('X_I cannot be directly mapped as N content' @@ -2638,174 +2601,216 @@ def _compile_reactions(self): # f'in asm2d_S_I_i_P = {asm_S_I_i_P} is not equal to \n' # f'adm_S_I_i_P = {adm_S_I_i_P}') - # adm_ions_idx = cmps_adm.indices(['S_IN', 'S_IP', 'S_IC', 'S_cat', 'S_an']) - S_O2_idx, S_NO3_idx = cmps_asm.indices(['S_O2', 'S_NO3']) - # asm_charged_idx = cmps_asm.indices(['S_NO3', 'S_NH4', 'S_ALK', 'S_PO4', 'S_A', 'X_PP']) + adm_ions_idx = cmps_adm.indices(['S_IN', 'S_IP', 'S_IC', 'S_cat', 'S_an']) - # frac_deg = self.frac_deg - # alpha_IP = self.alpha_IP - # alpha_IN = self.alpha_IN - # alpha_IC = self.alpha_IC - # proton_charge = 10**(-self.pKa[0]+self.pH) - 10**(-self.pH) # self.pKa[0] is pKw #!!! should be within asm2d2madm1 + frac_deg = self.frac_deg f_corr = self.balance_cod_tkn_tp - asm = self.asm2d_model - adm = self.adm1_model - p1_stoichio = np.asarray(asm.stoichiometry.loc['hetero_growth_S_A']) - p1_stoichio /= abs(p1_stoichio[S_O2_idx]) - p2_stoichio = np.asarray(asm.stoichiometry.loc['denitri_S_A']) - p2_stoichio /= abs(p2_stoichio[S_NO3_idx]) - p3_stoichio = np.array([adm.parameters[f'f_{k}_xb'] for k in ('sI', 'ch', 'pr', 'li', 'xI')]) - # To convert components from ASM2d to mADM1 (asm2d-2-madm1) def asm2d2madm1(asm_vals): + # S_I, S_S, X_I, X_S, X_BH, X_BA, X_P, S_O, S_NO, S_NH, S_ND, X_ND, S_ALK, S_N2, H2O = asm_vals - _asm_vals = asm_vals.copy() + S_O2, S_N2, S_NH4, S_NO3, S_PO4, S_F, S_A, S_I, S_ALK, X_I, X_S, X_H, \ + X_PAO, X_PP, X_PHA, X_AUT, X_MeOH, X_MeP, H2O = asm_vals # Step 0: charged component snapshot (# pg. 84 of IWA ASM textbook) - # _sno3, _snh4, _salk, _spo4, _sa, _xpp = asm_vals[asm_charged_idx] - - # PROCESS 1: remove S_O2 with S_A with associated X_H growth (aerobic growth of X_H on S_A) - O2_coddm = _asm_vals[S_O2_idx] - _asm_vals += O2_coddm * p1_stoichio # makes S_O2 = 0 + _sno3 = S_NO3 + _snh4 = S_NH4 + _salk = S_ALK + _spo4 = S_PO4 + _sa = S_A + # _xpp = X_PP + + # Step 1: remove any remaining COD demand + O2_coddm = S_O2 + NO3_coddm = -S_NO3*S_NO3_i_COD - # PROCESS 2: remove S_NO3 with S_A with associated X_H growth (denitrification on S_A) - NO3_coddm = _asm_vals[S_NO3_idx] - _asm_vals += NO3_coddm * p2_stoichio # makes S_NO3 = 0 - - S_O2, S_N2, S_NH4, S_NO3, S_PO4, S_F, S_A, S_I, S_ALK, X_I, X_S, X_H, \ - X_PAO, X_PP, X_PHA, X_AUT, X_MeOH, X_MeP, H2O = _asm_vals - - S_IC = S_ALK - S_IN = S_NH4 - S_IP = S_PO4 + # cod_spl = S_S + X_S + X_BH + X_BA + # Replacing S_S with S_F + S_A (IWA ASM textbook) + + cod_spl = (S_A + S_F) + X_S + (X_H + X_AUT) + + # bioN = X_BH*X_BH_i_N + X_BA*X_BA_i_N + + bioN = X_H*X_H_i_N + X_AUT*X_AUT_i_N + bioP = X_H*X_H_i_P + X_AUT*X_AUT_i_P - # CONV 1: transform S_F into S_aa, S_su, S_fa # To be used in Step 2 - S_ND = S_F*N_SF # N in S_F - req_scod = S_ND / N_aa - - if S_F < req_scod: # if S_F cod is not enough to convert all organic soluble N into aa + S_ND_asm1 = S_F*S_F_i_N #S_ND (in asm1) equals the N content in S_F + # To be used in Step 3 + X_ND_asm1 = X_S*X_S_i_N #X_ND (in asm1) equals the N content in X_S + # To be used in Step 5 (a) + X_S_P = X_S*X_S_i_P + # To be used in Step 5 (b) + S_F_P = S_F*S_F_i_P + + if cod_spl <= O2_coddm: + S_O2 = O2_coddm - cod_spl + S_F = S_A = X_S = X_H = X_AUT = 0 + elif cod_spl <= O2_coddm + NO3_coddm: + S_O2 = 0 + S_NO3 = -(O2_coddm + NO3_coddm - cod_spl)/S_NO3_i_COD + S_A = S_F = X_S = X_H = X_AUT = 0 + else: + S_A -= O2_coddm + NO3_coddm + if S_A < 0: + S_F += S_A + S_A = 0 + if S_F < 0: + X_S += S_F + S_F = 0 + if X_S < 0: + X_H += X_S + X_S = 0 + if X_H < 0: + X_AUT += X_H + X_H = 0 + S_O2 = S_NO3 = 0 + + # Step 2: convert any readily biodegradable + # COD and TKN into amino acids and sugars + + # Directly map acetate + S_ac = S_A + # if S_ac_i_N == S_A_i_N and S_ac_i_P == S_A_i_P: + # S_ac = S_A + # else: + # raise RuntimeError('N and P content should be the same in S_A and S_ac for direct translation') + + # First we calculate the amount of amino acid required in ADM1 + # if all available soluble organic N can be mapped to amino acid + req_scod = S_ND_asm1 / S_aa_i_N + + # if available S_F is not enough to fulfill that amino acid requirement + if S_F < req_scod: + # then all available S_F is mapped to amino acids S_aa = S_F + # and no S_S would be available for conversion to sugars S_su = 0 - else: # if S_F cod is more than enough to convert all organic soluble N into aa - S_aa = req_scod # All soluble organic N will be mapped to amino acid + # This needs to be followed by a corresponding loss in soluble organic N + S_ND_asm1 -= S_aa * S_aa_i_N + # if available S_F is more than enough to fulfill that amino acid requirement + else: + # All soluble organic N will be mapped to amino acid + S_aa = req_scod + # The line above implies that a certain portion of S_S would also be consumed to form amino acid + # The S_S which is left would form sugar + # In simpler terms; S_F = S_F - S_aa; S_su = S_F S_su = S_F - S_aa + # All soluble organic N would thus be consumed in amino acid formation + S_ND_asm1 = 0 + + # Step 3: convert slowly biodegradable COD and TKN + # into proteins, lipids, and carbohydrates - S_IN += S_ND - S_aa*N_aa - S_IC += S_F*C_SF - (S_aa*C_aa + S_su*C_su) - S_IP += S_F*P_SF - - # PROCESS 3: biomass decay (X_H, X_AUT lysis) anaerobic - bio = X_H + X_AUT - _si, _ch, _pr, _li, _xi = bio * p3_stoichio - S_IC += bio*C_XB - (_si*C_SI + _ch*C_ch + _pr*C_pr + _li*C_li + _xi*C_XI) - S_IN += bio*N_XB - (_si*N_SI + _ch*N_ch + _pr*N_pr + _li*N_li + _xi*N_XI) - S_IP += bio*P_XB - (_si*P_SI + _ch*P_ch + _pr*P_pr + _li*P_li + _xi*P_XI) - - # CONV 2: transform asm X_S into X_pr, X_li, X_ch - X_ND = X_S*N_XS - req_xcod = X_ND / N_pr + # First we calculate the amount of protein required in ADM1 + # if all available particulate organic N can be mapped to amino acid + req_xcod = X_ND_asm1 / X_pr_i_N # Since X_pr_i_N >> X_pr_i_P there's no need to check req_xcod for N and P separately (CONFIRM LATER 05/16) # if available X_S is not enough to fulfill that protein requirement - if X_S < req_xcod: # if X_S cod is not enough to convert all organic particulate N into pr + if X_S < req_xcod: + # then all available X_S is mapped to amino acids X_pr = X_S + # and no X_S would be available for conversion to lipid or carbohydrates X_li = X_ch = 0 + # This needs to be followed by a corresponding loss in particulate organic N + X_ND_asm1 -= X_pr * X_pr_i_N + + # For P balance + # This needs to be followed by a corresponding loss in particulate organic P + X_S_P -= X_pr * X_pr_i_P + + # if available X_S is more than enough to fulfill that protein requirement else: - X_pr = req_xcod + # All particulate organic N will be mapped to amino acid + X_pr = req_xcod + # The line above implies that a certain portion of X_S would also be consumed to form protein + # The X_S which is left would form lipid and carbohydrates in a percentage define by the user X_li = self.xs_to_li * (X_S - X_pr) X_ch = (X_S - X_pr) - X_li - - S_IN += X_ND - X_pr*N_pr - S_IC += X_S*C_XS - (X_pr*C_pr + X_li*C_li + X_ch*C_ch) - S_IP += X_S*P_XS - (X_pr*P_pr + X_li*P_li + X_ch*P_ch) - - X_pr += _pr - X_li += _li - X_ch += _ch - S_I += _si - X_I += _xi - - # PROCESS 4-5: omitted, PAO related components mapped directly - # CONV 3-5: convert S_A, S_I, X_I; conversion is immediate because identical component composition is enforced - S_ac = S_A - - # # Step 4: convert active biomass into protein, lipids, - # # carbohydrates and potentially particulate TKN + # All particulate organic N would thus be consumed in amino acid formation + X_ND_asm1 = 0 + + # For P balance + # This needs to be followed by a corresponding loss in particulate organic P + X_S_P -= X_pr * X_pr_i_P - # # First the amount of biomass N/P available for protein, lipid etc is determined - # # For this calculation, from total biomass N available the amount - # # of particulate inert N/P expected in ADM1 is subtracted + # Step 4: convert active biomass into protein, lipids, + # carbohydrates and potentially particulate TKN + + # First the amount of biomass N/P available for protein, lipid etc is determined + # For this calculation, from total biomass N available the amount + # of particulate inert N/P expected in ADM1 is subtracted - # available_bioN = bioN - (X_H + X_AUT) * (1-frac_deg) * adm_X_I_i_N - # if available_bioN < 0: - # raise RuntimeError('Not enough N in X_H and X_AUT to fully convert ' - # 'the non-biodegradable portion into X_I in ADM1.') + available_bioN = bioN - (X_H + X_AUT) * (1-frac_deg) * adm_X_I_i_N + if available_bioN < 0: + raise RuntimeError('Not enough N in X_H and X_AUT to fully convert ' + 'the non-biodegradable portion into X_I in ADM1.') - # available_bioP = bioP - (X_H + X_AUT) * (1-frac_deg) * adm_X_I_i_P - # if available_bioP < 0: - # raise RuntimeError('Not enough P in X_H and X_AUT to fully convert ' - # 'the non-biodegradable portion into X_I in ADM1.') + available_bioP = bioP - (X_H + X_AUT) * (1-frac_deg) * adm_X_I_i_P + if available_bioP < 0: + raise RuntimeError('Not enough P in X_H and X_AUT to fully convert ' + 'the non-biodegradable portion into X_I in ADM1.') - # # Then the amount of biomass N/P required for biomass conversion to protein is determined - # req_bioN = (X_H + X_AUT) * frac_deg * X_pr_i_N - # req_bioP = (X_H + X_AUT) * frac_deg * X_pr_i_P - - # # Case I: if both available biomass N/P and particulate organic N/P is greater than - # # required biomass N/P for conversion to protein - # if available_bioN + X_ND_asm1 >= req_bioN and available_bioP + X_S_P >= req_bioP: - # # then all biodegradable biomass N/P (corrsponding to protein demand) is converted to protein - # X_pr += (X_H + X_AUT) * frac_deg - # # the remaining biomass N/P is transfered as organic N/P - # X_ND_asm1 += available_bioN - req_bioN - # X_S_P += available_bioP - req_bioP + # Then the amount of biomass N/P required for biomass conversion to protein is determined + req_bioN = (X_H + X_AUT) * frac_deg * X_pr_i_N + req_bioP = (X_H + X_AUT) * frac_deg * X_pr_i_P + + # Case I: if both available biomass N/P and particulate organic N/P is greater than + # required biomass N/P for conversion to protein + if available_bioN + X_ND_asm1 >= req_bioN and available_bioP + X_S_P >= req_bioP: + # then all biodegradable biomass N/P (corrsponding to protein demand) is converted to protein + X_pr += (X_H + X_AUT) * frac_deg + # the remaining biomass N/P is transfered as organic N/P + X_ND_asm1 += available_bioN - req_bioN + X_S_P += available_bioP - req_bioP - # # Case II: if available biomass N and particulate organic N is less than - # # required biomass N for conversion to protein, but available biomass P and - # # particulate organic P is greater than required biomass P for conversion to protein + # Case II: if available biomass N and particulate organic N is less than + # required biomass N for conversion to protein, but available biomass P and + # particulate organic P is greater than required biomass P for conversion to protein - # # Case III: if available biomass P and particulate organic P is less than - # # required biomass P for conversion to protein, but available biomass N and - # # particulate organic N is greater than required biomass N for conversion to protein + # Case III: if available biomass P and particulate organic P is less than + # required biomass P for conversion to protein, but available biomass N and + # particulate organic N is greater than required biomass N for conversion to protein - # # Case IV: if both available biomass N/P and particulate organic N/P is less than - # # required biomass N/P for conversion to protein - # else: + # Case IV: if both available biomass N/P and particulate organic N/P is less than + # required biomass N/P for conversion to protein + else: - # if (available_bioP + X_S_P)/X_pr_i_P < (available_bioN + X_ND_asm1)/X_pr_i_N: - # # all available P and particulate organic P is converted to protein - # bio2pr = (available_bioP + X_S_P)/X_pr_i_P - # X_pr += bio2pr - # # Biodegradable biomass available after conversion to protein is calculated - # bio_to_split = (X_H + X_AUT) * frac_deg - bio2pr - # # Part of the remaining biomass is mapped to lipid based on user defined value - # bio_split_to_li = bio_to_split * self.bio_to_li - # X_li += bio_split_to_li - # # The other portion of the remanining biomass is mapped to carbohydrates - # X_ch += (bio_to_split - bio_split_to_li) - # # Since all organic P has been mapped to protein, none is left - # X_S_P = 0 + if (available_bioP + X_S_P)/X_pr_i_P < (available_bioN + X_ND_asm1)/X_pr_i_N: + # all available P and particulate organic P is converted to protein + bio2pr = (available_bioP + X_S_P)/X_pr_i_P + X_pr += bio2pr + # Biodegradable biomass available after conversion to protein is calculated + bio_to_split = (X_H + X_AUT) * frac_deg - bio2pr + # Part of the remaining biomass is mapped to lipid based on user defined value + bio_split_to_li = bio_to_split * self.bio_to_li + X_li += bio_split_to_li + # The other portion of the remanining biomass is mapped to carbohydrates + X_ch += (bio_to_split - bio_split_to_li) + # Since all organic P has been mapped to protein, none is left + X_S_P = 0 - # # the remaining biomass N is transfered as organic N - # X_ND_asm1 += available_bioN - (bio2pr*X_pr_i_N) + # the remaining biomass N is transfered as organic N + X_ND_asm1 += available_bioN - (bio2pr*X_pr_i_N) - # else: - # # all available N and particulate organic N is converted to protein - # bio2pr = (available_bioN + X_ND_asm1)/X_pr_i_N - # X_pr += bio2pr - # # Biodegradable biomass available after conversion to protein is calculated - # bio_to_split = (X_H + X_AUT) * frac_deg - bio2pr - # # Part of the remaining biomass is mapped to lipid based on user defined value - # bio_split_to_li = bio_to_split * self.bio_to_li - # X_li += bio_split_to_li - # # The other portion of the remanining biomass is mapped to carbohydrates - # X_ch += (bio_to_split - bio_split_to_li) - # # Since all organic N has been mapped to protein, none is left - # X_ND_asm1 = 0 + else: + # all available N and particulate organic N is converted to protein + bio2pr = (available_bioN + X_ND_asm1)/X_pr_i_N + X_pr += bio2pr + # Biodegradable biomass available after conversion to protein is calculated + bio_to_split = (X_H + X_AUT) * frac_deg - bio2pr + # Part of the remaining biomass is mapped to lipid based on user defined value + bio_split_to_li = bio_to_split * self.bio_to_li + X_li += bio_split_to_li + # The other portion of the remanining biomass is mapped to carbohydrates + X_ch += (bio_to_split - bio_split_to_li) + # Since all organic N has been mapped to protein, none is left + X_ND_asm1 = 0 - # # the remaining biomass P is transfered as organic P - # X_S_P += available_bioP - (bio2pr*X_pr_i_P) + # the remaining biomass P is transfered as organic P + X_S_P += available_bioP - (bio2pr*X_pr_i_P) # Step 5: map particulate inerts @@ -2861,7 +2866,7 @@ def asm2d2madm1(asm_vals): # --------------------------Flores Alsina et al. 2016 version-------------------------------------- # X_I = X_I # COD balance - # X_I += (X_H+X_AUT) * (1-frac_deg) + X_I += (X_H+X_AUT) * (1-frac_deg) # --------------------------Flores Alsina et al. 2016 version-------------------------------------- # # 5(b) @@ -2978,10 +2983,15 @@ def asm2d2madm1(asm_vals): # S_IN = S_ND_asm1 + X_ND_asm1 + S_NH4 + supply_inert_n_asm2d # --------------------------Rai version-------------------------------------- + # --------------------------Flores Alsina et al. 2016 version-------------------------------------- + S_IN = S_ND_asm1 + X_ND_asm1 + S_NH4 + # --------------------------Flores Alsina et al. 2016 version-------------------------------------- + + S_IP = S_F_P + X_S_P + S_PO4 # Step 8: check COD and TKN balance # has TKN: S_aa, S_IN, S_I, X_pr, X_I - # S_IC = S_cat = S_an = 0 + S_IC = S_cat = S_an = 0 # When mapping components directly in Step 9 ensure the values of # cmps.i_N, cmps.i_P, and cmps.i_COD are same in both ASM2d and ADM1 @@ -3005,39 +3015,37 @@ def asm2d2madm1(asm_vals): X_I, X_PHA, X_PP, X_PAO, 0, 0, # S_K, S_Mg, X_MeOH, X_MeP, - 0, 0, H2O]) # S_cat, S_an + S_cat, S_an, H2O]) - # adm_vals = f_corr(asm_vals, adm_vals) - adm_vals = f_corr(_asm_vals, adm_vals) - - # # Step 7: charge balance - # asm_charge_tot = - _sa/64 + _snh4/14 - _sno3/14 - 1.5*_spo4/31 - _salk - _xpp/31 #Based on page 84 of IWA ASM handbook + adm_vals = f_corr(asm_vals, adm_vals) - # #!!! charge balance should technically include VFAs, S_K, S_Mg, - # # but since their concentrations are assumed zero it is acceptable. + # Step 7: charge balance + asm_charge = - _sa/64 + _snh4/14 - _sno3/14 - 1.5*_spo4/31 - _salk# - _xpp/31 #Based on page 84 of IWA ASM handbook - # S_IN = adm_vals[adm_ions_idx[0]] - # S_IP = adm_vals[adm_ions_idx[1]] + #!!! charge balance should technically include VFAs, S_K, S_Mg, + # but since their concentrations are assumed zero it is acceptable. - # S_IC = (asm_charge_tot -S_IN*alpha_IN -S_IP*alpha_IP)/alpha_IC + S_IN, S_IP = adm_vals[adm_ions_idx[:2]] - # # proton_charge = (OH)^-1 - (H)^+1 - # # net_Scat = Scat - San - # net_Scat = asm_charge_tot + proton_charge + S_IC = (asm_charge - S_IN*self.alpha_IN - S_IP*self.alpha_IP)/self.alpha_IC + proton_charge = 10**(-self.pKa[0]+self.pH) - 10**(-self.pH) # self.pKa[0] is pKw + # proton_charge = (OH)^-1 - (H)^+1 + # net_Scat = Scat - San + net_Scat = asm_charge + proton_charge - # if net_Scat > 0: - # S_cat = net_Scat - # S_an = 0 - # else: - # S_cat = 0 - # S_an = -net_Scat + if net_Scat > 0: + S_cat = net_Scat + S_an = 0 + else: + S_cat = 0 + S_an = -net_Scat - # adm_vals[adm_ions_idx[2:]] = [S_IC, S_cat, S_an] + adm_vals[adm_ions_idx[2:]] = [S_IC, S_cat, S_an] return adm_vals self._reactions = asm2d2madm1 - + #%% ADM1ptoASM2d_A1 class ADM1ptoASM2d_A1(mADMjunction): ''' @@ -3193,7 +3201,7 @@ def _compile_reactions(self): f_corr = self.balance_cod_tkn # To convert components from ADM1p to ASM2d (A1) - def madm12asm2d(adm_vals): + def adm1p2asm2d(adm_vals): _adm_vals = adm_vals.copy() @@ -3246,7 +3254,7 @@ def madm12asm2d(adm_vals): asm_vals = f_corr(adm_vals, asm_vals) return asm_vals - self._reactions = madm12asm2d + self._reactions = adm1p2asm2d #%% ASM2dtoADM1p_A1 @@ -3410,7 +3418,7 @@ def _compile_reactions(self): p3_stoichio = np.array([adm.parameters[f'f_{k}_xb'] for k in ('sI', 'ch', 'pr', 'li', 'xI')]) # To convert components from ASM2d to mADM1 (asm2d-2-madm1) - def asm2d2madm1(asm_vals): + def asm2d2adm1p(asm_vals): _asm_vals = asm_vals.copy() # PROCESS 1: remove S_O2 with S_A with associated X_H growth (aerobic growth of X_H on S_A) @@ -3494,4 +3502,4 @@ def asm2d2madm1(asm_vals): adm_vals = f_corr(_asm_vals, adm_vals) return adm_vals - self._reactions = asm2d2madm1 + self._reactions = asm2d2adm1p From dfa1caedb37cf13ecb328b46cadcf26930dfed47 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Mon, 20 May 2024 08:49:07 -0700 Subject: [PATCH 340/424] Update _junction.py --- qsdsan/sanunits/_junction.py | 39 +++++++++++++++++++++++++++++------- 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/qsdsan/sanunits/_junction.py b/qsdsan/sanunits/_junction.py index 68271746..11192cf4 100644 --- a/qsdsan/sanunits/_junction.py +++ b/qsdsan/sanunits/_junction.py @@ -1779,8 +1779,13 @@ class mADM1toASM2d(mADMjunction): # Since we are matching PAOs directly from ASM2d to mADM1, it is important # for PAOs to have identical N/P content across models - adm_X_PAO_i_N = 0.07 - adm_X_PAO_i_P = 0.02 + # adm_X_PAO_i_N = 0.07 + # adm_X_PAO_i_P = 0.02 + + @property + def pH(self): + '''[float] pH of the upstream/downstream.''' + return self.ins[0].pH def balance_cod_tkn(self, adm_vals, asm_vals): cmps_adm = self.ins[0].components @@ -2372,11 +2377,31 @@ class ASM2dtomADM1(mADMjunction): bio_to_li = 0.4 frac_deg = 0.68 - def isbalanced(self, lhs, rhs_vals, rhs_i): - rhs = sum(rhs_vals*rhs_i) - error = rhs - lhs - tol = max(self.rtol*lhs, self.rtol*rhs, self.atol) - return abs(error) <= tol, error, tol, rhs + @property + def T(self): + '''[float] Temperature of the downstream [K].''' + try: return self.outs[0].sink.T + except: return self._T + @T.setter + def T(self, T): + self._T = self.outs[0].T = T + + @property + def pH(self): + '''[float] downstream pH.''' + if self._pH: return self._pH + else: + try: return self.outs[0].sink.outs[1].pH + except: return 7. + @pH.setter + def pH(self, ph): + self._pH = self.outs[0].pH = ph + + # def isbalanced(self, lhs, rhs_vals, rhs_i): + # rhs = sum(rhs_vals*rhs_i) + # error = rhs - lhs + # tol = max(self.rtol*lhs, self.rtol*rhs, self.atol) + # return abs(error) <= tol, error, tol, rhs def balance_cod_tkn_tp(self, asm_vals, adm_vals): cmps_asm = self.ins[0].components From c2ca9a9a63eaccb13be1dca377764c1f17544f30 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Mon, 20 May 2024 08:50:51 -0700 Subject: [PATCH 341/424] Update _junction.py --- qsdsan/sanunits/_junction.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/qsdsan/sanunits/_junction.py b/qsdsan/sanunits/_junction.py index 11192cf4..defc0119 100644 --- a/qsdsan/sanunits/_junction.py +++ b/qsdsan/sanunits/_junction.py @@ -2377,6 +2377,16 @@ class ASM2dtomADM1(mADMjunction): bio_to_li = 0.4 frac_deg = 0.68 + def __init__(self, ID='', upstream=None, downstream=(), thermo=None, + init_with='WasteStream', F_BM_default=None, isdynamic=False, + adm1_model=None, asm2d_model=None, T=298.15, pH=7): + self._T = T + self._pH = pH + super().__init__(ID=ID, upstream=upstream, downstream=downstream, + thermo=thermo, init_with=init_with, + F_BM_default=F_BM_default, isdynamic=isdynamic, + adm1_model=adm1_model, asm2d_model=asm2d_model) + @property def T(self): '''[float] Temperature of the downstream [K].''' From 22224ecf6add235af96b1cb9b52ea5ad7ec8a954 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Mon, 20 May 2024 08:53:02 -0700 Subject: [PATCH 342/424] Update _junction.py --- qsdsan/sanunits/_junction.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/qsdsan/sanunits/_junction.py b/qsdsan/sanunits/_junction.py index defc0119..06957682 100644 --- a/qsdsan/sanunits/_junction.py +++ b/qsdsan/sanunits/_junction.py @@ -1782,6 +1782,14 @@ class mADM1toASM2d(mADMjunction): # adm_X_PAO_i_N = 0.07 # adm_X_PAO_i_P = 0.02 + @property + def T(self): + '''[float] Temperature of the upstream/downstream [K].''' + return self.ins[0].T + @T.setter + def T(self, T): + self.ins[0].T = self.outs[0].T = T + @property def pH(self): '''[float] pH of the upstream/downstream.''' From e8c35a47d07908f75d6fc3988bfec70f4b4ac9e9 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Tue, 21 May 2024 10:46:10 -0700 Subject: [PATCH 343/424] fix tkn balance --- qsdsan/sanunits/_junction.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/qsdsan/sanunits/_junction.py b/qsdsan/sanunits/_junction.py index 06957682..29110f42 100644 --- a/qsdsan/sanunits/_junction.py +++ b/qsdsan/sanunits/_junction.py @@ -1924,7 +1924,7 @@ def _compile_reactions(self): S_F_i_N = cmps_asm.S_F.i_N # S_A_i_N = cmps_asm.S_A.i_N - # asm_X_I_i_N = cmps_asm.X_I.i_N + asm_X_I_i_N = cmps_asm.X_I.i_N # asm_S_I_i_N = cmps_asm.S_I.i_N asm_ions_idx = cmps_asm.indices(('S_NH4', 'S_A', 'S_NO3', 'S_PO4', 'S_ALK')) @@ -1932,7 +1932,7 @@ def _compile_reactions(self): X_S_i_P = cmps_asm.X_S.i_P S_F_i_P = cmps_asm.S_F.i_P # S_A_i_P = cmps_asm.S_A.i_P - # asm_X_I_i_P = cmps_asm.X_I.i_P + asm_X_I_i_P = cmps_asm.X_I.i_P # asm_S_I_i_P = cmps_asm.S_I.i_P #!!! All checks are now done in `check_component_property` @@ -2058,8 +2058,8 @@ def madm12asm2d(adm_vals): #!!! equality is enforced in `check_component_properties` # if asm_X_I_i_N == adm_X_I_i_N and asm_X_I_i_P == adm_X_I_i_P: # X_I = xi_cod - # excess_N = (bio_cod * (1 - self.bio_to_xs))*asm_X_I_i_N - # excess_P = (bio_cod * (1 - self.bio_to_xs))*asm_X_I_i_P + excess_N = (bio_cod * (1 - self.bio_to_xs))*asm_X_I_i_N + excess_P = (bio_cod * (1 - self.bio_to_xs))*asm_X_I_i_P # else: # raise RuntimeError('N and P content in X_I should be same') @@ -2285,8 +2285,8 @@ def madm12asm2d(adm_vals): # S_PO4 = S_IP + si_p + ssub_p + xsub_p + xi_p + bio_p #------------------------------Rai version--------------------------------------------- - S_NH4 = S_IN + ssub_n + xsub_n + bio_n #- excess_N - S_PO4 = S_IP + ssub_p + xsub_p + bio_p #- excess_P + S_NH4 = S_IN + ssub_n + xsub_n + bio_n - excess_N + S_PO4 = S_IP + ssub_p + xsub_p + bio_p - excess_P #------------------------------Rai version--------------------------------------------- # S_A += si_cod + ssub_cod + xsub_cod + xi_cod + xs_cod From 26a323bd5215f5a50ab3923f7daac5ee68e5b992 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Tue, 21 May 2024 11:50:34 -0700 Subject: [PATCH 344/424] fix bug in `IdealClarifier` and update doctest --- qsdsan/sanunits/_clarifier.py | 56 +++++++++++++++++------------------ 1 file changed, 27 insertions(+), 29 deletions(-) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index a92dbdac..85faa0f5 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -913,7 +913,7 @@ def _update_state(self): of.state[:] = arr else: self._f_uf = fuf = e_rmv*Qi/Qs - self._f_of = fof = (1-e_rmv)/(1-Qi/Qs) + self._f_of = fof = (1-e_rmv)/(1-Qs/Qi) uf.state[:-1] = Cs * ((1-x) + x*fuf) uf.state[-1] = Qs of.state[:-1] = Cs * ((1-x) + x*fof) @@ -1248,23 +1248,22 @@ class PrimaryClarifier(IdealClarifier): Examples -------- - >>> from qsdsan import set_thermo, Components, WasteStream + >>> from qsdsan import set_thermo, Components, WasteStream, System >>> cmps = Components.load_default() >>> cmps_test = cmps.subgroup(['S_F', 'S_NH4', 'X_OHO', 'H2O']) >>> set_thermo(cmps_test) >>> ws = WasteStream('ws', S_F = 10, S_NH4 = 20, X_OHO = 15, H2O=1000) - >>> from qsdsan.sanunits import PrimaryClarifier - >>> PC = PrimaryClarifier(ID='IC', ins=ws, outs=('effluent', 'sludge'), + >>> from qsdsan.sanunits import PrimaryClarifier, CSTR + >>> M1 = CSTR('mixer', ins=ws, outs='out', aeration=None) + >>> PC = PrimaryClarifier(ID='IC', ins=M1-0, outs=('effluent', 'sludge'), ... solids_removal_efficiency=0.6, ... sludge_flow_rate=ws.F_vol*24*0.3) - >>> PC.simulate() - >>> effluent, sludge = PC.outs - >>> sludge.imass['X_OHO']/ws.imass['X_OHO'] - 0.6 + >>> sys = System('sys', path=(M1, PC)) + >>> sys.simulate(t_span=(0,10), method='BDF') # doctest: +ELLIPSIS >>> PC.show() # doctest: +ELLIPSIS PrimaryClarifier: IC ins... - [0] ws + [0] out from CSTR-mixer phase: 'l', T: 298.15 K, P: 101325 Pa flow (g/hr): S_F 1e+04 S_NH4 2e+04 @@ -1287,35 +1286,34 @@ class PrimaryClarifier(IdealClarifier): flow (g/hr): S_F 7e+03 S_NH4 1.4e+04 X_OHO 6e+03 - H2O 7e+05 + H2O 7.03e+05 WasteStream-specific properties: pH : 7.0 - COD : 17804.5 mg/L - BOD : 11530.2 mg/L - TC : 6075.4 mg/L - TOC : 6075.4 mg/L - TN : 20022.9 mg/L - TP : 252.0 mg/L - TK : 39.2 mg/L - TSS : 6382.0 mg/L + COD : 17734.2 mg/L + BOD : 11484.7 mg/L + TC : 6051.5 mg/L + TOC : 6051.5 mg/L + TN : 19943.9 mg/L + TP : 251.0 mg/L + TK : 39.0 mg/L + TSS : 6356.8 mg/L [1] sludge phase: 'l', T: 298.15 K, P: 101325 Pa flow (g/hr): S_F 3e+03 S_NH4 6e+03 X_OHO 9e+03 - H2O 3e+05 + H2O 2.97e+05 WasteStream-specific properties: pH : 7.0 - COD : 37848.4 mg/L - BOD : 22869.1 mg/L - TC : 13417.3 mg/L - TOC : 13417.3 mg/L - TN : 21146.9 mg/L - TP : 634.0 mg/L - TK : 135.3 mg/L - TSS : 22045.9 mg/L - - + COD : 38196.8 mg/L + BOD : 23079.7 mg/L + TC : 13540.8 mg/L + TOC : 13540.8 mg/L + TN : 21341.5 mg/L + TP : 639.8 mg/L + TK : 136.6 mg/L + TSS : 22248.9 mg/L + References ---------- [1] Chapter-10: Primary Treatment. Design of water resource recovery facilities. From 70fc6e5c74c165eb604fdf397cf9306fc219b2b3 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Tue, 21 May 2024 13:02:42 -0700 Subject: [PATCH 345/424] Delete _junction_copy.py --- qsdsan/sanunits/_junction_copy.py | 1421 ----------------------------- 1 file changed, 1421 deletions(-) delete mode 100644 qsdsan/sanunits/_junction_copy.py diff --git a/qsdsan/sanunits/_junction_copy.py b/qsdsan/sanunits/_junction_copy.py deleted file mode 100644 index b8d7077a..00000000 --- a/qsdsan/sanunits/_junction_copy.py +++ /dev/null @@ -1,1421 +0,0 @@ -# -*- coding: utf-8 -*- -''' -QSDsan: Quantitative Sustainable Design for sanitation and resource recovery systems - -This module is developed by: - - Joy Zhang - - Yalin Li - - Saumitra Rai - -This module is under the University of Illinois/NCSA Open Source License. -Please refer to https://github.com/QSD-Group/QSDsan/blob/main/LICENSE.txt -for license details. -''' - -import numpy as np -from warnings import warn -from math import isclose -from biosteam.units import Junction as BSTjunction -from .. import SanUnit, processes as pc - -__all__ = ( - 'Junction', - 'ADMjunction', 'ADMtoASM', 'ASMtoADM', - ) - -#%% -class Junction(SanUnit): - ''' - A non-reactive class that serves to convert a stream with one set of components - and into another. - - Thermal conditions of the downstream (T, P) will be copied from those of the upstream. - - Parameters - ---------- - upstream : stream or str - Influent stream. - downstream : stream or str - Effluent stream. - reactions : iterable(dict) | callable - Iterable of dict that has the conversion of upstream components to - downstream components, - or a function that will return the concentration of the effluent - with influent concentration as the input. - - If given as an iterable of dict, keys of each of the dict should be - the ID or alias of components, - values should be the conversion/yield, - which should be negative for reactants and positive for products. - ''' - _graphics = BSTjunction._graphics - - def __init__(self, ID='', upstream=None, downstream=(), thermo=None, - init_with='WasteStream', F_BM_default=None, isdynamic=False, - reactions=None, **kwargs): - thermo = downstream.thermo if downstream else thermo - SanUnit.__init__(self, ID, ins=upstream, outs=downstream, thermo=thermo, - init_with=init_with, - F_BM_default=F_BM_default, isdynamic=isdynamic, - skip_property_package_check=True) - if reactions: self.reactions = reactions - else: self.reactions = None - - for key, val in kwargs.items(): - setattr(self, key, val) - - def _no_parse_reactions(self, rxns): - if rxns is None: return - raise RuntimeError('Reactions are automatically compiled.') - - - def _parse_reactions(self, rxns): - cmps_in = self.ins[0].components - cmps_outs = self.outs[0].components - - num_rxns = len(rxns) - num_upcmps = len(cmps_in) - num_downcmps = len(cmps_outs) - RX = np.zeros(shape=(num_rxns, num_upcmps)) # each row is a reaction - RY = np.zeros(shape=(num_rxns, num_downcmps)) - - for n, dct in enumerate(rxns): - RXn, RYn = RX[n], RY[n] - for cmp, val in dct.items(): - if val < 0: RXn[cmps_in.index(cmp)] = val - elif val > 0: RYn[cmps_outs.index(cmp)] = val - - # Transfer overlapped components - overlapped = set(cmps_in.IDs).intersection(set(cmps_outs.IDs)) - RXsum = RX.sum(axis=0) - RXnew = [] - RYnew = [] - for ID in overlapped: - idx_up = cmps_in.index(ID) - idx_down= cmps_outs.index(ID) - if RXsum[idx_up] != -1: - newRXn = np.zeros(num_upcmps) - newRYn = np.zeros(num_downcmps) - newRXn[idx_up] = -1 - RXsum[idx_up] - newRYn[idx_down] = -newRXn[idx_up] - RXnew.append(newRXn) - RYnew.append(newRYn) - RX = np.concatenate((RX, np.array(RXnew))) - RY = np.concatenate((RY, np.array(RYnew))) - - # Check if all upstream components are converted - RXsum = RX.sum(axis=0) - if np.where(RXsum!=-1)[0].any(): - index = np.where(RXsum!=-1)[0][0] - raise ValueError(f'Conversion for Component "{cmps_in.IDs[index]}" ' - f'is {abs(RXsum[index])}, not 100%.') - self._RX = RX - self._RY = RY - - - def _compile_reactions(self): - def reactions(X): - X.reshape(1, len(X)) - Yarr = -(self._RX*X).T @ self._RY # _RX: (num_rxns, num_upcmps); _RY: (num_rxns, num_downcmps) - Y = Yarr.sum(axis=0) # Yarr: (num_upcmps, num_downcmps) - return Y.reshape(Y.shape[1],) - self._reactions = reactions - - - def _run(self): - ins = self.ins[0] - rxns = self.reactions - X = ins.conc.value - Y = rxns(X) - outs = self.outs[0] - outs.thermal_condition.copy_like(ins.thermal_condition) - concs = dict(zip(outs.components.IDs, Y)) - concs.pop('H2O', None) - outs.set_flow_by_concentration( - flow_tot=ins.F_vol, - concentrations=concs, - units=('m3/hr', 'mg/L')) - - - # Below are dynamic simulation-related properties - @property - def state(self): - '''The state of the Junction, including component concentrations [mg/L] and flow rate [m^3/d].''' - if self._state is None: return None - else: - return dict(zip(list(self.components.IDs) + ['Q'], self._state)) - - def _init_dynamic(self): - super()._init_dynamic() - # Need to use ins' components, otherwise _ins_QC will follow the shape of - # the unit's (i.e., downstream) components - self._ins_QC = np.zeros((len(self._ins), len(self.ins[0].components)+1)) - self._ins_dQC = self._ins_QC.copy() - - def _init_state(self): - ''' - Initialize state by specifying or calculating component concentrations - based on influents. Total flow rate is always initialized as the sum of - influent wastestream flows. - ''' - self._state = np.append(self.outs[0].conc, self.outs[0].F_vol*24) - self._dstate = self._state * 0. - self._cached_state = self._state.copy() - self._cached_t = 0 - - def _update_state(self): - ''' - Updates conditions of output stream based on conditions of the Junction. - ''' - self._outs[0].state = self._state - - def _update_dstate(self): - ''' - Updates rates of change of output stream from rates of change of the Junction. - ''' - self._outs[0].dstate = self._dstate - - # The unit's state should be the same as the effluent state - # react the state arr and dstate arr - def _compile_AE(self): - _state = self._state - _dstate = self._dstate - _update_state = self._update_state - _update_dstate = self._update_dstate - rxns = self.reactions - def yt(t, QC_ins, dQC_ins): - for i, j in zip((QC_ins, dQC_ins), (_state, _dstate)): - X = i[0][:-1] # shape = (1, num_upcmps) - Y = rxns(X) - j[:-1] = Y - j[-1] = i[0][-1] # volumetric flow of outs should equal that of ins - _update_state() - _update_dstate() - self._AE = yt - - - @property - def upstream(self): - '''[qsdsan.WasteStream] Influent.''' - return self.ins[0] - @upstream.setter - def upstream(self, upstream): - self.ins[0] = upstream - - @property - def downstream(self): - '''[qsdsan.WasteStream] Effluent.''' - return self.outs[0] - @downstream.setter - def downstream(self, downstream): - self.outs[0] = downstream - - @property - def AE(self): - if self._AE is None: - self._compile_AE() - return self._AE - - @property - def reactions(self): - ''' - [callable] Function that takes the concentration array of the influent - and convert to the concentration array of the effluent. - ''' - if not self._reactions: self._compile_reactions() - return self._reactions - @reactions.setter - def reactions(self, i): - if callable(i): self._reactions = i - else: - self._parse_reactions(i) - self._compile_reactions() - - -# %% - -#TODO: add a `rtol` kwargs for error checking -class ADMjunction(Junction): - ''' - An abstract superclass holding common properties of ADM interface classes. - Users should use its subclasses (e.g., ``ASMtoADM``, ``ADMtoASM``) instead. - - See Also - -------- - :class:`qsdsan.sanunits.Junction` - - :class:`qsdsan.sanunits.ADMtoASM` - - :class:`qsdsan.sanunits.ASMtoADM` - ''' - _parse_reactions = Junction._no_parse_reactions - rtol = 1e-2 - atol = 1e-6 - - def __init__(self, ID='', upstream=None, downstream=(), thermo=None, - init_with='WasteStream', F_BM_default=None, isdynamic=False, - adm1_model=None): - self.adm1_model = adm1_model # otherwise there won't be adm1_model when `_compile_reactions` is called - if thermo is None: - warn('No `thermo` object is provided and is prone to raise error. ' - 'If you are not sure how to get the `thermo` object, ' - 'use `thermo = qsdsan.set_thermo` after setting thermo with the `Components` object.') - super().__init__(ID=ID, upstream=upstream, downstream=downstream, - thermo=thermo, init_with=init_with, - F_BM_default=F_BM_default, isdynamic=isdynamic) - - - @property - def T(self): - '''[float] Temperature of the upstream/downstream [K].''' - return self.ins[0].T - @T.setter - def T(self, T): - self.ins[0].T = self.outs[0].T = T - - @property - def pH(self): - '''[float] pH of the upstream/downstream.''' - return self.ins[0].pH - - @property - def adm1_model(self): - '''[qsdsan.Process] ADM process model.''' - return self._adm1_model - @adm1_model.setter - def adm1_model(self, model): - if not isinstance(model, pc.ADM1): - raise ValueError('`adm1_model` must be an `AMD1` object, ' - f'the given object is {type(model).__name__}.') - self._adm1_model = model - - @property - def T_base(self): - '''[float] Base temperature in the ADM1 model.''' - return self.adm1_model.rate_function.params['T_base'] - - @property - def pKa_base(self): - '''[float] pKa of the acid-base pairs at the base temperature in the ADM1 model.''' - Ka_base = self.adm1_model.rate_function.params['Ka_base'] - return -np.log10(Ka_base) - - @property - def Ka_dH(self): - '''[float] Heat of reaction for Ka.''' - return self.adm1_model.rate_function.params['Ka_dH'] - - @property - def pKa(self): - ''' - [numpy.array] pKa array of the following acid-base pairs: - ('H+', 'OH-'), ('NH4+', 'NH3'), ('H3PO4', 'H2PO4-'), ('CO2', 'HCO3-'), - ('HAc', 'Ac-'), ('HPr', 'Pr-'), ('HBu', 'Bu-'), ('HVa', 'Va-') - ''' - return self.pKa_base-np.log10(np.exp(pc.T_correction_factor(self.T_base, self.T, self.Ka_dH))) - - @property - def alpha_IC(self): - '''[float] Charge per g of C.''' - pH = self.pH - pKa_IC = self.pKa[2] - return -1/(1+10**(pKa_IC-pH))/12 - - @property - def alpha_IN(self): - '''[float] Charge per g of N.''' - pH = self.pH - pKa_IN = self.pKa[1] - return 10**(pKa_IN-pH)/(1+10**(pKa_IN-pH))/14 - - - def _compile_AE(self): - _state = self._state - _dstate = self._dstate - _cached_state = self._cached_state - _update_state = self._update_state - _update_dstate = self._update_dstate - rxn = self.reactions - - def yt(t, QC_ins, dQC_ins): - before_vals = QC_ins[0,:-1] - _state[:-1] = rxn(before_vals) - _state[-1] = QC_ins[0,-1] - if t > self._cached_t: - _dstate[:] = (_state - _cached_state)/(t-self._cached_t) - _cached_state[:] = _state - self._cached_t = t - _update_state() - _update_dstate() - - self._AE = yt - -# %% - -class ADMtoASM(ADMjunction): - ''' - Interface unit to convert anaerobic digestion model (ADM) components - to activated sludge model (ASM) components. - - Parameters - ---------- - upstream : stream or str - Influent stream with ADM components. - downstream : stream or str - Effluent stream with ASM components. - adm1_model : obj - The anaerobic digestion process model (:class:`qsdsan.processes.ADM1_p_extension`). - rtol : float - Relative tolerance for COD and TKN balance. - atol : float - Absolute tolerance for COD and TKN balance. - - References - ---------- - [1] Nopens, I.; Batstone, D. J.; Copp, J. B.; Jeppsson, U.; Volcke, E.; - Alex, J.; Vanrolleghem, P. A. An ASM/ADM Model Interface for Dynamic - Plant-Wide Simulation. Water Res. 2009, 43, 1913–1923. - - [2] Flores-Alsina, X., Solon, K., Kazadi Mbamba, C., Tait, S., Gernaey, K. V., - Jeppsson, U., & Batstone, D. J. (2016). Modelling phosphorus (P), sulfur (S) - and iron (FE) interactions for dynamic simulations of anaerobic digestion processes. - Water Research, 95, 370–382. - See Also - -------- - :class:`qsdsan.sanunits.ADMjunction` - - :class:`qsdsan.sanunits.ASMtoADM` - - `math.isclose ` - ''' - # User defined values - # bio_to_xs = 0.7 (Not using this split since no X_P exists in ASM2d) - - # Should be constants - cod_vfa = np.array([64, 112, 160, 208]) - - def isbalanced(self, lhs, rhs_vals, rhs_i): - rhs = sum(rhs_vals*rhs_i) - error = rhs - lhs - tol = max(self.rtol*lhs, self.rtol*rhs, self.atol) - return abs(error) <= tol, error, tol, rhs - - def balance_cod_tkn(self, adm_vals, asm_vals): - cmps_adm = self.ins[0].components - cmps_asm = self.outs[0].components - asm_i_COD = cmps_asm.i_COD - adm_i_COD = cmps_adm.i_COD - gas_idx = cmps_adm.indices(('S_h2', 'S_ch4')) - asm_i_N = cmps_asm.i_N - adm_i_N = cmps_adm.i_N - asm_i_P = cmps_asm.i_P - adm_i_P = cmps_adm.i_P - adm_cod = sum(adm_vals*adm_i_COD) - sum(adm_vals[gas_idx]) - adm_tkn = sum(adm_vals*adm_i_N) - adm_tp = sum(adm_vals*adm_i_P) - - cod_bl, cod_err, cod_tol, asm_cod = self.isbalanced(adm_cod, asm_vals, asm_i_COD) - tkn_bl, tkn_err, tkn_tol, asm_tkn = self.isbalanced(adm_tkn, asm_vals, asm_i_N) - tp_bl, tp_err, tp_tol, asm_tp = self.isbalanced(adm_tp, asm_vals, asm_i_P) - - if tkn_bl and tp_bl: - if cod_bl: - return asm_vals - else: - if cod_err > 0: dcod = -(cod_err - cod_tol)/asm_cod - else: dcod = -(cod_err + cod_tol)/asm_cod - _asm_vals = asm_vals * (1 + (asm_i_COD>0)*dcod) - _tkn_bl, _tkn_err, _tkn_tol, _asm_tkn = self.isbalanced(adm_tkn, _asm_vals, asm_i_N) - _tp_bl, _tp_err, _tp_tol, _asm_tp = self.isbalanced(adm_tp, _asm_vals, asm_i_P) - if _tkn_bl and _tp_bl: return _asm_vals - else: - warn('cannot balance COD, TKN, and TP at the same \n' - f'time with rtol={self.rtol} and atol={self.atol}.\n ' - f'influent (ADM) TKN is {adm_tkn}\n ' - f'effluent (ASM) TKN is {asm_tkn} or {_asm_tkn}\n ' - f'influent (ADM) TP is {adm_tp}\n ' - f'effluent (ASM) TP is {asm_tp} or {_asm_tp}. ' - f'influent (ADM) COD is {adm_cod}\n ' - f'effluent (ASM) COD is {asm_cod} or {asm_cod*(1+dcod)}. ') - return asm_vals - elif cod_bl and tp_bl: - if tkn_bl: - return asm_vals - else: - if tkn_err > 0: dtkn = -(tkn_err - tkn_tol)/asm_tkn - else: dtkn = -(tkn_err + tkn_tol)/asm_tkn - _asm_vals = asm_vals * (1 + (asm_i_N>0)*dtkn) - _cod_bl, _cod_err, _cod_tol, _asm_cod = self.isbalanced(adm_cod, _asm_vals, asm_i_COD) - _tp_bl, _tp_err, _tp_tol, _asm_tp = self.isbalanced(adm_tp, _asm_vals, asm_i_P) - if _cod_bl and _tp_bl: return _asm_vals - else: - warn('cannot balance COD, TKN, and TP at the same time' - f'time with rtol={self.rtol} and atol={self.atol}.\n ' - f'influent (ADM) COD is {adm_cod}\n ' - f'effluent (ASM) COD is {asm_cod} or {_asm_cod}\n ' - f'influent (ADM) TP is {adm_tp}\n ' - f'effluent (ASM) TP is {asm_tp} or {_asm_tp}. ' - f'influent (ADM) TKN is {adm_tkn}\n ' - f'effluent (ASM) TKN is {asm_tkn} or {asm_tkn*(1+dtkn)}. ') - return asm_vals - elif cod_bl and tkn_bl: - if tp_bl: - return asm_vals - else: - if tp_err > 0: dtp = -(tp_err - tp_tol)/asm_tp - else: dtp = -(tp_err + tp_tol)/asm_tp - _asm_vals = asm_vals * (1 + (asm_i_P>0)*dtp) - _cod_bl, _cod_err, _cod_tol, _asm_cod = self.isbalanced(adm_cod, _asm_vals, asm_i_COD) - _tkn_bl, _tkn_err, _tkn_tol, _asm_tkn = self.isbalanced(adm_tkn, _asm_vals, asm_i_N) - if _cod_bl and _tkn_bl: return _asm_vals - else: - warn('cannot balance COD, TKN, and TP at the same time' - f'time with rtol={self.rtol} and atol={self.atol}.\n ' - f'influent (ADM) COD is {adm_cod}\n ' - f'effluent (ASM) COD is {asm_cod} or {_asm_cod}\n ' - f'influent (ADM) TKN is {adm_tkn}\n ' - f'effluent (ASM) TKN is {asm_tkn} or {_asm_tkn}. ' - f'influent (ADM) TP is {adm_tp}\n ' - f'effluent (ASM) TP is {asm_tp} or {asm_tp*(1+dtp)}. ') - return asm_vals - else: - warn('cannot balance COD, TKN and TP at the same time. \n' - 'Atleast two of the three COD, TKN, and TP are not balanced \n' - f'time with rtol={self.rtol} and atol={self.atol}.\n ' - f'influent (ADM) COD is {adm_cod}\n ' - f'effluent (ASM) COD is {asm_cod}\n ' - f'influent (ADM) TP is {adm_tp}\n ' - f'effluent (ASM) TP is {asm_tp}' - f'influent (ADM) TKN is {adm_tkn}\n ' - f'effluent (ASM) TKN is {asm_tkn}. ') - return asm_vals - - def _compile_reactions(self): - # Retrieve constants - ins = self.ins[0] - outs = self.outs[0] - rtol = self.rtol - atol = self.atol - - cmps_adm = ins.components - # N balance - X_pr_i_N = cmps_adm.X_pr.i_N - S_aa_i_N = cmps_adm.S_aa.i_N - adm_X_I_i_N = cmps_adm.X_I.i_N - adm_S_I_i_N = cmps_adm.S_I.i_N - adm_i_N = cmps_adm.i_N - adm_bio_N_indices = cmps_adm.indices(('X_su', 'X_aa', 'X_fa', - 'X_c4', 'X_pro', 'X_ac', 'X_h2')) - - # P balance - X_pr_i_P = cmps_adm.X_pr.i_P - adm_X_I_i_P = cmps_adm.X_I.i_P - adm_S_I_i_P = cmps_adm.S_I.i_P - adm_i_P = cmps_adm.i_P - adm_bio_P_indices = cmps_adm.indices(('X_su', 'X_aa', 'X_fa', - 'X_c4', 'X_pro', 'X_ac', 'X_h2')) - - cmps_asm = outs.components - - # N balance - X_S_i_N = cmps_asm.X_S.i_N - S_F_i_N = cmps_asm.S_F.i_N - S_A_i_N = cmps_asm.S_A.i_N - asm_X_I_i_N = cmps_asm.X_I.i_N - asm_S_I_i_N = cmps_asm.S_I.i_N - asm_ions_idx = cmps_asm.indices(('S_NH4', 'S_A', 'S_NO3', 'S_PO4', 'X_PP', 'S_ALK')) - - # P balance - X_S_i_P = cmps_asm.X_S.i_P - S_F_i_P = cmps_asm.S_F.i_P - S_A_i_P = cmps_asm.S_A.i_P - asm_X_I_i_P = cmps_asm.X_I.i_P - asm_S_I_i_P = cmps_asm.S_I.i_P - - # Checks for direct mapping of X_PAO, X_PP, X_PHA - - # Check for X_PAO (measured as COD so i_COD = 1 in both ASM2d and ADM1) - asm_X_PAO_i_N = cmps_asm.X_PAO.i_N - adm_X_PAO_i_N = cmps_adm.X_PAO.i_N - if asm_X_PAO_i_N != adm_X_PAO_i_N: - raise RuntimeError('X_PAO cannot be directly mapped as N content' - f'in asm2d_X_PAO_i_N = {asm_X_PAO_i_N} is not equal to' - f'adm_X_PAO_i_N = {adm_X_PAO_i_N}') - - asm_X_PAO_i_P = cmps_asm.X_PAO.i_P - adm_X_PAO_i_P = cmps_adm.X_PAO.i_P - if asm_X_PAO_i_P != adm_X_PAO_i_P: - raise RuntimeError('X_PAO cannot be directly mapped as P content' - f'in asm2d_X_PAO_i_P = {asm_X_PAO_i_P} is not equal to' - f'adm_X_PAO_i_P = {adm_X_PAO_i_P}') - - # Checks not required for X_PP as measured as P in both, with i_COD = i_N = 0 - # Checks not required for X_PHA as measured as COD in both, with i_N = i_P = 0 - - alpha_IN = self.alpha_IN - alpha_IC = self.alpha_IC - alpha_IP = self.alpha_IP - alpha_vfa = self.alpha_vfa - f_corr = self.balance_cod_tkn - - def adm2asm(adm_vals): - S_su, S_aa, S_fa, S_va, S_bu, S_pro, S_ac, S_h2, S_ch4, S_IC, S_IN, S_IP, S_I, \ - X_ch, X_pr, X_li, X_su, X_aa, X_fa, X_c4, X_pro, X_ac, X_h2, X_I, \ - X_PHA, X_PP, X_PAO, S_K, S_Mg, X_MeOH, X_MeP, S_cat, S_an, H2O = adm_vals - - # Step 0: snapshot of charged components - # Not sure about charge on X_PP, S_Mg, S_K (PHA and PAO would have zero charge) - _ions = np.array([S_IN, S_IC, S_IP, X_PP, S_Mg, S_K, S_ac, S_pro, S_bu, S_va]) - - # Step 1a: convert biomass into X_S+X_ND - bio_cod = X_su + X_aa + X_fa + X_c4 + X_pro + X_ac + X_h2 - bio_n = sum((adm_vals*adm_i_N)[adm_bio_N_indices]) - bio_p = sum((adm_vals*adm_i_P)[adm_bio_P_indices]) - - # There is no X_P (particulate products arising due to biomass decay) - # or equivalent component in ASM2d - # xp_cod = bio_cod * (1-self.bio_to_xs) - # xp_ndm = xp_cod*X_P_i_N - # if xp_ndm > bio_n: - # warn('Not enough biomass N to map the specified proportion of ' - # 'biomass COD into X_P. Mapped as much COD as possible, the rest ' - # 'goes to X_S.') - # X_P = bio_n/asm_X_P_i_N - # bio_n = 0 - # else: - # X_P = xp_cod - # bio_n -= xp_ndm - - # X_S = bio_cod - X_P - - # COD balance - X_S = bio_cod - - # N balance - X_S_N = X_S*X_S_i_N - if X_S_N <= bio_n: - # In ASM1-ADM1 interface if there was excess bio_n compared to - # X_S_N, it was transferred to X_ND. But since X_ND does not - # exist in ASM2d, S_IN is used for N balance - # Here, S_IN is also used based on the fact that in case of - # deficit bio_N, S_IN is used to compensate for excess X_S_N - # in Nopens at al. 2009 - S_IN += (bio_n - X_S_N) - bio_n = 0 - elif X_S_N <= bio_n + S_IN: - S_IN -= (X_S_N - bio_n) - bio_n = 0 - else: - if isclose(X_S_N, bio_n + S_IN, rel_tol=rtol, abs_tol=atol): - S_IN = bio_n = 0 - else: - raise RuntimeError('Not enough nitrogen (S_IN + biomass) to map ' - 'all biomass COD into X_S') - - # P balance - X_S_P = X_S*X_S_i_P - if X_S_P <= bio_p: - # Here, S_IP is based on the fact that in case of - # deficit bio_N, S_IN is used to compensate for excess X_S_N - # in Nopens at al. 2009 - S_IP += (bio_p - X_S_P) - bio_p = 0 - elif X_S_P <= bio_p + S_IP: - S_IP -= (X_S_P - bio_p) - bio_p = 0 - else: - if isclose(X_S_P, bio_p + S_IP, rel_tol=rtol, abs_tol=atol): - S_IP = bio_p = 0 - else: - raise RuntimeError('Not enough phosphorous (S_IP + biomass) to map ' - 'all biomass COD into X_S') - - # Step 1b: convert particulate substrates into X_S - - # First we calculate the COD, N, and P content in particulate substrate - xsub_cod = X_ch + X_pr + X_li - xsub_n = X_pr*X_pr_i_N - xsub_p = X_pr*X_pr_i_P - - # Then we determine the amount of X_S required for COD, N, and P balance - X_S_cod = xsub_cod - X_S_n = xsub_n/X_S_i_N - X_S_p = xsub_p/X_S_i_P - - # Identify the limiting component and accordingly form X_S - X_S_add = min(X_S_cod, X_S_n, X_S_p) - X_S += X_S_add - - # Calculate the imbalance in the non-limiting components - if X_S_add == X_S_cod: - def_N = (xsub_n - X_S_add*X_S_i_N) - def_P = (xsub_p - X_S_add*X_S_i_P) - S_IN += def_N - S_IP += def_P - elif X_S_add == X_S_n: - def_COD = (xsub_cod - X_S_add) - def_P = (xsub_p - X_S_add*X_S_i_P) - S_A = def_COD # suggested by Jeremy - S_IP += def_P - else: - def_COD = (xsub_cod - X_S_add) - def_N = (xsub_n - X_S_add*X_S_i_N) - S_A = def_COD # suggested by Jeremy - S_IN += def_N - - # Step 2: map all X_I from ADM to ASM - excess_XIn = X_I * (adm_X_I_i_N - asm_X_I_i_N) - S_IN += excess_XIn - if S_IN < 0: - if isclose(S_IN, 0, rel_tol=rtol, abs_tol=atol): S_IN = 0 - # This seems to be a typo. Shouldn't there be an else here? - raise RuntimeError('Not enough nitrogen (X_I + S_IN) to map ' - 'all ADM X_I into ASM X_I') - - excess_XIp = X_I * (adm_X_I_i_P - asm_X_I_i_P) - S_IP += excess_XIp - if S_IP < 0: - if isclose(S_IP, 0, rel_tol=rtol, abs_tol=atol): S_IP = 0 - # This seems to be a typo. Shouldn't there be an else here? - raise RuntimeError('Not enough phosphorous (X_I + S_IP) to map ' - 'all ADM X_I into ASM X_I') - - # Step 3: map ADM S_I into ASM S_I and S_NH4 - excess_SIn = S_I * (adm_S_I_i_N - asm_S_I_i_N) - if excess_SIn > 0: - S_NH4 = excess_SIn - else: - S_NH4 = 0 - S_IN += excess_SIn - if S_IN < 0: - if isclose(S_IN, 0, rel_tol=rtol, abs_tol=atol): S_IN = 0 - # This seems to be a typo. Shouldn't there be an else here? - raise RuntimeError('Not enough nitrogen (S_I + S_IN) to map ' - 'all ADM S_I into ASM S_I') - S_NH4 += S_IN - - excess_SIp = S_I * (adm_S_I_i_P - asm_S_I_i_P) - if excess_SIp > 0: - S_PO4 = excess_SIp - else: - S_PO4 = 0 - S_IP += excess_SIp - if S_IP < 0: - if isclose(S_IP, 0, rel_tol=rtol, abs_tol=atol): S_IP = 0 - # This seems to be a typo. Shouldn't there be an else here? - raise RuntimeError('Not enough nitrogen (S_I + S_IP) to map ' - 'all ADM S_I into ASM S_I') - S_PO4 += S_IP - - # Step 4: map all soluble substrates into S_A and S_F - ssub_cod = S_su + S_aa + S_fa + S_va + S_bu + S_pro + S_ac - ssub_n = S_aa * S_aa_i_N - - # P balance not required as all the 7 soluble substrates have no P - - # N balance - S_F = ssub_n/S_F_i_N - # COD balance - S_A += ssub_cod - S_F - - # Technically both S_A_i_N and S_A_i_P would be 0 - if S_A_i_N > 0 or S_A_i_P > 0: - # excess N formed subtracted from S_NH4 - S_NH4 -= (ssub_cod - S_F)*S_A_i_N - # excess P (due to S_A) formed subtracted from S_PO4 - S_PO4 -= (ssub_cod - S_F)*S_A_i_P - - if S_F_i_P > 0: - # excess P (due to S_F) formed subtracted from S_PO4 - S_PO4 -= S_F*S_F_i_P - - - # Step 6: check COD and TKN balance - asm_vals = np.array(([ - 0, 0, # S_O2, S_N2, - S_NH4, - 0, - S_PO4, S_F, S_A, S_I, - 0, # S_ALK, - X_I, X_S, - 0, # X_H, - X_PAO, X_PP, X_PHA, # directly mapped - 0, # X_AUT, - X_MeOH, X_MeP, H2O])) # directly mapped - - if S_h2 > 0 or S_ch4 > 0: - warn('Ignored dissolved H2 or CH4.') - - asm_vals = f_corr(adm_vals, asm_vals) - - # Step 5: charge balance for alkalinity - S_NH4 = asm_vals[asm_ions_idx[0]] - S_A = asm_vals[asm_ions_idx[1]] - S_NO3 = asm_vals[asm_ions_idx[2]] - S_PO4 = asm_vals[asm_ions_idx[3]] - X_PP = asm_vals[asm_ions_idx[4]] - # Need to include S_K, S_Mg in the charge balance (Maybe ask Joy/Jeremy) - S_ALK = (sum(_ions * np.append([alpha_IN, alpha_IC, alpha_IP], alpha_vfa)) - (S_NH4/14 - S_A/64 - S_NO3/14 -1.5*S_PO4/31 - X_PP/31))*(-12) - asm_vals[asm_ions_idx[5]] = S_ALK - - return asm_vals - - self._reactions = adm2asm - - @property - def alpha_vfa(self): - # This may need change based on P-extension of ADM1 (ask Joy?) - return 1.0/self.cod_vfa*(-1.0/(1.0 + 10**(self.pKa[3:]-self.pH))) - - -# %% - -# While using this interface X_I.i_N in ASM2d should be 0.06, instead of 0.02. -class ASMtoADM(ADMjunction): - ''' - Interface unit to convert activated sludge model (ASM) components - to anaerobic digestion model (ADM) components. - - Parameters - ---------- - upstream : stream or str - Influent stream with ASM components. - downstream : stream or str - Effluent stream with ADM components. - adm1_model : obj - The anaerobic digestion process model (:class:`qsdsan.processes.ADM1_p_extension`). - xs_to_li : float - Split of slowly biodegradable substrate COD to lipid, - after all N is mapped into protein. - bio_to_li : float - Split of biomass COD to lipid, after all biomass N is - mapped into protein. - frac_deg : float - Biodegradable fraction of biomass COD. - rtol : float - Relative tolerance for COD and TKN balance. - atol : float - Absolute tolerance for COD and TKN balance. - - References - ---------- - [1] Nopens, I.; Batstone, D. J.; Copp, J. B.; Jeppsson, U.; Volcke, E.; - Alex, J.; Vanrolleghem, P. A. An ASM/ADM Model Interface for Dynamic - Plant-Wide Simulation. Water Res. 2009, 43, 1913–1923. - - [2] Flores-Alsina, X., Solon, K., Kazadi Mbamba, C., Tait, S., Gernaey, K. V., - Jeppsson, U., & Batstone, D. J. (2016). Modelling phosphorus (P), sulfur (S) - and iron (FE) interactions for dynamic simulations of anaerobic digestion processes. - Water Research, 95, 370–382. - - See Also - -------- - :class:`qsdsan.sanunits.ADMjunction` - - :class:`qsdsan.sanunits.ADMtoASM` - - `math.isclose ` - ''' - # User defined values - xs_to_li = 0.7 - bio_to_li = 0.4 - frac_deg = 0.68 - - - def isbalanced(self, lhs, rhs_vals, rhs_i): - rhs = sum(rhs_vals*rhs_i) - error = rhs - lhs - tol = max(self.rtol*lhs, self.rtol*rhs, self.atol) - return abs(error) <= tol, error, tol, rhs - - def balance_cod_tkn_tp(self, asm_vals, adm_vals): - cmps_asm = self.ins[0].components - cmps_adm = self.outs[0].components - asm_i_COD = cmps_asm.i_COD - adm_i_COD = cmps_adm.i_COD - non_tkn_idx = cmps_asm.indices(('S_NO', 'S_N2')) - asm_i_N = cmps_asm.i_N - adm_i_N = cmps_adm.i_N - asm_i_P = cmps_asm.i_P - adm_i_P = cmps_adm.i_P - asm_cod = sum(asm_vals*asm_i_COD) - asm_tkn = sum(asm_vals*asm_i_N) - sum(asm_vals[non_tkn_idx]) - asm_tp = sum(asm_vals*asm_i_P) - cod_bl, cod_err, cod_tol, adm_cod = self.isbalanced(asm_cod, adm_vals, adm_i_COD) - tkn_bl, tkn_err, tkn_tol, adm_tkn = self.isbalanced(asm_tkn, adm_vals, adm_i_N) - tp_bl, tp_err, tp_tol, adm_tp = self.isbalanced(asm_tp, adm_vals, adm_i_P) - - if tkn_bl and tp_bl: - if cod_bl: - return adm_vals - else: - if cod_err > 0: dcod = -(cod_err - cod_tol)/adm_cod - else: dcod = -(cod_err + cod_tol)/adm_cod - _adm_vals = adm_vals * (1 + (adm_i_COD>0)*dcod) - _tkn_bl, _tkn_err, _tkn_tol, _adm_tkn = self.isbalanced(asm_tkn, _adm_vals, adm_i_N) - _tp_bl, _tp_err, _tp_tol, _adm_tp = self.isbalanced(asm_tp, _adm_vals, adm_i_P) - if _tkn_bl and _tp_bl: return _adm_vals - else: - warn('cannot balance COD, TKN, and TP at the same \n' - f'time with rtol={self.rtol} and atol={self.atol}.\n ' - f'influent (ASM) TKN is {asm_tkn}\n ' - f'effluent (ADM) TKN is {adm_tkn} or {_adm_tkn}\n ' - f'influent TP is {asm_tp}\n ' - f'effluent TP is {adm_tp} or {_adm_tp}. ' - f'influent COD is {asm_cod}\n ' - f'effluent COD is {adm_cod} or {adm_cod*(1+dcod)}. ') - return adm_vals - elif cod_bl and tp_bl: - if tkn_bl: - return adm_vals - else: - if tkn_err > 0: dtkn = -(tkn_err - tkn_tol)/adm_tkn - else: dtkn = -(tkn_err + tkn_tol)/adm_tkn - _adm_vals = adm_vals * (1 + (adm_i_N>0)*dtkn) - _cod_bl, _cod_err, _cod_tol, _adm_cod = self.isbalanced(asm_cod, _adm_vals, adm_i_COD) - _tp_bl, _tp_err, _tp_tol, _adm_tp = self.isbalanced(asm_tp, _adm_vals, adm_i_P) - if _cod_bl and _tp_bl: return _adm_vals - else: - warn('cannot balance COD, TKN, and TP at the same time' - f'time with rtol={self.rtol} and atol={self.atol}.\n ' - f'influent (ASM) COD is {asm_cod}\n ' - f'effluent (ADM) COD is {adm_cod} or {_adm_cod}\n ' - f'influent TP is {asm_tp}\n ' - f'effluent TP is {adm_tp} or {_adm_tp}. ' - f'influent TKN is {asm_tkn}\n ' - f'effluent TKN is {adm_tkn} or {adm_tkn*(1+dtkn)}. ' - 'To balance TKN please ensure ASM2d(X_I.i_N) = ADM1(X_I.i_N)') - return adm_vals - elif cod_bl and tkn_bl: - if tp_bl: - return adm_vals - else: - if tp_err > 0: dtp = -(tp_err - tp_tol)/adm_tp - else: dtp = -(tp_err + tp_tol)/adm_tp - _adm_vals = adm_vals * (1 + (adm_i_P>0)*dtp) - _cod_bl, _cod_err, _cod_tol, _adm_cod = self.isbalanced(asm_cod, _adm_vals, adm_i_COD) - _tkn_bl, _tkn_err, _tkn_tol, _adm_tkn = self.isbalanced(asm_tkn, _adm_vals, adm_i_N) - if _cod_bl and _tkn_bl: return _adm_vals - else: - warn('cannot balance COD, TKN, and TP at the same time' - f'time with rtol={self.rtol} and atol={self.atol}.\n ' - f'influent (ASM) COD is {asm_cod}\n ' - f'effluent (ADM) COD is {adm_cod} or {_adm_cod}\n ' - f'influent TKN is {asm_tkn}\n ' - f'effluent TKN is {adm_tkn} or {_adm_tkn}. ' - f'influent TP is {asm_tp}\n ' - f'effluent TP is {adm_tp} or {adm_tp*(1+dtp)}. ') - return adm_vals - else: - warn('cannot balance COD, TKN and TP at the same time. \n' - 'Atleast two of the three COD, TKN, and TP are not balanced \n' - f'time with rtol={self.rtol} and atol={self.atol}.\n ' - f'influent (ASM) COD is {asm_cod}\n ' - f'effluent (ADM) COD is {adm_cod}\n ' - f'influent TP is {asm_tp}\n ' - f'effluent TP is {adm_tp}' - f'influent TKN is {asm_tkn}\n ' - f'effluent TKN is {adm_tkn}. ') - return adm_vals - - def _compile_reactions(self): - # Retrieve constants - ins = self.ins[0] - outs = self.outs[0] - rtol = self.rtol - atol = self.atol - - cmps_asm = ins.components - - # For COD balance - S_NO3_i_COD = cmps_asm.S_NO3.i_COD - - # For N balance - X_H_i_N = cmps_asm.X_H.i_N - X_AUT_i_N = cmps_asm.X_AUT.i_N - S_F_i_N = cmps_asm.S_F.i_N - X_S_i_N = cmps_asm.X_S.i_N - asm_X_I_i_N = cmps_asm.X_I.i_N - asm_S_I_i_N = cmps_asm.S_I.i_N - - # For P balance - X_H_i_P = cmps_asm.X_H.i_P - X_AUT_i_P = cmps_asm.X_AUT.i_P - S_F_i_P = cmps_asm.S_F.i_P - X_S_i_P = cmps_asm.X_S.i_P - asm_X_I_i_P = cmps_asm.X_I.i_P - - if cmps_asm.S_A.i_N > 0: - warn(f'S_A in ASM has positive nitrogen content: {cmps_asm.S_S.i_N} gN/gCOD. ' - 'These nitrogen will be ignored by the interface model ' - 'and could lead to imbalance of TKN after conversion.') - if cmps_asm.S_A.i_P > 0: - warn(f'S_A in ASM has positive phosphorous content: {cmps_asm.S_S.i_P} gN/gCOD. ' - 'These phosphorous will be ignored by the interface model ' - 'and could lead to imbalance of TP after conversion.') - if cmps_asm.S_I.i_P > 0: - warn(f'S_I in ASM has positive phosphorous content: {cmps_asm.S_I.i_P} gN/gCOD. ' - 'These phosphorous will be ignored by the interface model ' - 'and could lead to imbalance of TP after conversion.') - # We do not need to check if X_S.i_N != 0 since we take care of it using X_ND_asm1 - # We do not need to check if S_F.i_N != 0 since we take care of it using S_ND_asm1 - - cmps_adm = outs.components - - # For nitrogen balance - S_aa_i_N = cmps_adm.S_aa.i_N - X_pr_i_N = cmps_adm.X_pr.i_N - adm_S_I_i_N = cmps_adm.S_I.i_N - adm_X_I_i_N = cmps_adm.X_I.i_N - - # For phosphorous balance - X_pr_i_P = cmps_adm.X_pr.i_P - adm_S_I_i_P = cmps_adm.S_I.i_P - adm_X_I_i_P = cmps_adm.X_I.i_P - - # Checks for direct mapping of X_PAO, X_PP, X_PHA - - # Check for X_PAO (measured as COD so i_COD = 1 in both ASM2d and ADM1) - asm_X_PAO_i_N = cmps_asm.X_PAO.i_N - adm_X_PAO_i_N = cmps_adm.X_PAO.i_N - if asm_X_PAO_i_N != adm_X_PAO_i_N: - raise RuntimeError('X_PAO cannot be directly mapped as N content' - f'in asm2d_X_PAO_i_N = {asm_X_PAO_i_N} is not equal to' - f'adm_X_PAO_i_N = {adm_X_PAO_i_N}') - - asm_X_PAO_i_P = cmps_asm.X_PAO.i_P - adm_X_PAO_i_P = cmps_adm.X_PAO.i_P - if asm_X_PAO_i_P != adm_X_PAO_i_P: - raise RuntimeError('X_PAO cannot be directly mapped as P content' - f'in asm2d_X_PAO_i_P = {asm_X_PAO_i_P} is not equal to' - f'adm_X_PAO_i_P = {adm_X_PAO_i_P}') - - # Checks not required for X_PP as measured as P in both, with i_COD = i_N = 0 - # Checks not required for X_PHA as measured as COD in both, with i_N = i_P = 0 - - adm_ions_idx = cmps_adm.indices(['S_IN', 'S_IP', 'S_IC', 'S_cat', 'S_an']) - - frac_deg = self.frac_deg - alpha_IP = self.alpha_IP - alpha_IN = self.alpha_IN - alpha_IC = self.alpha_IC - proton_charge = 10**(-self.pKa[0]+self.pH) - 10**(-self.pH) # self.pKa[0] is pKw - f_corr = self.balance_cod_tkn_tp - - def asm2adm(asm_vals): - # S_I, S_S, X_I, X_S, X_BH, X_BA, X_P, S_O, S_NO, S_NH, S_ND, X_ND, S_ALK, S_N2, H2O = asm_vals - - S_O2, S_N2, S_NH4, S_NO3, S_PO4, S_F, S_A, S_I, S_ALK, X_I, X_S, X_H, \ - X_PAO, X_PP, X_PHA, X_AUT, X_MeOH, X_MeP, H2O = asm_vals - - # Step 0: charged component snapshot (# pg. 84 of IWA ASM textbook) - _sno3 = S_NO3 - _snh4 = S_NH4 - _salk = S_ALK - _spo4 = S_PO4 - _sa = S_A - _xpp = X_PP - - # Step 1: remove any remaining COD demand - O2_coddm = S_O2 - NO3_coddm = -S_NO3*S_NO3_i_COD - - # cod_spl = S_S + X_S + X_BH + X_BA - # Replacing S_S with S_F + S_A (IWA ASM textbook) - - cod_spl = (S_A + S_F) + X_S + (X_H + X_AUT) - - # bioN = X_BH*X_BH_i_N + X_BA*X_BA_i_N - - bioN = X_H*X_H_i_N + X_AUT*X_AUT_i_N - bioP = X_H*X_H_i_P + X_AUT*X_AUT_i_P - - # To be used in Step 2 - S_ND_asm1 = S_F*S_F_i_N #S_ND (in asm1) equals the N content in S_F - # To be used in Step 3 - X_ND_asm1 = X_S*X_S_i_N #X_ND (in asm1) equals the N content in X_S - # To be used in Step 5 (a) - X_S_P = X_S*X_S_i_P - # To be used in Step 5 (b) - S_F_P = S_F*S_F_i_P - - if cod_spl <= O2_coddm: - S_O2 = O2_coddm - cod_spl - S_F = S_A = X_S = X_H = X_AUT = 0 - elif cod_spl <= O2_coddm + NO3_coddm: - S_O2 = 0 - S_NO3 = -(O2_coddm + NO3_coddm - cod_spl)/S_NO3_i_COD - S_A = S_F = X_S = X_H = X_AUT = 0 - else: - S_A -= O2_coddm + NO3_coddm - if S_A < 0: - S_F += S_A - S_A = 0 - if S_F < 0: - X_S += S_F - S_F = 0 - if X_S < 0: - X_H += X_S - X_S = 0 - if X_H < 0: - X_AUT += X_H - X_H = 0 - S_O2 = S_NO3 = 0 - - # Step 2: convert any readily biodegradable - # COD and TKN into amino acids and sugars - - # S_S (in asm1) equals to the sum of S_F and S_A (pg. 82 IWA ASM models handbook) - S_S_asm1 = S_F + S_A - - # First we calculate the amount of amino acid required in ADM1 - # if all available soluble organic N can be mapped to amino acid - req_scod = S_ND_asm1 / S_aa_i_N - - # if available S_S is not enough to fulfill that amino acid requirement - if S_S_asm1 < req_scod: - # then all available S_S is mapped to amino acids - S_aa = S_S_asm1 - # and no S_S would be available for conversion to sugars - S_su = 0 - # This needs to be followed by a corresponding loss in soluble organic N - S_ND_asm1 -= S_aa * S_aa_i_N - # if available S_S is more than enough to fulfill that amino acid requirement - else: - # All soluble organic N will be mapped to amino acid - S_aa = req_scod - # The line above implies that a certain portion of S_S would also be consumed to form amino acid - # The S_S which is left would form sugar - # In simpler terms; S_S = S_S - S_aa; S_su = S_S - S_su = S_S_asm1 - S_aa - # All soluble organic N would thus be consumed in amino acid formation - S_ND_asm1 = 0 - - # Step 3: convert slowly biodegradable COD and TKN - # into proteins, lipids, and carbohydrates - - # First we calculate the amount of protein required in ADM1 - # if all available particulate organic N can be mapped to amino acid - req_xcod = X_ND_asm1 / X_pr_i_N - # Since X_pr_i_N >> X_pr_i_P there's no need to check req_xcod for N and P separately (CONFIRM LATER 05/16) - - # if available X_S is not enough to fulfill that protein requirement - if X_S < req_xcod: - # then all available X_S is mapped to amino acids - X_pr = X_S - # and no X_S would be available for conversion to lipid or carbohydrates - X_li = X_ch = 0 - # This needs to be followed by a corresponding loss in particulate organic N - X_ND_asm1 -= X_pr * X_pr_i_N - - # For P balance (CONFIRM LATER 05/16) - # This needs to be followed by a corresponding loss in particulate organic N - X_S_P -= X_pr * X_pr_i_P - - # if available X_S is more than enough to fulfill that protein requirement - else: - # All particulate organic N will be mapped to amino acid - X_pr = req_xcod - # The line above implies that a certain portion of X_S would also be consumed to form protein - # The X_S which is left would form lipid and carbohydrates in a percentage define by the user - X_li = self.xs_to_li * (X_S - X_pr) - X_ch = (X_S - X_pr) - X_li - # All particulate organic N would thus be consumed in amino acid formation - X_ND_asm1 = 0 - - # For P balance (CONFIRM LATER 05/16) - # This needs to be followed by a corresponding loss in particulate organic N - X_S_P -= X_pr * X_pr_i_P - - # Step 4: convert active biomass into protein, lipids, - # carbohydrates and potentially particulate TKN - - # First the amount of biomass N/P available for protein, lipid etc is determined - # For this calculation, from total biomass N available the amount - # of particulate inert N/P expected in ADM1 is subtracted - - available_bioN = bioN - (X_H + X_AUT) * (1-frac_deg) * adm_X_I_i_N - if available_bioN < 0: - raise RuntimeError('Not enough N in X_H and X_AUT to fully convert ' - 'the non-biodegradable portion into X_I in ADM1.') - - available_bioP = bioP - (X_H + X_AUT) * (1-frac_deg) * adm_X_I_i_P - if available_bioP < 0: - raise RuntimeError('Not enough P in X_H and X_AUT to fully convert ' - 'the non-biodegradable portion into X_I in ADM1.') - - # Then the amount of biomass N/P required for biomass conversion to protein is determined - req_bioN = (X_H + X_AUT) * frac_deg * X_pr_i_N - req_bioP = (X_H + X_AUT) * frac_deg * X_pr_i_P - - # Case I: if both available biomass N/P and particulate organic N/P is greater than - # required biomass N/P for conversion to protein - if available_bioN + X_ND_asm1 >= req_bioN and available_bioP + X_S_P >= req_bioP: - # then all biodegradable biomass N/P (corrsponding to protein demand) is converted to protein - X_pr += (X_H + X_AUT) * frac_deg - # the remaining biomass N/P is transfered as organic N/P - X_ND_asm1 += available_bioN - req_bioN - X_S_P += available_bioP - req_bioP - - # Case II: if available biomass N and particulate organic N is less than - # required biomass N for conversion to protein, but available biomass P and - # particulate organic P is greater than required biomass P for conversion to protein - - # Case III: if available biomass P and particulate organic P is less than - # required biomass P for conversion to protein, but available biomass N and - # particulate organic N is greater than required biomass N for conversion to protein - - # Case IV: if both available biomass N/P and particulate organic N/P is less than - # required biomass N/P for conversion to protein - else: - - if (available_bioP + X_S_P)/X_pr_i_P < (available_bioN + X_ND_asm1)/X_pr_i_N: - # all available P and particulate organic P is converted to protein - bio2pr = (available_bioP + X_S_P)/X_pr_i_P - X_pr += bio2pr - # Biodegradable biomass available after conversion to protein is calculated - bio_to_split = (X_H + X_AUT) * frac_deg - bio2pr - # Part of the remaining biomass is mapped to lipid based on user defined value - bio_split_to_li = bio_to_split * self.bio_to_li - X_li += bio_split_to_li - # The other portion of the remanining biomass is mapped to carbohydrates - X_ch += (bio_to_split - bio_split_to_li) - # Since all organic P has been mapped to protein, none is left - X_S_P = 0 - - # the remaining biomass N is transfered as organic N - X_ND_asm1 += available_bioN - (bio2pr*X_pr_i_N) - - else: - # all available N and particulate organic N is converted to protein - bio2pr = (available_bioN + X_ND_asm1)/X_pr_i_N - X_pr += bio2pr - # Biodegradable biomass available after conversion to protein is calculated - bio_to_split = (X_H + X_AUT) * frac_deg - bio2pr - # Part of the remaining biomass is mapped to lipid based on user defined value - bio_split_to_li = bio_to_split * self.bio_to_li - X_li += bio_split_to_li - # The other portion of the remanining biomass is mapped to carbohydrates - X_ch += (bio_to_split - bio_split_to_li) - # Since all organic N has been mapped to protein, none is left - X_ND_asm1 = 0 - - # the remaining biomass P is transfered as organic P - X_S_P += available_bioP - (bio2pr*X_pr_i_P) - - - # Step 5: map particulate inerts - - # 5 (a) - # First determine the amount of particulate inert N/P available from ASM2d - xi_nsp_asm2d = X_I * asm_X_I_i_N - xi_psp_asm2d = X_I * asm_X_I_i_P - - # Then determine the amount of particulate inert N/P that could be produced - # in ADM1 given the ASM1 X_I - xi_ndm = X_I * adm_X_I_i_N - xi_pdm = X_I * adm_X_I_i_P - - # if particulate inert N available in ASM1 is greater than ADM1 demand - if xi_nsp_asm2d + X_ND_asm1 >= xi_ndm: - deficit = xi_ndm - xi_nsp_asm2d - # COD balance - X_I += (X_H+X_AUT) * (1-frac_deg) - # N balance - X_ND_asm1 -= deficit - # P balance - if xi_psp_asm2d + X_S_P >= xi_pdm: - deficit = xi_pdm - xi_psp_asm2d - X_S_P -= deficit - elif isclose(xi_psp_asm2d+X_S_P, xi_pdm, rel_tol=rtol, abs_tol=atol): - X_S_P = 0 - else: - raise RuntimeError('Not enough P in X_I, X_S to fully ' - 'convert X_I in ASM2d into X_I in ADM1.') - elif isclose(xi_nsp_asm2d+X_ND_asm1, xi_ndm, rel_tol=rtol, abs_tol=atol): - # COD balance - X_I += (X_H+X_AUT) * (1-frac_deg) - # N balance - X_ND_asm1 = 0 - # P balance - if xi_psp_asm2d + X_S_P >= xi_pdm: - deficit = xi_pdm - xi_psp_asm2d - X_S_P -= deficit - elif isclose(xi_psp_asm2d+X_S_P, xi_pdm, rel_tol=rtol, abs_tol=atol): - X_S_P = 0 - else: - raise RuntimeError('Not enough P in X_I, X_S to fully ' - 'convert X_I in ASM2d into X_I in ADM1.') - else: - # Since the N balance cannot hold, the P balance is not futher checked - raise RuntimeError('Not enough N in X_I, X_S to fully ' - 'convert X_I in ASM2d into X_I in ADM1.') - - # 5(b) - - # Then determine the amount of soluble inert N/P that could be produced - # in ADM1 given the ASM1 X_I - req_sn = S_I * adm_S_I_i_N - req_sp = S_I * adm_S_I_i_P - - supply_inert_n_asm2d = S_I * asm_S_I_i_N - - # N balance - if req_sn <= S_ND_asm1 + supply_inert_n_asm2d: - S_ND_asm1 -= (req_sn - supply_inert_n_asm2d) - supply_inert_n_asm2d = 0 - # P balance - if req_sp <= S_F_P: - S_F_P -= req_sp - elif req_sp <= S_F_P + X_S_P: - X_S_P -= (req_sp - S_F_P) - S_F_P = 0 - elif req_sp <= S_F_P + X_S_P + S_PO4: - S_PO4 -= (req_sp - S_F_P - X_S_P) - S_F_P = X_S_P = 0 - else: - warn('Additional soluble inert COD is mapped to S_su.') - # Can these be executed in this case? I think so - SI_cod = (S_F_P + X_S_P + S_PO4)/adm_S_I_i_P - S_su += S_I - SI_cod - S_I = SI_cod - S_F_P = X_S_P = S_PO4 = 0 - # Should I redo N balance here? - # N balance - elif req_sn <= S_ND_asm1 + X_ND_asm1 + supply_inert_n_asm2d: - X_ND_asm1 -= (req_sn - S_ND_asm1 - supply_inert_n_asm2d) - S_ND_asm1 = supply_inert_n_asm2d = 0 - # P balance - if req_sp <= S_F_P: - S_F_P -= req_sp - elif req_sp <= S_F_P + X_S_P: - X_S_P -= (req_sp - S_F_P) - S_F_P = 0 - elif req_sp <= S_F_P + X_S_P + S_PO4: - S_PO4 -= (req_sp - S_F_P - X_S_P) - S_F_P = X_S_P = 0 - else: - warn('Additional soluble inert COD is mapped to S_su.') - # Can these be executed in this case? I think so - SI_cod = (S_F_P + X_S_P + S_PO4)/adm_S_I_i_P - S_su += S_I - SI_cod - S_I = SI_cod - S_F_P = X_S_P = S_PO4 = 0 - # Should I redo N balance here? - # N balance - elif req_sn <= S_ND_asm1 + X_ND_asm1 + S_NH4 + supply_inert_n_asm2d: - S_NH4 -= (req_sn - S_ND_asm1 - X_ND_asm1 - supply_inert_n_asm2d) - S_ND_asm1 = X_ND_asm1 = supply_inert_n_asm2d = 0 - # P balance - if req_sp <= S_F_P: - S_F_P -= req_sp - elif req_sp <= S_F_P + X_S_P: - X_S_P -= (req_sp - S_F_P) - S_F_P = 0 - elif req_sp <= S_F_P + X_S_P + S_PO4: - S_PO4 -= (req_sp - S_F_P - X_S_P) - S_F_P = X_S_P = 0 - else: - warn('Additional soluble inert COD is mapped to S_su.') - # Can these be executed in this case? I think so - SI_cod = (S_F_P + X_S_P + S_PO4)/adm_S_I_i_P - S_su += S_I - SI_cod - S_I = SI_cod - S_F_P = X_S_P = S_PO4 = 0 - # Should I redo N balance here? - elif req_sp <= S_F_P or req_sp <= S_F_P + X_S_P or req_sp <= S_F_P + X_S_P + S_PO4: - warn('Additional soluble inert COD is mapped to S_su.') - SI_cod = (S_ND_asm1 + X_ND_asm1 + S_NH4 + supply_inert_n_asm2d)/adm_S_I_i_N - S_su += S_I - SI_cod - S_I = SI_cod - S_ND_asm1 = X_ND_asm1 = S_NH4 = supply_inert_n_asm2d = 0 - req_sp = S_I * adm_S_I_i_P - if req_sp <= S_F_P: - S_F_P -= req_sp - elif req_sp <= S_F_P + X_S_P: - X_S_P -= (req_sp - S_F_P) - S_F_P = 0 - elif req_sp <= S_F_P + X_S_P + S_PO4: - S_PO4 -= (req_sp - S_F_P - X_S_P) - S_F_P = X_S_P = 0 - else: - if (S_ND_asm1 + X_ND_asm1 + S_NH4 + supply_inert_n_asm2d)/adm_S_I_i_N < (S_F_P + X_S_P + S_PO4)/adm_S_I_i_P: - warn('Additional soluble inert COD is mapped to S_su.') - SI_cod = (S_ND_asm1 + X_ND_asm1 + S_NH4 + supply_inert_n_asm2d)/adm_S_I_i_N - S_su += S_I - SI_cod - S_I = SI_cod - S_ND_asm1 = X_ND_asm1 = S_NH4 = supply_inert_n_asm2d = 0 - - req_sp = S_I * adm_S_I_i_P - S_PO4 -= (req_sp - S_F_P - X_S_P) - S_F_P = X_S_P = 0 - else: - warn('Additional soluble inert COD is mapped to S_su.') - SI_cod = (S_F_P + X_S_P + S_PO4)/adm_S_I_i_P - S_su += S_I - SI_cod - S_I = SI_cod - S_F_P = X_S_P = S_PO4 = 0 - - req_sn = S_I * adm_S_I_i_N - S_NH4 -= (req_sn - S_ND_asm1 - X_ND_asm1 - supply_inert_n_asm2d) - S_ND_asm1 = X_ND_asm1 = supply_inert_n_asm2d = 0 - - # Step 6: Step map any remaining TKN/P - S_IN = S_ND_asm1 + X_ND_asm1 + S_NH4 + supply_inert_n_asm2d - S_IP = S_F_P + X_S_P + S_PO4 - - # Step 8: check COD and TKN balance - # has TKN: S_aa, S_IN, S_I, X_pr, X_I - S_IC = S_cat = S_an = 0 - - # When mapping components directly in Step 9 ensure the values of - # cmps.i_N, cmps.i_P, and cmps.i_COD are same in both ASM2d and ADM1 - - # Step 9: Mapping common state variables directly - # The next three commented lines are executed when outputting - # array of ADM1 components - # X_PAO (ADM1) = X_PAO (ASM2d) - # X_PP (ADM1) = X_PP (ASM2d) - # X_PHA (ADM1) = X_PHA (ASM2d) - # X_MeOH (ADM1) = X_MeOH (ASM2d) - # X_MeP (ADM1) = X_MeP (ASM2d) - - adm_vals = np.array([ - S_su, S_aa, - 0, 0, 0, 0, 0, # S_fa, S_va, S_bu, S_pro, S_ac, - 0, 0, # S_h2, S_ch4, - S_IC, S_IN, S_IP, S_I, - X_ch, X_pr, X_li, - 0, 0, 0, 0, 0, 0, 0, # X_su, X_aa, X_fa, X_c4, X_pro, X_ac, X_h2, - X_I, X_PHA, X_PP, X_PAO, - 0, 0, # S_K, S_Mg, - X_MeOH, X_MeP, - S_cat, S_an, H2O]) - - adm_vals = f_corr(asm_vals, adm_vals) - - # Step 7: charge balance - asm_charge_tot = - _sa/64 + _snh4/14 - _sno3/14 - 1.5*_spo4/31 - _salk - _xpp/31 #Based on page 84 of IWA ASM handbook - #!!! charge balance should technically include VFAs, - # but VFAs concentrations are assumed zero per previous steps?? - S_IN = adm_vals[adm_ions_idx[0]] - S_IP = adm_vals[adm_ions_idx[1]] - #!!! charge balance from ADM1 should technically include S_K, and S_Mg, - #but since both are zero, it is acceptable - S_IC = (asm_charge_tot -S_IN*alpha_IN -S_IP*alpha_IP)/alpha_IC - net_Scat = asm_charge_tot + proton_charge - if net_Scat > 0: - S_cat = net_Scat - S_an = 0 - else: - S_cat = 0 - S_an = -net_Scat - - adm_vals[adm_ions_idx[2:]] = [S_IC, S_cat, S_an] - - return adm_vals - - self._reactions = asm2adm From 715e379c27d5a8194fd3dec1ab5b14d0f2dd3e45 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Tue, 28 May 2024 22:52:56 -0700 Subject: [PATCH 346/424] PFR checkpoint --- qsdsan/sanunits/_pfr.py | 80 +++++ .../sanunits/_suspended_growth_bioreactor.py | 307 +++++++++++++++++- 2 files changed, 377 insertions(+), 10 deletions(-) create mode 100644 qsdsan/sanunits/_pfr.py diff --git a/qsdsan/sanunits/_pfr.py b/qsdsan/sanunits/_pfr.py new file mode 100644 index 00000000..333dc601 --- /dev/null +++ b/qsdsan/sanunits/_pfr.py @@ -0,0 +1,80 @@ +# -*- coding: utf-8 -*- +""" +Created on Fri May 24 08:18:43 2024 + +@author: joy_c +""" +import numpy as np +from numba import njit, cfunc + + +N = 6 +V = np.array([249, 2313, 2366, 2366, 4982, 4982]) +denom = np.diag(1/V) +f_in = np.array([[0, 0.8, 0.2, 0,0,0], + [1, 0, 0, 0, 0, 0]]) +internal_recycles = [(5, 2, 1.5e5)] # from, to, Q +Q_internal = np.zeros(N) +for i_from, i_to, qr in internal_recycles: + Q_internal[i_to: i_from] += qr + +DO = [0,0,0,0,2.,2.] +kLa = [0]*6 +DOsat = 8.0 + +#%% +from exposan import bsm1 +bsm1.load() +cmps = bsm1.PE.components +asm = bsm1.A1.suspended_growth_model +M_stoi = asm.stoichio_eval() +f_rho = asm.rate_function +DO_id = cmps.index('S_O') + +#%% +@njit +def dydt(t, QC_ins, QC, dQC_ins=None): + y = QC.reshape((N, len(QC)/N)) + Cs = y[:,:-1] + if any(DO): + for i in range(N): + if DO[i] > 0: Cs[i, DO_id] = DO[i] + elif any(kLa): + do = Cs[:, DO_id] + aer = kLa*(DOsat-do) + aer[aer < 0] = 0. + Qs = np.dot(QC_ins[:,-1], f_in).cumsum() + Q_internal + M_ins = f_in.T @ np.diag(QC_ins[:,-1]) @ QC_ins[:,:-1] # N * n_cmps + for i_from, i_to, qr in internal_recycles: + M_ins[i_to] += Cs[i_from] * qr + # rhos = np.apply_along_axis(f_rho, 1, Cs) # n_zone * n_process + rhos = np.vstack([f_rho(c) for c in Cs]) + rxn = rhos @ M_stoi + dy = np.zeros_like(y) + dy[:,:-1] = denom @ (M_ins - np.diag(Qs) @ Cs) + rxn + if any(DO): dy[:,DO_id] = 0. + elif any(kLa): dy[:,DO_id] += aer + return dy.flatten() + +#%% +import qsdsan.sanunits as su +from qsdsan import System +s = bsm1.sys.flowsheet.stream +u = bsm1.sys.flowsheet.unit +inf = s.wastewater +inf.unlink() +ras = s.RAS +ras.unlink() +s.treated.unlink() +AS = su.PFR('AS', ins=[inf, ras], outs=0-u.C1, V_tanks=[1000]*2+[1333]*3, + influent_fractions=[[1.0, 0,0,0,0], [1.0, 0,0,0,0]], + internal_recycles=[(4,0,55338)], kLa=[0,0,240,240,84], DO_ID='S_O', + suspended_growth_model=u.A1._model) +AS.set_init_conc( + S_I=30, S_S=5, X_I=1000, X_S=100, X_BH=500, X_BA=100, X_P=100, S_O=2, S_NO=20, + S_NH=2, S_ND=1, X_ND=1, S_ALK=84 + ) +sys = System('sys', path=(AS, u.C1)) + +#%% +sys.simulate(t_span=(0,50), method='BDF') diff --git a/qsdsan/sanunits/_suspended_growth_bioreactor.py b/qsdsan/sanunits/_suspended_growth_bioreactor.py index 955807b6..38d1aa18 100644 --- a/qsdsan/sanunits/_suspended_growth_bioreactor.py +++ b/qsdsan/sanunits/_suspended_growth_bioreactor.py @@ -19,11 +19,12 @@ from math import floor, ceil import numpy as np import pandas as pd -# from numba import njit +from numba import njit __all__ = ('CSTR', 'BatchExperiment', 'SBR', + 'PFR', ) def _add_aeration_to_growth_model(aer, model): @@ -732,16 +733,302 @@ def J_func(t, y): return J_func(*y) return (dC_dt, J_func) -# class PFR(SanUnit): +#%% +class PFR(SanUnit): + + _N_ins = 1 + _N_outs = 1 + _ins_size_is_fixed = False + _outs_size_is_fixed = True + + def __init__(self, ID='', ins=None, outs=(), thermo=None, init_with='WasteStream', + N_tanks_in_series=5, V_tanks=[1500, 1500, 3000, 3000, 3000], + influent_fractions=[[1.0, 0,0,0,0]], internal_recycles=[(4,0,35000),], + DO_setpoints=[], kLa=[0, 0, 120, 120, 60], DO_sat=8.0, + DO_ID='S_O2', suspended_growth_model=None, + isdynamic=True, exogenous_vars=(), **kwargs): + if exogenous_vars: + warn(f'currently exogenous dynamic variables are not supported in process simulation for {self.ID}') + SanUnit.__init__(self, ID, ins, outs, thermo, init_with, isdynamic=isdynamic, + exogenous_vars=exogenous_vars, **kwargs) + self.N_tanks_in_series = N_tanks_in_series + self.V_tanks = V_tanks + self.influent_fractions = influent_fractions + self.internal_recycles = internal_recycles + self.DO_setpoints = DO_setpoints + self.kLa = kLa + self.DO_sat = DO_sat + self.DO_ID = DO_ID + self.suspended_growth_model = suspended_growth_model + self._concs = None + self._Qs = self.V_tanks * 0 + + @property + def V_tanks(self): + '''[iterable[float]] Volumes of CSTRs in series [m3]''' + return self._Vs + @V_tanks.setter + def V_tanks(self, Vs): + if not iter(Vs): + raise TypeError(f'V_tanks must be an iterable, not {type(Vs).__name__}') + elif len(Vs) != self.N_tanks_in_series: + raise RuntimeError(f'cannot set the volumes of {self.N_tanks_in_series} tanks' + f'in series with {len(Vs)} value(s).') + else: self._Vs = np.asarray(Vs) + + @property + def influent_fractions(self): + '''[iterable[float]] Fractions of influents fed into different zones [unitless]''' + return self._f_ins + @influent_fractions.setter + def influent_fractions(self, fs): + fs = np.asarray(fs) + if len(fs.shape) == 1: + if len(self.ins) != 1: + raise RuntimeError(f'influent fractions must be a 2d array, with {len(self.ins)} row(s)' + f'and {self.N_tanks_in_series} columns') + if fs.shape[0] != self.N_tanks_in_series: + raise RuntimeError('cannot set the fractions of influent fed into ' + f'{self.N_tanks_in_series} tanks in series with {len(fs)} value(s).') + fs = fs.reshape(1, len(fs)) + elif len(fs.shape) > 2: + raise RuntimeError(f'influent fractions must be a 2d array, with {len(self.ins)} row(s)' + f'and {self.N_tanks_in_series} columns') + if (fs < 0).any(): + raise ValueError('influent fractions must not have negative value(s)') + for row in fs: + rowsum = row.sum() + if rowsum != 1: row /= rowsum + self._f_ins = fs + + @property + def internal_recycles(self): + '''[list[3-tuple[int, int, float]]] A list of three-element tuples (i, j, Q) indicating internal recycling + streams from zone i to zone j with a flowrate of Q [m3/d], respectively. + Indices i,j start from 0 not 1.''' + return self._rcy + @internal_recycles.setter + def internal_recycles(self, rcy): + isa = isinstance + if isa(rcy, tuple): + if len(rcy) != 3: + raise TypeError('internal recycles must be indicated by a list of 3-tuples') + rcy = [rcy] + elif isa(rcy, list): + for row in rcy: + if len(row) != 3: + raise TypeError('internal recycles must be indicated by a list of 3-tuples') + else: + raise TypeError('internal recycles must be indicated by a list of 3-tuples') + _rcy = [] + for row in rcy: + i, j, q = row + _rcy.append((int(i), int(j), q)) + self._rcy = _rcy + + @property + def DO_setpoints(self): + '''[iterable[float]] Dissolve oxygen setpoints of CSTRs in series [mg-O2/L]. + 0 is treated as no active aeration. DO setpoints take priority over kLa values.''' + return self._DOs + @DO_setpoints.setter + def DO_setpoints(self, DOs): + if not iter(DOs): + raise TypeError(f'DO setpoints must be an iterable, not {type(DOs).__name__}') + elif 0 < len(DOs) != self.N_tanks_in_series: + raise RuntimeError(f'cannot set the DO setpoints of {self.N_tanks_in_series} tanks' + f'in series with {len(DOs)} value(s).') + else: self._DOs = np.asarray(DOs) + + @property + def kLa(self): + '''[iterable[float]] Aeration kLa values of CSTRs in series [d^(-1)]. If DO + setpoints are specified, kLa values would be ignored in process simulation.''' + return self._kLa + @kLa.setter + def kLa(self, ks): + if not iter(ks): + raise TypeError(f'V_tanks must be an iterable, not {type(ks).__name__}') + elif len(ks) != self.N_tanks_in_series: + raise RuntimeError(f'cannot set kLa of {self.N_tanks_in_series} tanks' + f'in series with {len(ks)} value(s).') + else: self._kLa = np.asarray(ks) + + @property + def suspended_growth_model(self): + '''[:class:`CompiledProcesses` or NoneType] Suspended growth model.''' + return self._model + + @suspended_growth_model.setter + def suspended_growth_model(self, model): + if isinstance(model, CompiledProcesses) or model is None: self._model = model + else: raise TypeError(f'suspended_growth_model must be one of the following ' + f'types: CompiledProesses, NoneType. Not {type(model)}') + + @property + def DO_ID(self): + '''[str] The `Component` ID for dissolved oxygen used in the suspended growth model and the aeration model.''' + return self._DO_ID + + @DO_ID.setter + def DO_ID(self, doid): + if doid not in self.components.IDs: + raise ValueError(f'DO_ID must be in the set of `CompiledComponents` used to set thermo, ' + f'i.e., one of {self.components.IDs}.') + self._DO_ID = doid + + + def _run(self): + out, = self.outs + out.mix_from(self.ins) + + @property + def state(self): + '''The state of the PFR, including component concentrations [mg/L] and flow rate [m^3/d] for each zone.''' + if self._state is None: return None + else: + N = self.N_tanks_in_series + y = self._state.copy() + y = y.reshape((N, int(len(y)/N))) + y = pd.DataFrame(y, columns=self._state_header) + return y + + def set_init_conc(self, concentrations=None, i_zone=None, **kwargs): + '''set the initial concentrations [mg/L] of specific zones.''' + isa = isinstance + cmps = self.components + N = self.N_tanks_in_series + if self._concs is None: self._concs = np.zeros((N, len(cmps))) + if concentrations is None: + concs = cmps.kwarray(kwargs) + if i_zone is None: + self._concs[:] = concs + else: + self._concs[i_zone] = concs + elif isa(concentrations, pd.DataFrame): + concentrations.index = range(N) + dct = concentrations.to_dict('index') + for i, concs in dct: + self._concs[i] = cmps.kwarray(concs) + elif isa(concentrations, np.ndarray): + if concentrations.shape != self._concs.shape: + raise RuntimeError(f'cannot set the concentrations of {len(cmps)} ' + f'components across {N} with a {concentrations.shape} array') + self._concs = concentrations + else: + raise TypeError('specify initial concentrations with pandas.DataFrame, numpy.ndarray' + 'or " = value" kwargs') + + def _init_state(self): + self._state_header = ny = list(self.components.IDs) + ['Q'] + self._Qs_idx = list(len(ny) * np.arange(1, 1+self.N_tanks_in_series) - 1) + out, = self.outs + y = np.zeros((self.N_tanks_in_series, len(ny))) + if self._concs is not None: + y[:,:-1] = self._concs + else: + y[:,:-1] = out.conc + y[:,-1] = out.F_vol*24 + self._state = y.flatten() + self._dstate = self._state * 0. + + def _update_state(self): + out, = self.outs + n_cmp = len(self.components) + self._state[self._Qs_idx] = self._Qs + out.state[:-1] = self._state[-(n_cmp+1):-1] + out.state[-1] = sum(ws.state[-1] for ws in self.ins) + + def _update_dstate(self): + out, = self.outs + n_cmp = len(self.components) + out.dstate[:-1] = self._dstate[-(n_cmp+1):-1] + out.dstate[-1] = sum(ws.dstate[-1] for ws in self.ins) -# _N_ins = 1 -# _N_outs = 2 + @property + def ODE(self): + if self._ODE is None: + self._compile_ODE() + return self._ODE -# def __init__(self, ID='', ins=None, outs=(), **kwargs): -# SanUnit.__init__(self, ID, ins, outs) + def _compile_ODE(self): + _Qs = self._Qs + _dstate = self._dstate + _update_dstate = self._update_dstate + N = self.N_tanks_in_series + _1_ov_V = np.diag(1/self.V_tanks) + f_in = self.influent_fractions + DO = self.DO_setpoints + kLa = self.kLa + rcy = self.internal_recycles + DO_idx = self.components.index(self.DO_ID) + DOsat = self.DO_sat + Q_internal = np.zeros(N) + for i_from, i_to, qr in rcy: + Q_internal[i_to: i_from] += qr -# def _run(self, steady_state=True): -# pass + if self._model is None: + warn(f'{self.ID} was initialized without a suspended growth model, ' + f'and thus run as a non-reactive unit') + Rs = lambda Cs: 0. + else: + f_rho = self._model.rate_function + M_stoi = self._model.stoichio_eval() + # @njit + def Rs(Cs): + rhos = np.vstack([f_rho(c) for c in Cs]) + rxn = rhos @ M_stoi + return rxn + + if any(DO): + aerated_zones = (DO > 0) + aerated_DO = DO[aerated_zones] + # @njit + def dy_dt(t, QC_ins, QC, dQC_ins): + y = QC.reshape((N, int(len(QC)/N))) + Cs = y[:,:-1] + Cs[aerated_zones, DO_idx] = aerated_DO + _Qs[:] = Qs = np.dot(QC_ins[:,-1], f_in).cumsum() + Q_internal + M_ins = f_in.T @ np.diag(QC_ins[:,-1]) @ QC_ins[:,:-1] # N * n_cmps + M_outs = np.diag(Qs) @ Cs + M_ins[1:] += M_outs[:-1] + for i_from, i_to, qr in rcy: + M_rcy = Cs[i_from] * qr + M_ins[i_to] += M_rcy + if i_from + 1 < N: M_ins[i_from+1] -= M_rcy + rxn = Rs(Cs) + dy = np.zeros_like(y) + dy[:,:-1] = _1_ov_V @ (M_ins - M_outs) + rxn + dy[aerated_zones, DO_idx] = 0. + _dstate[:] = dy.flatten() + _update_dstate() + + else: + if not any(kLa): kLa = np.zeros(N) + # @njit + def dy_dt(t, QC_ins, QC, dQC_ins): + # breakpoint() + y = QC.reshape((N, int(len(QC)/N))) + Cs = y[:,:-1] + do = Cs[:, DO_idx] + aer = kLa*(DOsat-do) + # aer[aer < 0] = 0. + _Qs[:] = Qs = np.dot(QC_ins[:,-1], f_in).cumsum() + Q_internal + M_ins = f_in.T @ np.diag(QC_ins[:,-1]) @ QC_ins[:,:-1] # N * n_cmps + M_outs = np.diag(Qs) @ Cs + M_ins[1:] += M_outs[:-1] + for i_from, i_to, qr in rcy: + M_rcy = Cs[i_from] * qr + M_ins[i_to] += M_rcy + if i_from + 1 < N: M_ins[i_from+1] -= M_rcy + rxn = Rs(Cs) + dy = np.zeros_like(y) + dy[:,:-1] = _1_ov_V @ (M_ins - M_outs) + rxn + dy[:,DO_idx] += aer + _dstate[:] = dy.flatten() + _update_dstate() + + self._ODE = dy_dt -# def _design(self): -# pass \ No newline at end of file + def _design(self): + pass \ No newline at end of file From 5562ef4e51f99c40d92faa7b96e164b4f5252e36 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Thu, 30 May 2024 09:59:09 -0700 Subject: [PATCH 347/424] update `PFR` --- .../sanunits/_suspended_growth_bioreactor.py | 138 +++++++++++++++--- 1 file changed, 120 insertions(+), 18 deletions(-) diff --git a/qsdsan/sanunits/_suspended_growth_bioreactor.py b/qsdsan/sanunits/_suspended_growth_bioreactor.py index 38d1aa18..edfdc34c 100644 --- a/qsdsan/sanunits/_suspended_growth_bioreactor.py +++ b/qsdsan/sanunits/_suspended_growth_bioreactor.py @@ -81,8 +81,8 @@ class CSTR(SanUnit): e.g., temperature, sunlight irradiance. Must be independent of state variables of the suspended_growth_model (if has one). - References: - + References + ---------- [1] Shoener, B. D.; Zhong, C.; Greiner, A. D.; Khunjar, W. O.; Hong, P.-Y.; Guest, J. S. Design of Anaerobic Membrane Bioreactors for the Valorization of Dilute Organic Carbon Waste Streams. @@ -735,6 +735,103 @@ def J_func(t, y): #%% class PFR(SanUnit): + """ + A plug flow reactor discretized into CSTRs in series with internal recycles and multiple influents. + + Parameters + ---------- + N_tanks_in_series : int, optional + The number of CSTRs or zones in which the PFR is discretized. The default is 5. + V_tanks : iterable[float], optional + The volume [m3] for each zone, length must match the number of CSTRs. + The default is [1500, 1500, 3000, 3000, 3000]. + influent_fractions : iterable[float], optional + The volume fractions fed to each zone for each influent. Number of rows + must match the number of influents. Number of columns must match the number + of zones. The default is [[1.0, 0,0,0,0]]. + internal_recycles : list[3-tuple[int, int, float]], optional + A list of three-element tuples (i, j, Q) indicating internal recycling + streams from zone i to zone j with a flowrate of Q [m3/d], respectively. + Indices i,j start from 0 not 1. The default is [(4,0,35000),]. + DO_setpoints : iterable[float], optional + Dissolve oxygen setpoints of each zone [mg-O2/L]. Length must match the + number of zones. 0 is treated as no active aeration. + DO setpoints take priority over kLa values. The default is []. + kLa : iterable[float], optional + Oxygen transfer rate constant values of each zone [d^(-1)]. If DO + setpoints are specified, kLa values would be ignored in process simulation. + The default is [0, 0, 120, 120, 60]. + DO_sat : float, optional + Saturation dissolved oxygen concentration [mg/L]. The default is 8.0. + + Examples + -------- + >>> import qsdsan.sanunits as su, qsdsan.processes as pc + >>> from qsdsan import WasteStream + >>> cmps = pc.create_asm1_cmps() + >>> asm1 = pc.ASM1() + >>> inf = WasteStream('inf', H2O=1.53e6, S_I=46, S_S=54, X_I=1770, X_S=230, + ... X_BH=3870, X_BA=225, X_P=680, S_O=0.377, S_NO=7.98, + ... S_NH=25.6, S_ND=5.87, X_ND=13.4, S_ALK=103) + >>> AS = su.PFR('AS', ins=(inf,), outs=('eff',), V_tanks=[1000]*2+[1333]*3, + ... influent_fractions=[[1.0, 0,0,0,0]], DO_setpoints=[0]*2+[1.7, 2.4, 0.5], + ... internal_recycles=[(4,0,55338)], DO_ID='S_O', + ... suspended_growth_model=asm1) + >>> AS.set_init_conc(S_I=30, S_S=5, X_I=1000, X_S=100, X_BH=500, X_BA=100, + ... X_P=100, S_O=2, S_NO=20, S_NH=2, S_ND=1, X_ND=1, S_ALK=84) + >>> AS.simulate(t_span=(0,100), method='BDF') + >>> eff, = AS.outs + >>> eff.show() + WasteStream: eff from + phase: 'l', T: 298.15 K, P: 101325 Pa + flow (g/hr): S_I 4.6e+04 + S_S 2.24e+03 + X_I 1.77e+06 + X_S 7.59e+04 + X_BH 3.94e+06 + X_BA 2.3e+05 + X_P 6.95e+05 + S_O 770 + S_NO 1.6e+04 + S_NH 2.68e+03 + S_ND 1.06e+03 + X_ND 5.42e+03 + S_ALK 7.65e+04 + S_N2 2.11e+04 + H2O 1.53e+09 + WasteStream-specific properties: + pH : 7.0 + Alkalinity : 2.5 mg/L + COD : 4389.1 mg/L + BOD : 1563.3 mg/L + TC : 1599.8 mg/L + TOC : 1550.1 mg/L + TN : 329.0 mg/L + TP : 68.2 mg/L + TK : 15.1 mg/L + TSS : 3268.3 mg/L + Component concentrations (mg/L): + S_I 29.9 + S_S 1.5 + X_I 1150.0 + X_S 49.3 + X_BH 2557.0 + X_BA 149.5 + X_P 451.9 + S_O 0.5 + S_NO 10.4 + S_NH 1.7 + S_ND 0.7 + X_ND 3.5 + S_ALK 49.7 + S_N2 13.7 + H2O 994140.9 + + See Also + -------- + :class:`qsdsan.sanunits.CSTR` + + """ _N_ins = 1 _N_outs = 1 @@ -746,9 +843,11 @@ def __init__(self, ID='', ins=None, outs=(), thermo=None, init_with='WasteStream influent_fractions=[[1.0, 0,0,0,0]], internal_recycles=[(4,0,35000),], DO_setpoints=[], kLa=[0, 0, 120, 120, 60], DO_sat=8.0, DO_ID='S_O2', suspended_growth_model=None, - isdynamic=True, exogenous_vars=(), **kwargs): + isdynamic=True, **kwargs): + + exogenous_vars = kwargs.pop('exogenous_vars', None) if exogenous_vars: - warn(f'currently exogenous dynamic variables are not supported in process simulation for {self.ID}') + warn('currently exogenous dynamic variables are not supported in process simulation for PFR') SanUnit.__init__(self, ID, ins, outs, thermo, init_with, isdynamic=isdynamic, exogenous_vars=exogenous_vars, **kwargs) self.N_tanks_in_series = N_tanks_in_series @@ -920,10 +1019,11 @@ def set_init_conc(self, concentrations=None, i_zone=None, **kwargs): 'or " = value" kwargs') def _init_state(self): - self._state_header = ny = list(self.components.IDs) + ['Q'] - self._Qs_idx = list(len(ny) * np.arange(1, 1+self.N_tanks_in_series) - 1) + self._state_header = header = list(self.components.IDs) + ['Q'] + self._ncol = ncol = len(header) + self._Qs_idx = list(ncol * np.arange(1, 1+self.N_tanks_in_series) - 1) out, = self.outs - y = np.zeros((self.N_tanks_in_series, len(ny))) + y = np.zeros((self.N_tanks_in_series, ncol)) if self._concs is not None: y[:,:-1] = self._concs else: @@ -934,15 +1034,17 @@ def _init_state(self): def _update_state(self): out, = self.outs - n_cmp = len(self.components) + ncol = self._ncol self._state[self._Qs_idx] = self._Qs - out.state[:-1] = self._state[-(n_cmp+1):-1] + if out.state is None: out.state = np.zeros(ncol) + out.state[:-1] = self._state[-ncol:-1] out.state[-1] = sum(ws.state[-1] for ws in self.ins) def _update_dstate(self): out, = self.outs - n_cmp = len(self.components) - out.dstate[:-1] = self._dstate[-(n_cmp+1):-1] + ncol = self._ncol + if out.dstate is None: out.dstate = np.zeros(ncol) + out.dstate[:-1] = self._dstate[-ncol:-1] out.dstate[-1] = sum(ws.dstate[-1] for ws in self.ins) @property @@ -956,6 +1058,7 @@ def _compile_ODE(self): _dstate = self._dstate _update_dstate = self._update_dstate N = self.N_tanks_in_series + ncol = self._ncol _1_ov_V = np.diag(1/self.V_tanks) f_in = self.influent_fractions DO = self.DO_setpoints @@ -965,7 +1068,7 @@ def _compile_ODE(self): DOsat = self.DO_sat Q_internal = np.zeros(N) for i_from, i_to, qr in rcy: - Q_internal[i_to: i_from] += qr + Q_internal[i_to: i_from+1] += qr if self._model is None: warn(f'{self.ID} was initialized without a suspended growth model, ' @@ -976,7 +1079,8 @@ def _compile_ODE(self): M_stoi = self._model.stoichio_eval() # @njit def Rs(Cs): - rhos = np.vstack([f_rho(c) for c in Cs]) + # rhos = np.vstack([f_rho(c) for c in Cs]) + rhos = np.apply_along_axis(f_rho, 1, Cs) # n_zone * n_process rxn = rhos @ M_stoi return rxn @@ -985,7 +1089,7 @@ def Rs(Cs): aerated_DO = DO[aerated_zones] # @njit def dy_dt(t, QC_ins, QC, dQC_ins): - y = QC.reshape((N, int(len(QC)/N))) + y = QC.reshape((N, ncol)) Cs = y[:,:-1] Cs[aerated_zones, DO_idx] = aerated_DO _Qs[:] = Qs = np.dot(QC_ins[:,-1], f_in).cumsum() + Q_internal @@ -1007,12 +1111,10 @@ def dy_dt(t, QC_ins, QC, dQC_ins): if not any(kLa): kLa = np.zeros(N) # @njit def dy_dt(t, QC_ins, QC, dQC_ins): - # breakpoint() - y = QC.reshape((N, int(len(QC)/N))) - Cs = y[:,:-1] + y = QC.reshape((N, ncol)) + Cs = y[:,:-1] do = Cs[:, DO_idx] aer = kLa*(DOsat-do) - # aer[aer < 0] = 0. _Qs[:] = Qs = np.dot(QC_ins[:,-1], f_in).cumsum() + Q_internal M_ins = f_in.T @ np.diag(QC_ins[:,-1]) @ QC_ins[:,:-1] # N * n_cmps M_outs = np.diag(Qs) @ Cs From 2e12c9a8cc62aa01a4bc1e9b0b47dfe0b0acb474 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Thu, 30 May 2024 10:32:45 -0700 Subject: [PATCH 348/424] Delete _pfr.py --- qsdsan/sanunits/_pfr.py | 80 ----------------------------------------- 1 file changed, 80 deletions(-) delete mode 100644 qsdsan/sanunits/_pfr.py diff --git a/qsdsan/sanunits/_pfr.py b/qsdsan/sanunits/_pfr.py deleted file mode 100644 index 333dc601..00000000 --- a/qsdsan/sanunits/_pfr.py +++ /dev/null @@ -1,80 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Fri May 24 08:18:43 2024 - -@author: joy_c -""" -import numpy as np -from numba import njit, cfunc - - -N = 6 -V = np.array([249, 2313, 2366, 2366, 4982, 4982]) -denom = np.diag(1/V) -f_in = np.array([[0, 0.8, 0.2, 0,0,0], - [1, 0, 0, 0, 0, 0]]) -internal_recycles = [(5, 2, 1.5e5)] # from, to, Q -Q_internal = np.zeros(N) -for i_from, i_to, qr in internal_recycles: - Q_internal[i_to: i_from] += qr - -DO = [0,0,0,0,2.,2.] -kLa = [0]*6 -DOsat = 8.0 - -#%% -from exposan import bsm1 -bsm1.load() -cmps = bsm1.PE.components -asm = bsm1.A1.suspended_growth_model -M_stoi = asm.stoichio_eval() -f_rho = asm.rate_function -DO_id = cmps.index('S_O') - -#%% -@njit -def dydt(t, QC_ins, QC, dQC_ins=None): - y = QC.reshape((N, len(QC)/N)) - Cs = y[:,:-1] - if any(DO): - for i in range(N): - if DO[i] > 0: Cs[i, DO_id] = DO[i] - elif any(kLa): - do = Cs[:, DO_id] - aer = kLa*(DOsat-do) - aer[aer < 0] = 0. - Qs = np.dot(QC_ins[:,-1], f_in).cumsum() + Q_internal - M_ins = f_in.T @ np.diag(QC_ins[:,-1]) @ QC_ins[:,:-1] # N * n_cmps - for i_from, i_to, qr in internal_recycles: - M_ins[i_to] += Cs[i_from] * qr - # rhos = np.apply_along_axis(f_rho, 1, Cs) # n_zone * n_process - rhos = np.vstack([f_rho(c) for c in Cs]) - rxn = rhos @ M_stoi - dy = np.zeros_like(y) - dy[:,:-1] = denom @ (M_ins - np.diag(Qs) @ Cs) + rxn - if any(DO): dy[:,DO_id] = 0. - elif any(kLa): dy[:,DO_id] += aer - return dy.flatten() - -#%% -import qsdsan.sanunits as su -from qsdsan import System -s = bsm1.sys.flowsheet.stream -u = bsm1.sys.flowsheet.unit -inf = s.wastewater -inf.unlink() -ras = s.RAS -ras.unlink() -s.treated.unlink() -AS = su.PFR('AS', ins=[inf, ras], outs=0-u.C1, V_tanks=[1000]*2+[1333]*3, - influent_fractions=[[1.0, 0,0,0,0], [1.0, 0,0,0,0]], - internal_recycles=[(4,0,55338)], kLa=[0,0,240,240,84], DO_ID='S_O', - suspended_growth_model=u.A1._model) -AS.set_init_conc( - S_I=30, S_S=5, X_I=1000, X_S=100, X_BH=500, X_BA=100, X_P=100, S_O=2, S_NO=20, - S_NH=2, S_ND=1, X_ND=1, S_ALK=84 - ) -sys = System('sys', path=(AS, u.C1)) - -#%% -sys.simulate(t_span=(0,50), method='BDF') From 5bcc76c41b8bef0b2100acee5ffdf90f31c44fba Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Thu, 30 May 2024 11:19:04 -0700 Subject: [PATCH 349/424] update state headers for `PFR`, `AnaerobicCSTR` --- qsdsan/sanunits/_anaerobic_reactor.py | 8 +++---- .../sanunits/_suspended_growth_bioreactor.py | 21 ++++++++++++------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/qsdsan/sanunits/_anaerobic_reactor.py b/qsdsan/sanunits/_anaerobic_reactor.py index 06d47d20..3d17ebf6 100644 --- a/qsdsan/sanunits/_anaerobic_reactor.py +++ b/qsdsan/sanunits/_anaerobic_reactor.py @@ -343,13 +343,13 @@ def model(self, model): if model is not None: #!!! how to make unit conversion generalizable to all models? self._S_vapor = self.ideal_gas_law(p=self.p_vapor()) - self._n_gas = len(model._biogas_IDs) - self._state_keys = list(self.components.IDs) \ + self._n_gas = ng = len(model._biogas_IDs) + self._state_keys = keys = list(self.components.IDs) \ + [ID+'_gas' for ID in self.model._biogas_IDs] \ + ['Q'] self._gas_cmp_idx = self.components.indices(self.model._biogas_IDs) - self._state_header = self._state_keys - + units = ['kg/m3']*len(self.components) + ['M']*ng + ['m3/d'] + self._state_header = [f'{name} [{unit}]' for name, unit in zip(keys, units)] split = property(CSTR.split.fget) @split.setter diff --git a/qsdsan/sanunits/_suspended_growth_bioreactor.py b/qsdsan/sanunits/_suspended_growth_bioreactor.py index edfdc34c..b517c62c 100644 --- a/qsdsan/sanunits/_suspended_growth_bioreactor.py +++ b/qsdsan/sanunits/_suspended_growth_bioreactor.py @@ -963,7 +963,17 @@ def suspended_growth_model(self, model): if isinstance(model, CompiledProcesses) or model is None: self._model = model else: raise TypeError(f'suspended_growth_model must be one of the following ' f'types: CompiledProesses, NoneType. Not {type(model)}') - + if model is not None: + self._state_keys = keys = list(self.components.IDs) + ['Q'] + self._ncol = ncol = len(keys) + N = self.N_tanks_in_series + self._Qs_idx = list(ncol * np.arange(1, 1+N) - 1) + names = [f'{i} [mg/L]' for i in self.components.IDs] + ['Q [m3/d]'] + names *= N + zones = [[f'zone {i}']*ncol for i in range(N)] + zones = sum(zones, []) + self._state_header = [f'{z} {n}' for z,n in zip(zones, names)] + @property def DO_ID(self): '''[str] The `Component` ID for dissolved oxygen used in the suspended growth model and the aeration model.''' @@ -988,8 +998,8 @@ def state(self): else: N = self.N_tanks_in_series y = self._state.copy() - y = y.reshape((N, int(len(y)/N))) - y = pd.DataFrame(y, columns=self._state_header) + y = y.reshape((N, self._ncol)) + y = pd.DataFrame(y, columns=self._state_keys) return y def set_init_conc(self, concentrations=None, i_zone=None, **kwargs): @@ -1019,11 +1029,8 @@ def set_init_conc(self, concentrations=None, i_zone=None, **kwargs): 'or " = value" kwargs') def _init_state(self): - self._state_header = header = list(self.components.IDs) + ['Q'] - self._ncol = ncol = len(header) - self._Qs_idx = list(ncol * np.arange(1, 1+self.N_tanks_in_series) - 1) out, = self.outs - y = np.zeros((self.N_tanks_in_series, ncol)) + y = np.zeros((self.N_tanks_in_series, self._ncol)) if self._concs is not None: y[:,:-1] = self._concs else: From 07bc7376a2ed1c6d4f3f83ecdc8e4617ce3ae4f8 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Thu, 30 May 2024 11:48:08 -0700 Subject: [PATCH 350/424] minor bug fix --- qsdsan/sanunits/_suspended_growth_bioreactor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qsdsan/sanunits/_suspended_growth_bioreactor.py b/qsdsan/sanunits/_suspended_growth_bioreactor.py index b517c62c..6b612d5e 100644 --- a/qsdsan/sanunits/_suspended_growth_bioreactor.py +++ b/qsdsan/sanunits/_suspended_growth_bioreactor.py @@ -1017,7 +1017,7 @@ def set_init_conc(self, concentrations=None, i_zone=None, **kwargs): elif isa(concentrations, pd.DataFrame): concentrations.index = range(N) dct = concentrations.to_dict('index') - for i, concs in dct: + for i, concs in dct.items(): self._concs[i] = cmps.kwarray(concs) elif isa(concentrations, np.ndarray): if concentrations.shape != self._concs.shape: From 7c243316d510049edfa49b02f50d302a77da859c Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Thu, 30 May 2024 14:12:46 -0700 Subject: [PATCH 351/424] fix kLa setting in `PFR` --- .../sanunits/_suspended_growth_bioreactor.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/qsdsan/sanunits/_suspended_growth_bioreactor.py b/qsdsan/sanunits/_suspended_growth_bioreactor.py index 6b612d5e..926620f1 100644 --- a/qsdsan/sanunits/_suspended_growth_bioreactor.py +++ b/qsdsan/sanunits/_suspended_growth_bioreactor.py @@ -946,12 +946,18 @@ def kLa(self): return self._kLa @kLa.setter def kLa(self, ks): - if not iter(ks): - raise TypeError(f'V_tanks must be an iterable, not {type(ks).__name__}') - elif len(ks) != self.N_tanks_in_series: - raise RuntimeError(f'cannot set kLa of {self.N_tanks_in_series} tanks' - f'in series with {len(ks)} value(s).') - else: self._kLa = np.asarray(ks) + if any(self._DOs): + if ks != []: + warn('kLa is ignored because DO setpoints have been specified. ' + 'To specify kLa, first set DO_setpoints as []') + self._kLa = [] + else: + if not iter(ks): + raise TypeError(f'V_tanks must be an iterable, not {type(ks).__name__}') + elif len(ks) != self.N_tanks_in_series: + raise RuntimeError(f'cannot set kLa of {self.N_tanks_in_series} tanks' + f'in series with {len(ks)} value(s).') + else: self._kLa = np.asarray(ks) @property def suspended_growth_model(self): @@ -986,7 +992,6 @@ def DO_ID(self, doid): f'i.e., one of {self.components.IDs}.') self._DO_ID = doid - def _run(self): out, = self.outs out.mix_from(self.ins) From bef8afdd7d31f53bd0215235fd2ed36de19d6412 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Thu, 30 May 2024 14:34:21 -0700 Subject: [PATCH 352/424] minor fix of doctest --- qsdsan/sanunits/_suspended_growth_bioreactor.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/qsdsan/sanunits/_suspended_growth_bioreactor.py b/qsdsan/sanunits/_suspended_growth_bioreactor.py index 926620f1..d2126e75 100644 --- a/qsdsan/sanunits/_suspended_growth_bioreactor.py +++ b/qsdsan/sanunits/_suspended_growth_bioreactor.py @@ -773,10 +773,11 @@ class PFR(SanUnit): >>> inf = WasteStream('inf', H2O=1.53e6, S_I=46, S_S=54, X_I=1770, X_S=230, ... X_BH=3870, X_BA=225, X_P=680, S_O=0.377, S_NO=7.98, ... S_NH=25.6, S_ND=5.87, X_ND=13.4, S_ALK=103) - >>> AS = su.PFR('AS', ins=(inf,), outs=('eff',), V_tanks=[1000]*2+[1333]*3, - ... influent_fractions=[[1.0, 0,0,0,0]], DO_setpoints=[0]*2+[1.7, 2.4, 0.5], - ... internal_recycles=[(4,0,55338)], DO_ID='S_O', - ... suspended_growth_model=asm1) + >>> AS = su.PFR('AS', ins=(inf,), outs=('eff',), + ... N_tanks_in_series=5, V_tanks=[1000]*2+[1333]*3, + ... influent_fractions=[[1.0, 0,0,0,0]], DO_setpoints=[0]*2+[1.7, 2.4, 0.5], + ... internal_recycles=[(4,0,55338)], DO_ID='S_O', + ... suspended_growth_model=asm1) >>> AS.set_init_conc(S_I=30, S_S=5, X_I=1000, X_S=100, X_BH=500, X_BA=100, ... X_P=100, S_O=2, S_NO=20, S_NH=2, S_ND=1, X_ND=1, S_ALK=84) >>> AS.simulate(t_span=(0,100), method='BDF') From 57954af323b06ea8e0bebe40ba120c10748acca5 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Fri, 31 May 2024 09:25:16 -0700 Subject: [PATCH 353/424] fix mADM1-ASM2d charge balances --- qsdsan/sanunits/_junction.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/qsdsan/sanunits/_junction.py b/qsdsan/sanunits/_junction.py index 29110f42..80b3a6ef 100644 --- a/qsdsan/sanunits/_junction.py +++ b/qsdsan/sanunits/_junction.py @@ -1985,7 +1985,7 @@ def madm12asm2d(adm_vals): # Step 0: snapshot of charged components # Not sure about charge on X_PP, S_Mg, S_K (PHA and PAO would have zero charge) - # X_PP is charge-neutral + # X_PP in ADM1 is charge neutral _ions = np.array([S_IN, S_IC, S_IP, S_Mg, S_K, S_ac, S_pro, S_bu, S_va]) # Step 1a: convert biomass and inert particulates into X_S and X_I @@ -2322,9 +2322,11 @@ def madm12asm2d(adm_vals): # _ions = np.array([S_IN, S_IC, S_IP, S_Mg, S_K, S_ac, S_pro, S_bu, S_va]) - adm_alphas = np.array([self.alpha_IN, self.alpha_IC, self.alpha_IP, 2, 1, *self.alpha_vfa]) + adm_alphas = np.array([self.alpha_IN, self.alpha_IC, self.alpha_IP, + 2/24, 1/39, *self.alpha_vfa]) #!!! should be in unit of charge per g adm_charge = np.dot(_ions, adm_alphas) - S_ALK = (adm_charge - (S_NH4/14 - S_A/64 - S_NO3/14 - 1.5*S_PO4/31))*(-12) + #!!! X_PP in ASM2d has negative charge, to compensate for the absent variables S_K & S_Mg + S_ALK = (adm_charge - (S_NH4/14 - S_A/64 - S_NO3/14 - 1.5*S_PO4/31 - X_PP/31))*(-12) asm_vals[asm_ions_idx[-1]] = S_ALK @@ -2662,7 +2664,7 @@ def asm2d2madm1(asm_vals): _salk = S_ALK _spo4 = S_PO4 _sa = S_A - # _xpp = X_PP + _xpp = X_PP # Step 1: remove any remaining COD demand O2_coddm = S_O2 @@ -3063,16 +3065,13 @@ def asm2d2madm1(asm_vals): adm_vals = f_corr(asm_vals, adm_vals) # Step 7: charge balance - asm_charge = - _sa/64 + _snh4/14 - _sno3/14 - 1.5*_spo4/31 - _salk# - _xpp/31 #Based on page 84 of IWA ASM handbook + asm_charge = - _sa/64 + _snh4/14 - _sno3/14 - 1.5*_spo4/31 - _salk - _xpp/31 #Based on page 84 of IWA ASM handbook + S_IN, S_IP = adm_vals[adm_ions_idx[:2]] #!!! charge balance should technically include VFAs, S_K, S_Mg, # but since their concentrations are assumed zero it is acceptable. - - S_IN, S_IP = adm_vals[adm_ions_idx[:2]] - S_IC = (asm_charge - S_IN*self.alpha_IN - S_IP*self.alpha_IP)/self.alpha_IC proton_charge = 10**(-self.pKa[0]+self.pH) - 10**(-self.pH) # self.pKa[0] is pKw - # proton_charge = (OH)^-1 - (H)^+1 # net_Scat = Scat - San net_Scat = asm_charge + proton_charge From 8262234594b019df712b6998de264fde7893da2f Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Mon, 3 Jun 2024 10:55:03 -0700 Subject: [PATCH 354/424] debug algebraic dissolved H2 solver --- qsdsan/processes/_adm1.py | 14 ++++---- qsdsan/processes/_adm1_p_extension.py | 50 ++++++++++++++++++++++++--- qsdsan/sanunits/_anaerobic_reactor.py | 6 ++-- 3 files changed, 58 insertions(+), 12 deletions(-) diff --git a/qsdsan/processes/_adm1.py b/qsdsan/processes/_adm1.py index fb940b13..84f8a185 100644 --- a/qsdsan/processes/_adm1.py +++ b/qsdsan/processes/_adm1.py @@ -23,12 +23,11 @@ from warnings import warn __all__ = ('create_adm1_cmps', 'ADM1', - 'non_compet_inhibit', 'substr_inhibit', + 'non_compet_inhibit', 'grad_non_compet_inhibit', + 'substr_inhibit', 'grad_substr_inhibit', 'mass2mol_conversion', 'T_correction_factor', 'pH_inhibit', 'Hill_inhibit', - 'rhos_adm1', - 'solve_pH', 'TempState', - 'dydt_Sh2_AD', 'grad_dydt_Sh2_AD') + 'rhos_adm1', 'TempState',) _path = ospath.join(data_path, 'process_data/_adm1.tsv') _load_components = settings.get_default_chemicals @@ -356,8 +355,8 @@ def grad_dydt_Sh2_AD(S_h2, state_arr, h, params, f_stoichio, V_liq, S_h2_in): grad_rhos[:] = ks * X_bio * Iph * Iin grad_rhos[:-1] *= substr_inhibit(substrates, Ks) * grad_Ih2 - if S_va > 0: rhos[1] *= 1/(1+S_bu/S_va) - if S_bu > 0: rhos[2] *= 1/(1+S_va/S_bu) + if S_va > 0: grad_rhos[1] *= 1/(1+S_bu/S_va) + if S_bu > 0: grad_rhos[2] *= 1/(1+S_va/S_bu) grad_rhos[-1] *= grad_substr_inhibit(S_h2, K_h2) stoichio = f_stoichio(state_arr) @@ -663,6 +662,9 @@ def __new__(cls, components=None, path=None, N_xc=2.686e-3, N_I=4.286e-3, N_aa=7 K_H_base, K_H_dH, kLa, T_base, self._components, root])) + dct['solve_pH'] = solve_pH + dct['dydt_Sh2_AD'] = dydt_Sh2_AD + dct['grad_dydt_Sh2_AD'] = grad_dydt_Sh2_AD return self def set_pKas(self, pKas): diff --git a/qsdsan/processes/_adm1_p_extension.py b/qsdsan/processes/_adm1_p_extension.py index 5c6d757c..8db65e1c 100644 --- a/qsdsan/processes/_adm1_p_extension.py +++ b/qsdsan/processes/_adm1_p_extension.py @@ -24,12 +24,13 @@ create_asm2d_cmps, T_correction_factor, non_compet_inhibit, + grad_non_compet_inhibit, substr_inhibit, + grad_substr_inhibit, mass2mol_conversion, Hill_inhibit, ADM1, TempState - # _rhos_adm1, ) from qsdsan.utils import ospath, data_path from scipy.optimize import brenth @@ -129,9 +130,9 @@ def solve_pH(state_arr, Ka, unit_conversion): co2 = weak_acids[6] - Ka[3] * weak_acids[6] / (Ka[3] + h) return h, nh3, co2 -rhos_adm1_p_extension = lambda state_arr, params: _rhos_adm1_p_extension(state_arr, params, h=None) +rhos_adm1_p_extension = lambda state_arr, params: _rhos_adm1p(state_arr, params, h=None) -def _rhos_adm1_p_extension(state_arr, params, h=None): +def _rhos_adm1p(state_arr, params, h=None): ks = params['rate_constants'] Ks = params['half_sat_coeffs'] @@ -257,6 +258,45 @@ def _rhos_adm1_p_extension(state_arr, params, h=None): # print(rhos) return rhos + +def dydt_Sh2_AD(S_h2, state_arr, h, params, f_stoichio, V_liq, S_h2_in): + state_arr[7] = S_h2 + Q = state_arr[37] + rxn = _rhos_adm1p(state_arr, params, h=h) + stoichio = f_stoichio(state_arr) # should return the stoichiometric coefficients of S_h2 for all processes + return Q/V_liq*(S_h2_in - S_h2) + np.dot(rxn, stoichio) + +grad_rhos = np.zeros(5) +X_bio = np.zeros(5) +def grad_dydt_Sh2_AD(S_h2, state_arr, h, params, f_stoichio, V_liq, S_h2_in): + state_arr[7] = S_h2 + ks = params['rate_constants'][[5,6,7,8,10]] + Ks = params['half_sat_coeffs'][2:6] + K_h2 = params['half_sat_coeffs'][7] + pH_ULs = params['pH_ULs'] + pH_LLs = params['pH_LLs'] + KS_IN = params['KS_IN'] + KIs_h2 = params['KIs_h2'] + kLa = params['kLa'] + + X_bio[:] = state_arr[[18,19,19,20,22]] + substrates = state_arr[2:6] + S_va, S_bu, S_IN = state_arr[[3,4,10]] + Iph = Hill_inhibit(h, pH_ULs, pH_LLs)[[2,3,4,5,7]] + Iin = substr_inhibit(S_IN, KS_IN) + grad_Ih2 = grad_non_compet_inhibit(S_h2, KIs_h2) + + grad_rhos[:] = ks * X_bio * Iph * Iin + grad_rhos[:-1] *= substr_inhibit(substrates, Ks) * grad_Ih2 + if S_va > 0: grad_rhos[1] *= 1/(1+S_bu/S_va) + if S_bu > 0: grad_rhos[2] *= 1/(1+S_va/S_bu) + + grad_rhos[-1] *= grad_substr_inhibit(S_h2, K_h2) + stoichio = f_stoichio(state_arr) + + Q = state_arr[37] + return -Q/V_liq + np.dot(grad_rhos, stoichio[[5,6,7,8,10]]) + kLa*stoichio[-3] + #%% # ============================================================================= # ADM1_p_extension class @@ -443,7 +483,9 @@ def __new__(cls, components=None, path=None, N_xc=2.686e-3, N_I=4.286e-3, N_aa=7 T_base, self._components, root, #!!! new parameter KS_IP*P_mw])) - + dct['solve_pH'] = solve_pH + dct['dydt_Sh2_AD'] = dydt_Sh2_AD + dct['grad_dydt_Sh2_AD'] = grad_dydt_Sh2_AD return self def set_half_sat_K(self, K, process): diff --git a/qsdsan/sanunits/_anaerobic_reactor.py b/qsdsan/sanunits/_anaerobic_reactor.py index 3d17ebf6..f291d498 100644 --- a/qsdsan/sanunits/_anaerobic_reactor.py +++ b/qsdsan/sanunits/_anaerobic_reactor.py @@ -18,8 +18,7 @@ from math import ceil, pi from biosteam import Stream from .. import SanUnit, Construction, WasteStream -from ..processes import Decay, T_correction_factor, solve_pH, \ - dydt_Sh2_AD, grad_dydt_Sh2_AD +from ..processes import Decay, T_correction_factor from ..sanunits import HXutility, WWTpump, CSTR from ..utils import ospath, load_data, data_path, auom, \ calculate_excavation_volume, ExogenousDynamicVariable as EDV @@ -567,6 +566,9 @@ def h2_stoichio(state_arr): _h2_stoichio = _M_stoichio[h2_idx] h2_stoichio = lambda state_arr: _h2_stoichio unit_conversion = cmps.i_mass / cmps.chem_MW + solve_pH = self.model.solve_pH + dydt_Sh2_AD = self.model.dydt_Sh2_AD + grad_dydt_Sh2_AD = self.model.grad_dydt_Sh2_AD def solve_h2(QC, S_in, T): Ka = params['Ka_base'] * T_correction_factor(params['T_base'], T, params['Ka_dH']) h, nh3, co2 = solve_pH(QC, Ka, unit_conversion) From 12f5622a5653006f9d8e66bb634a9a953bdb6640 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Mon, 3 Jun 2024 10:57:23 -0700 Subject: [PATCH 355/424] default AnaerobicCSTR back to ODE for h2 --- qsdsan/sanunits/_anaerobic_reactor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qsdsan/sanunits/_anaerobic_reactor.py b/qsdsan/sanunits/_anaerobic_reactor.py index f291d498..df356767 100644 --- a/qsdsan/sanunits/_anaerobic_reactor.py +++ b/qsdsan/sanunits/_anaerobic_reactor.py @@ -263,7 +263,7 @@ class AnaerobicCSTR(CSTR): _ins_size_is_fixed = False _outs_size_is_fixed = False _R = 8.3145e-2 # Universal gas constant, [bar/M/K] - algebraic_h2 = True + algebraic_h2 = False def __init__(self, ID='', ins=None, outs=(), thermo=None, init_with='WasteStream', V_liq=3400, V_gas=300, model=None, From 95b33b272536db44e8f8907d98a6cc8a73bfe1d8 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Wed, 5 Jun 2024 09:37:44 -0700 Subject: [PATCH 356/424] add modified ASM2d --- qsdsan/data/_masm2d.xlsx | Bin 0 -> 18185 bytes qsdsan/processes/_adm1_p_extension.py | 68 ++-- qsdsan/processes/_asm2d.py | 447 +++++++++++++++++--------- 3 files changed, 335 insertions(+), 180 deletions(-) create mode 100644 qsdsan/data/_masm2d.xlsx diff --git a/qsdsan/data/_masm2d.xlsx b/qsdsan/data/_masm2d.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..2ce17ae9eea3be8004b4954b878104df044eae61 GIT binary patch literal 18185 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 X_PP + [?]S_N2 + [?]S_NH4 + [?]S_ALK', -# ref_component='X_PP', -# rate_equation='q_PP * S_O2/(K_O2_PAO+S_O2) * S_PO4/(K_PS+S_PO4) * S_ALK/(K_ALK_PAO+S_ALK) * (X_PHA/X_PAO)/(K_PHA+X_PHA/X_PAO) * (K_MAX-X_PP/X_PAO)/(K_IPP+K_MAX-X_PP/X_PAO) * X_PAO * eta_NO3_PAO * K_O2_PAO/S_O2 * S_NO3/(K_NO3_PAO+S_NO3)', -# parameters=('Y_PHA', 'q_PP', 'K_O2_PAO', 'K_PS', 'K_ALK_PAO', 'K_PHA', 'eta_NO3_PAO', 'K_IPP', 'K_NO3_PAO'), -# conserved_for=('COD', 'N', 'P', 'NOD', 'charge')) - -# p14 = Process('PAO_anox_growth', -# '[1/Y_PAO]X_PHA + [?]S_NO3 + [?]S_PO4 -> X_PAO + [?]S_N2 + [?]S_NH4 + [?]S_ALK', -# ref_component='X_PAO', -# rate_equation='mu_PAO * S_O2/(K_O2_PAO + S_O2) * S_NH4/(K_NH4_PAO + S_NH4) * S_PO4/(K_P_PAO + S_PO4) * S_ALK/(K_ALK_PAO + S_ALK) * (X_PHA/X_PAO)/(K_PHA + X_PHA/X_PAO) * X_PAO * eta_NO3_PAO * K_O2_PAO/S_O2 * S_NO3/(K_NO3_PAO + S_NO3)', -# parameters=('Y_PAO', 'mu_PAO', 'K_O2_PAO', 'K_NH4_PAO', 'K_P_PAO', 'K_ALK_PAO', 'K_PHA', 'eta_NO3_PAO', 'K_NO3_PAO'), -# conserved_for=('COD', 'N', 'P', 'NOD', 'charge')) - -# asm2d.extend([p12, p14]) -# asm2d.compile() - -# # ASM2d typical values at 20 degree C -# asm2d.set_parameters( -# f_SI = 0, # production of soluble inerts in hydrolysis = 0.0 gCOD/gCOD -# Y_H = 0.625, # heterotrophic yield = 0.625 gCOD/gCOD -# f_XI_H=0.1, # fraction of inert COD generated in heterotrophic biomass lysis = 0.1 gCOD/gCOD -# Y_PAO = 0.625, # PAO yield = 0.625 gCOD/gCOD -# Y_PO4 = 0.4, # PP requirement (PO4 release) per PHA stored = 0.4 gP/gCOD -# Y_PHA = 0.2, # PHA requirement for PP storage = 0.2 gCOD/gP -# f_XI_PAO=0.1, # fraction of inert COD generated in PAO biomass lysis = 0.1 gCOD/gCOD -# Y_A = 0.24, # autotrophic yield = 0.24 gCOD/gN -# f_XI_AUT=0.1, # fraction of inert COD generated in autotrophic biomass lysis = 0.1 gCOD/gCOD -# K_h = 3, # hydrolysis rate constant = 3.0 d^(-1) -# eta_NO3 = 0.6, # reduction factor for anoxic hydrolysis = 0.6 -# eta_fe = 0.4, # anaerobic hydrolysis reduction factor = 0.4 -# K_O2 = 0.2, # O2 half saturation coefficient of hydrolysis = 0.2 mgO2/L -# K_NO3 = 0.5, # nitrate half saturation coefficient of hydrolysis = 0.5 mgN/L -# K_X = 0.1, # slowly biodegradable substrate half saturation coefficient for hydrolysis = 0.1 gCOD/gCOD -# mu_H = 6, # heterotrophic maximum specific growth rate = 6.0 d^(-1) -# q_fe = 3, # fermentation maximum rate = 3.0 d^(-1) -# eta_NO3_H = 0.8, # denitrification reduction factor for heterotrophic growth = 0.8 -# b_H = 0.4, # lysis and decay rate constant = 0.4 d^(-1) -# K_O2_H=0.2, # O2 half saturation coefficient of heterotrophs = 0.2 mgO2/L -# K_F = 4, # fermentable substrate half saturation coefficient for heterotrophic growth = 4.0 mgCOD/L -# K_fe = 4, # fermentable substrate half saturation coefficient for fermentation = 4.0 mgCOD/L -# K_A_H = 4, # VFA half saturation coefficient for heterotrophs = 4.0 mgCOD/L -# K_NO3_H=0.5, # nitrate half saturation coefficient = 0.5 mgN/L -# K_NH4_H = 0.05, # ammonium (nutrient) half saturation coefficient for heterotrophs = 0.05 mgN/L -# K_P_H = 0.01, # phosphorus (nutrient) half saturation coefficient for heterotrophs = 0.01 mgP/L -# K_ALK_H = 0.1*12, # alkalinity half saturation coefficient for heterotrophs = 0.1 mol(HCO3-)/m^3 = 1.2 gC/m^3 -# q_PHA = 3, # rate constant for storage of PHA = 3.0 d^(-1) -# q_PP = 1.5, # rate constant for storage of PP = 1.5 d^(-1) -# mu_PAO = 1, # PAO maximum specific growth rate = 1.0 d^(-1) -# eta_NO3_PAO=0.6, # denitrification reduction factor for PAO growth = 0.8 -# b_PAO = 0.2, # PAO lysis rate = 0.2 d^(-1) -# b_PP = 0.2, # PP lysis rate = 0.2 d^(-1) -# b_PHA = 0.2, # PHA lysis rate = 0.2 d^(-1) -# K_O2_PAO=0.2, # O2 half saturation coefficient for PAOs = 0.2 mgO2/L -# K_NO3_PAO=0.5, # nitrate half saturation coefficient for PAOs = 0.5 mgN/L -# K_A_PAO=4.0, # VFA half saturation coefficient for PAOs = 4.0 mgCOD/L -# K_NH4_PAO=0.05, # ammonium (nutrient) half saturation coefficient for PAOs = 0.05 mgN/L -# K_PS = 0.2, # phosphorus half saturation coefficient for storage of PP = 0.2 mgP/L -# K_P_PAO=0.01, # phosphorus (nutrient) half saturation coefficient for heterotrophs = 0.01 mgP/L -# K_ALK_PAO=0.1*12, # alkalinity half saturation coefficient for PAOs = 0.1 mol(HCO3-)/m^3 = 1.2 gC/m^3 -# K_PP = 0.01, # PP half saturation coefficient for storage of PHA = 0.01 gP/gCOD (?). gCOD/gCOD in GPS-X -# K_MAX = 0.34, # maximum ratio of X_PP/X_PAO = 0.34 gX_PP/gX_PAO -# K_IPP = 0.02, # inhibition coefficient for PP storage = 0.02 gP/gCOD -# K_PHA = 0.01, # PHA half saturation coefficient = 0.01 gCOD/gCOD -# mu_AUT = 1, # autotrophic maximum specific growth rate = 1.0 d^(-1) -# b_AUT = 0.15, # autotrophic decay rate = 0.15 d^(-1) -# K_O2_AUT = 0.5, # O2 half saturation coefficient for autotrophic growth = 0.5 mgO2/L -# K_NH4_AUT = 1, # ammonium (substrate) half saturation coefficient for autotrophic growth = 1.0 mgN/L -# K_ALK_AUT=0.5*12, # alkalinity half saturation coefficient for autotrophic growth = 0.5 mol(HCO3-)/m^3 = 6.0 gC/m^3 -# K_P_AUT=0.01, # phosphorus (nutrient) half saturation coefficient for autotrophic growth = 0.01 mgP/L -# k_PRE = 1, # phosphorus precipitation with MeOH rate constant = 1.0 m^3/g/d -# k_RED = 0.6, # redissoluation of phosphates rate constant = 0.6 d^(-1) -# K_ALK_PRE=0.5*12 # alkalinity half saturation coefficient for phosphate precipitation = 0.5 mol(HCO3-)/m^3 = 6.0 gC/m^3 -# ) - -# ASM2d typical values at 10 degree C -# asm2d.set_parameters( -# f_SI = 0, # production of soluble inerts in hydrolysis = 0.0 gCOD/gCOD -# Y_H = 0.625, # heterotrophic yield = 0.625 gCOD/gCOD -# f_XI_H=0.1, # fraction of inert COD generated in heterotrophic biomass lysis = 0.1 gCOD/gCOD -# Y_PAO = 0.625, # PAO yield = 0.625 gCOD/gCOD -# Y_PO4 = 0.4, # PP requirement (PO4 release) per PHA stored = 0.4 gP/gCOD -# Y_PHA = 0.2, # PHA requirement for PP storage = 0.2 gCOD/gP -# f_XI_PAO=0.1, # fraction of inert COD generated in PAO biomass lysis = 0.1 gCOD/gCOD -# Y_A = 0.24, # autotrophic yield = 0.24 gCOD/gN -# f_XI_AUT=0.1, # fraction of inert COD generated in autotrophic biomass lysis = 0.1 gCOD/gCOD -# K_h = 2, # hydrolysis rate constant = 2.0 d^(-1) -# eta_NO3 = 0.6, # reduction factor for anoxic hydrolysis = 0.6 -# eta_fe = 0.4, # anaerobic hydrolysis reduction factor = 0.4 -# K_O2 = 0.2, # O2 half saturation coefficient of hydrolysis = 0.2 mgO2/L -# K_NO3 = 0.5, # nitrate half saturation coefficient of hydrolysis = 0.5 mgN/L -# K_X = 0.1, # slowly biodegradable substrate half saturation coefficient for hydrolysis = 0.1 gCOD/gCOD -# mu_H = 3, # heterotrophic maximum specific growth rate = 3.0 d^(-1) -# q_fe = 1.5, # fermentation maximum rate = 1.5 d^(-1) -# eta_NO3_H = 0.8, # denitrification reduction factor for heterotrophic growth = 0.8 -# b_H = 0.4, # lysis and decay rate constant = 0.4 d^(-1) -# K_O2_H=0.2, # O2 half saturation coefficient of heterotrophs = 0.2 mgO2/L -# K_F = 4, # fermentable substrate half saturation coefficient for heterotrophic growth = 4.0 mgCOD/L -# K_fe = 4, # fermentable substrate half saturation coefficient for fermentation = 4.0 mgCOD/L -# K_A_H = 4, # VFA half saturation coefficient for heterotrophs = 4.0 mgCOD/L -# K_NO3_H=0.5, # nitrate half saturation coefficient = 0.5 mgN/L -# K_NH4_H = 0.05, # ammonium (nutrient) half saturation coefficient for heterotrophs = 0.05 mgN/L -# K_P_H = 0.01, # phosphorus (nutrient) half saturation coefficient for heterotrophs = 0.01 mgP/L -# K_ALK_H = 0.1*12, # alkalinity half saturation coefficient for heterotrophs = 0.1 mol(HCO3-)/m^3 = 1.2 gC/m^3 -# q_PHA = 2, # rate constant for storage of PHA = 2.0 d^(-1) -# q_PP = 1.0, # rate constant for storage of PP = 1.0 d^(-1) -# mu_PAO = 0.67, # PAO maximum specific growth rate = 0.67 d^(-1) -# eta_NO3_PAO=0.6, # denitrification reduction factor for PAO growth = 0.8 -# b_PAO = 0.1, # PAO lysis rate = 0.1 d^(-1) -# b_PP = 0.1, # PP lysis rate = 0.1 d^(-1) -# b_PHA = 0.1, # PHA lysis rate = 0.1 d^(-1) -# K_O2_PAO=0.2, # O2 half saturation coefficient for PAOs = 0.2 mgO2/L -# K_NO3_PAO=0.5, # nitrate half saturation coefficient for PAOs = 0.5 mgN/L -# K_A_PAO=4.0, # VFA half saturation coefficient for PAOs = 4.0 mgCOD/L -# K_NH4_PAO=0.05, # ammonium (nutrient) half saturation coefficient for PAOs = 0.05 mgN/L -# K_PS = 0.2, # phosphorus half saturation coefficient for storage of PP = 0.2 mgP/L -# K_P_PAO=0.01, # phosphorus (nutrient) half saturation coefficient for heterotrophs = 0.01 mgP/L -# K_ALK_PAO=0.1*12, # alkalinity half saturation coefficient for PAOs = 0.1 mol(HCO3-)/m^3 = 1.2 gC/m^3 -# K_PP = 0.01, # PP half saturation coefficient for storage of PHA = 0.01 gP/gCOD (?). gCOD/gCOD in GPS-X -# K_MAX = 0.34, # maximum ratio of X_PP/X_PAO = 0.34 gX_PP/gX_PAO -# K_IPP = 0.02, # inhibition coefficient for PP storage = 0.02 gP/gCOD -# K_PHA = 0.01, # PHA half saturation coefficient = 0.01 gCOD/gCOD -# mu_AUT = 0.35, # autotrophic maximum specific growth rate = 0.35 d^(-1) -# b_AUT = 0.05, # autotrophic decay rate = 0.05 d^(-1) -# K_O2_AUT = 0.5, # O2 half saturation coefficient for autotrophic growth = 0.5 mgO2/L -# K_NH4_AUT = 1, # ammonium (substrate) half saturation coefficient for autotrophic growth = 1.0 mgN/L -# K_ALK_AUT=0.5*12, # alkalinity half saturation coefficient for autotrophic growth = 0.5 mol(HCO3-)/m^3 = 6.0 gC/m^3 -# K_P_AUT=0.01, # phosphorus (nutrient) half saturation coefficient for autotrophic growth = 0.01 mgP/L -# k_PRE = 1, # phosphorus precipitation with MeOH rate constant = 1.0 m^3/g/d -# k_RED = 0.6, # redissoluation of phosphates rate constant = 0.6 d^(-1) -# K_ALK_PRE=0.5*12 # alkalinity half saturation coefficient for phosphate precipitation = 0.5 mol(HCO3-)/m^3 = 6.0 gC/m^3 -# ) - +def create_masm2d_cmps(set_thermo=True): + c2d = create_asm2d_cmps(False) + S_K = Component.from_chemical('S_K', chemical='K', + measured_as='K', + description='Potassium', + particle_size='Soluble', + degradability='Undegradable', + organic=False) + + S_Mg = Component.from_chemical('S_Mg', chemical='Mg', + measured_as='Mg', + description='Magnesium', + particle_size='Soluble', + degradability='Undegradable', + organic=False) + + S_IC = c2d.S_ALK.copy('S_IC') + + c2d.S_F.i_C = c2d.X_S.i_C = 0.31843 + c2d.S_F.i_N = c2d.X_S.i_N = 0.03352 + c2d.S_F.i_P = c2d.X_S.i_P = 5.59e-3 + + c2d.S_I.i_C = c2d.X_I.i_C = 0.36178 + c2d.S_I.i_N = c2d.X_I.i_N = 0.06003 + c2d.S_I.i_P = c2d.X_I.i_P = 6.49e-3 + c2d.S_F.i_mass = c2d.X_S.i_mass = c2d.S_I.i_mass = c2d.X_I.i_mass = 0.75 + c2d.S_F.f_Vmass_Totmass = c2d.X_S.f_Vmass_Totmass = c2d.S_I.f_Vmass_Totmass = c2d.X_I.f_Vmass_Totmass = 0.85 + + c2d.X_H.i_C = c2d.X_AUT.i_C = c2d.X_PAO.i_C = 0.36612 + c2d.X_H.i_N = c2d.X_AUT.i_N = c2d.X_PAO.i_N = 0.08615 + c2d.X_H.i_P = c2d.X_AUT.i_P = c2d.X_PAO.i_P = 0.02154 + c2d.X_H.i_mass = c2d.X_AUT.i_mass = c2d.X_PAO.i_mass = 0.90 + c2d.X_H.f_Vmass_Totmass = c2d.X_AUT.f_Vmass_Totmass = c2d.X_PAO.f_Vmass_Totmass = 0.85 + + c2d.X_PHA.i_C = 0.3 + c2d.X_PHA.i_mass = 0.55 + c2d.X_PHA.f_Vmass_Totmass = 0.92727 + c2d.X_PP.i_charge = 0 + + for cmp in (c2d.S_F, c2d.X_S, c2d.S_I, c2d.X_I, c2d.X_H, c2d.X_AUT, c2d.X_PHA): + cmp.i_NOD = None + c2d.refresh_constants() + c2d = [*c2d] + solubles = c2d[:8] # replace S_ALK with S_IC + others = c2d[9:] + + cmps = Components([*solubles, S_IC, S_K, S_Mg, *others]) + cmps.default_compile() + if set_thermo: settings.set_thermo(cmps) + + return cmps + + +#%% @chemicals_user class ASM2d(CompiledProcesses): ''' @@ -508,4 +413,238 @@ def __new__(cls, components=None, K_ALK_AUT=K_ALK_AUT*12, K_P_AUT=K_P_AUT, k_PRE=k_PRE, k_RED=k_RED, K_ALK_PRE=K_ALK_PRE*12, **kwargs) - return self \ No newline at end of file + return self + +#%% +_mpath = ospath.join(data_path, 'process_data/_masm2d.tsv') + +Monod = lambda S, K: S/(S+K) + +rhos = np.zeros(19) # 19 biological processes, no precipitation/dissociation or gas stripping yet +def rhos_masm2d(state_arr, params, acceptor_dependent_decay=True): + if 'ks' not in params: + k_h, mu_H, mu_PAO, mu_AUT, \ + q_fe, q_PHA, q_PP, \ + b_H, b_PAO, b_PP, b_PHA, b_AUT, \ + eta_NO3, eta_fe, eta_NO3_H, eta_NO3_PAO, \ + eta_NO3_Hl, eta_NO3_PAOl, eta_NO3_PPl, eta_NO3_PHAl, eta_NO3_AUTl, \ + K_O2, K_O2_H, K_O2_PAO, K_O2_AUT, \ + K_NO3, K_NO3_H, K_NO3_PAO, K_NO3_AUT, \ + K_X, K_F, K_fe, K_A_H, K_A_PAO, \ + K_NH4_H, K_NH4_PAO, K_NH4_AUT, \ + K_P_H, K_P_PAO, K_P_AUT, K_P_S, \ + K_PP, K_MAX, K_IPP, K_PHA, \ + = params.values() + + params['ks'] = ks = rhos * 0 + # rate constants + ks[:3] = k_h + ks[3:7] = mu_H + ks[7:19] = (q_fe, b_H, q_PHA, q_PP, q_PP, mu_PAO, mu_PAO, b_PAO, b_PP, b_PHA, mu_AUT, b_AUT) + # rate reduction factors + ks[1] *= eta_NO3 + ks[2] *= eta_fe + ks[5:7] *= eta_NO3_H + ks[[11,13]] *= eta_NO3_PAO + + # half saturation / inhibition factors + params['Ks_o2'] = np.array([K_O2, K_O2_H, K_O2_H, K_O2_PAO, K_O2_PAO, K_O2_AUT]) + params['Ks_no3'] = np.array([K_NO3, K_NO3_H, K_NO3_H, K_NO3_PAO, K_NO3_PAO, K_NO3_AUT]) + params['Ks_nh4'] = np.array([K_NH4_H, K_NH4_PAO, K_NH4_AUT]) + params['Ks_po4'] = np.array([K_P_H, K_P_PAO, K_P_AUT]) + params['eta_decay'] = np.array([eta_NO3_Hl, eta_NO3_PAOl, eta_NO3_PPl, eta_NO3_PHAl, eta_NO3_AUTl]) + + ks = params['ks'] + Ks_o2 = params['Ks_o2'] + Ks_no3 = params['Ks_no3'] + Ks_nh4 = params['Ks_nh4'] + Ks_po4 = params['Ks_po4'] + eta_decay = params['eta_decay'] + + Kx = params['K_X'] + Kf = params['K_F'] + Kfe = params['K_fe'] + Ka_H = params['K_A_H'] + Ka_PAO = params['K_A_PAO'] + Kp_stor = params['K_P_S'] + Kpp = params['K_PP'] + Kmax = params['K_MAX'] + Kipp = params['K_IPP'] + Kpha = params['K_PHA'] + + S_O2, S_NH4, S_NO3, S_PO4, S_F, S_A, \ + X_S, X_H, X_PAO, X_PP, X_PHA, X_AUT \ + = state_arr[[0,2,3,4,5,6,12,13,14,15,16,17]] + + nutrients = Monod(S_NH4, Ks_nh4) * Monod(S_PO4, Ks_po4) + + rhos[:] = ks + rhos[:9] *= X_H * nutrients[0] + rhos[9:15] *= X_PAO + rhos[12:14] *= nutrients[1] + rhos[15] *= X_PP + rhos[16] *= X_PHA + rhos[17:19] *= X_AUT + rhos[17] *= nutrients[2] + + aero = Monod(S_O2, Ks_o2) + anox = Monod(S_NO3, Ks_no3) + rhos[[0,3,4,10,12,17]] *= aero # aerobic + rhos[[1,5,6,11,13]] *= (1-aero[:5]) * anox[:5] # anoxic + rhos[[2,7]] *= (1-aero[:2]) * (1-anox[:2]) # anaerobic/fermentation + + if X_H > 0: rhos[:3] *= Monod(X_S/X_H, Kx) + if S_F > 0: rhos[[3,5]] *= Monod(S_F, Kf) * S_F/(S_F+S_A) + else: rhos[[3,5]] = 0. + if S_A > 0: rhos[[4,6]] *= Monod(S_A, Ka_H) * S_A/(S_F+S_A) + else: rhos[[4,6]] = 0. + + rhos[7] *= Monod(S_F, Kfe) + if X_PAO > 0: + pha = Monod(X_PHA/X_PAO, Kpha) + rhos[9] *= Monod(S_A, Ka_PAO) * Monod(X_PP/X_PAO, Kpp) + rhos[[10,11]] *= pha * Monod(Kmax-X_PP/X_PAO, Kipp) * Monod(S_PO4, Kp_stor) + rhos[[12,13]] *= pha + + if acceptor_dependent_decay: + rhos[8] *= (aero[1] + eta_decay[0]*(1-aero[1])*anox[1]) + rhos[14:17] *= (aero[3] +eta_decay[1:4]*(1-aero[3])*anox[3]) + rhos[18] *= (aero[5] + eta_decay[4]*(1-aero[5])*anox[5]) + + return rhos + +@chemicals_user +class mASM2d(CompiledProcesses): + ''' + Modified ASM2d. [1]_, [2]_ Compatible with `ADM1p` for plant-wide simulations. + + Parameters + ---------- + + Examples + -------- + >>> from qsdsan import processes as pc, set_thermo + >>> cmps = pc.create_asm2d_cmps() + >>> asm2d = pc.ASM2d() + >>> asm2d.show() + ASM2d([aero_hydrolysis, anox_hydrolysis, anae_hydrolysis, hetero_growth_S_F, hetero_growth_S_A, denitri_S_F, denitri_S_A, ferment, hetero_lysis, PAO_storage_PHA, aero_storage_PP, PAO_aero_growth_PHA, PAO_lysis, PP_lysis, PHA_lysis, auto_aero_growth, auto_lysis, precipitation, redissolution, anox_storage_PP, PAO_anox_growth]) + + References + ---------- + .. [1] Henze, M., Gujer, W., Mino, T., & van Loosdrecht, M. (2000). + Activated Sludge Models: ASM1, ASM2, ASM2d and ASM3. In IWA task group + on mathematical modelling for design and operation of biological + wastewater treatment (Ed.), Scientific and Technical Report No. 9. + IWA Publishing. + .. [2] Solon, K., Flores-Alsina, X., Kazadi Mbamba, C., Ikumi, D., Volcke, + E. I. P., Vaneeckhaute, C., Ekama, G., Vanrolleghem, P. A., Batstone, + D. J., Gernaey, K. V., & Jeppsson, U. (2017). Plant-wide modelling + of phosphorus transformations in wastewater treatment systems: + Impacts of control and operational strategies. Water Research, 113, + 97–110. https://doi.org/10.1016/j.watres.2017.02.007 + + ''' + _stoichio_params = ('f_SI', 'Y_H', 'Y_PAO', 'Y_PO4', 'Y_PHA', 'Y_A', + 'f_XI_H', 'f_XI_PAO', 'f_XI_AUT','K_XPP', 'Mg_XPP') + _kinetic_params = ('k_h', 'mu_H', 'mu_PAO', 'mu_AUT', + 'q_fe', 'q_PHA', 'q_PP', + 'b_H', 'b_PAO', 'b_PP', 'b_PHA', 'b_AUT', + # k_PRE, k_RED, + 'eta_NO3', 'eta_fe', 'eta_NO3_H', 'eta_NO3_PAO', + 'eta_NO3_Hl', 'eta_NO3_PAOl', 'eta_NO3_PPl', 'eta_NO3_PHAl', 'eta_NO3_AUTl', + 'K_O2', 'K_O2_H', 'K_O2_PAO', 'K_O2_AUT', + 'K_NO3', 'K_NO3_H', 'K_NO3_PAO', 'K_NO3_AUT', + 'K_X', 'K_F', 'K_fe', 'K_A_H', 'K_A_PAO', + 'K_NH4_H', 'K_NH4_PAO', 'K_NH4_AUT', + 'K_P_H', 'K_P_PAO', 'K_P_AUT', 'K_P_S', + 'K_PP', 'K_MAX', 'K_IPP', 'K_PHA', + # 'K_ALK_PRE' + ) + + decay_dependon_electron_acceptor = True + + def __new__(cls, components=None, path=None, + f_SI=0.0, Y_H=0.625, Y_PAO=0.625, Y_PO4=0.4, Y_PHA=0.2, Y_A=0.24, + f_XI_H=0.1, f_XI_PAO=0.1, f_XI_AUT=0.1, + k_h=3.0, mu_H=6.0, mu_PAO=1.0, mu_AUT=1.0, + q_fe=3.0, q_PHA=3.0, q_PP=1.5, + b_H=0.4, b_PAO=0.2, b_PP=0.2, b_PHA=0.2, b_AUT=0.15, + # k_PRE=1.0, k_RED=0.6, + eta_NO3=0.6, eta_fe=0.4, eta_NO3_H=0.8, eta_NO3_PAO=0.6, + eta_NO3_Hl=0.5, eta_NO3_PAOl=0.33, eta_NO3_PPl=0.33, eta_NO3_PHAl=0.33, eta_NO3_AUTl=0.33, + K_O2=0.2, K_O2_H=0.2, K_O2_PAO=0.2, K_O2_AUT=0.5, + K_NO3=0.5, K_NO3_H=0.5, K_NO3_PAO=0.5, K_NO3_AUT=0.5, + K_X=0.1, K_F=4.0, K_fe=4.0, K_A_H=4.0, K_A_PAO=4.0, + K_NH4_H=0.05, K_NH4_PAO=0.05, K_NH4_AUT=1.0, + K_P_H=0.01, K_P_PAO=0.01, K_P_AUT=0.01, K_P_S=0.2, + K_PP=0.01, K_MAX=0.34, K_IPP=0.02, K_PHA=0.01, + # K_ALK_PRE=0.5, + #!!! kLa and/or solubility values for gas stripping + #!!! precipitation kinetics + **kwargs): + + if not path: path = _mpath + + cmps = _load_components(components) + + self = Processes.load_from_file(path, + components=cmps, + conserved_for=('COD', 'C', 'N', 'P',), + parameters=cls._stoichio_params, + compile=False) + + if path == _path: + _p12 = Process('anox_storage_PP', + 'S_PO4 + [K_XPP]S_K + [Mg_XPP]S_Mg +[Y_PHA]X_PHA + [?]S_NO3 -> X_PP + [?]S_N2 + [?]S_NH4 + [?]S_IC', + components=cmps, + ref_component='X_PP', + conserved_for=('C', 'N', 'P', 'NOD',)) + + _p14 = Process('PAO_anox_growth', + '[1/Y_PAO]X_PHA + [?]S_NO3 + [?]S_PO4 -> X_PAO + [?]S_N2 + [?]S_NH4 + [?]S_IC', + components=cmps, + ref_component='X_PAO', + conserved_for=('C', 'N', 'P', 'NOD', 'COD')) + self.insert(11, _p12) + self.insert(13, _p14) + + #!!! add gas stripping + + self.compile(to_class=cls) + + dct = self.__dict__ + dct.update(kwargs) + stoichio_vals = (f_SI, Y_H, Y_PAO, Y_PO4, Y_PHA, Y_A, + f_XI_H, f_XI_PAO, f_XI_AUT, + cmps.X_PP.i_K, cmps.X_PP.i_Mg) + dct['_parameters'] = dict(zip(cls._stoichio_params, stoichio_vals)) + self.set_rate_function(rhos_masm2d) + kinetic_vals = (k_h, mu_H, mu_PAO, mu_AUT, + q_fe, q_PHA, q_PP, + b_H, b_PAO, b_PP, b_PHA, b_AUT, + # k_PRE, k_RED, + eta_NO3, eta_fe, eta_NO3_H, eta_NO3_PAO, + eta_NO3_Hl, eta_NO3_PAOl, eta_NO3_PPl, eta_NO3_PHAl, eta_NO3_AUTl, + K_O2, K_O2_H, K_O2_PAO, K_O2_AUT, + K_NO3, K_NO3_H, K_NO3_PAO, K_NO3_AUT, + K_X, K_F, K_fe, K_A_H, K_A_PAO, + K_NH4_H, K_NH4_PAO, K_NH4_AUT, + K_P_H, K_P_PAO, K_P_AUT, K_P_S, + K_PP, K_MAX, K_IPP, K_PHA, + # K_ALK_PRE + ) + self.rate_function._params = dict(zip(cls._kinetic_params, kinetic_vals)) + + return self + + def set_parameters(self, **parameters): + stoichio = self._parameters + kinetic = self.rate_function._params + if set(parameters.keys()).intersection(set(kinetic.keys())): + for key in ('ks', 'Ks_o2', 'Ks_no3', 'Ks_nh4', 'Ks_po4', 'eta_decay'): + kinetic.pop(key, None) + for k,v in parameters.items(): + if k in self._kinetic_params: kinetic[k] = v + else: stoichio[k] = v + if self._stoichio_lambdified is not None: + self.__dict__['_stoichio_lambdified'] = None \ No newline at end of file From 71901c02a087866b17dfebe6bfd40bb483beea93 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Wed, 5 Jun 2024 09:38:33 -0700 Subject: [PATCH 357/424] Update _asm2d.py --- qsdsan/processes/_asm2d.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/qsdsan/processes/_asm2d.py b/qsdsan/processes/_asm2d.py index 2764e6bb..16bdb10f 100644 --- a/qsdsan/processes/_asm2d.py +++ b/qsdsan/processes/_asm2d.py @@ -523,11 +523,6 @@ class mASM2d(CompiledProcesses): Examples -------- - >>> from qsdsan import processes as pc, set_thermo - >>> cmps = pc.create_asm2d_cmps() - >>> asm2d = pc.ASM2d() - >>> asm2d.show() - ASM2d([aero_hydrolysis, anox_hydrolysis, anae_hydrolysis, hetero_growth_S_F, hetero_growth_S_A, denitri_S_F, denitri_S_A, ferment, hetero_lysis, PAO_storage_PHA, aero_storage_PP, PAO_aero_growth_PHA, PAO_lysis, PP_lysis, PHA_lysis, auto_aero_growth, auto_lysis, precipitation, redissolution, anox_storage_PP, PAO_anox_growth]) References ---------- From 4802c5e4e16185fc1160b4e040d3c7e12c3d4ba4 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Wed, 5 Jun 2024 09:49:04 -0700 Subject: [PATCH 358/424] fix adm1p components --- qsdsan/data/process_data/_masm2d.tsv | 18 ++++++++++++++++++ qsdsan/processes/_adm1_p_extension.py | 8 ++++---- 2 files changed, 22 insertions(+), 4 deletions(-) create mode 100644 qsdsan/data/process_data/_masm2d.tsv diff --git a/qsdsan/data/process_data/_masm2d.tsv b/qsdsan/data/process_data/_masm2d.tsv new file mode 100644 index 00000000..705d2560 --- /dev/null +++ b/qsdsan/data/process_data/_masm2d.tsv @@ -0,0 +1,18 @@ + S_O2 S_F S_A S_I S_NH4 S_N2 S_NO3 S_PO4 S_IC S_K S_Mg X_I X_S X_H X_PAO X_PP X_PHA X_AUT +aero_hydrolysis 1-f_SI f_SI ? ? ? -1 +anox_hydrolysis 1-f_SI f_SI ? ? ? -1 +anae_hydrolysis 1-f_SI f_SI ? ? ? -1 +hetero_growth_S_F 1-1/Y_H (-1)/Y_H ? ? ? 1 +hetero_growth_S_A 1-1/Y_H (-1)/Y_H ? ? ? 1 +denitri_S_F (-1)/Y_H ? (1-Y_H)/(20/7*Y_H) (Y_H-1)/(20/7*Y_H) ? ? 1 +denitri_S_A (-1)/Y_H ? (1-Y_H)/(20/7*Y_H) (Y_H-1)/(20/7*Y_H) ? ? 1 +ferment -1 1 ? ? ? +hetero_lysis ? ? ? f_XI_H 1-f_XI_H -1 +storage_PHA -1 ? Y_PO4 ? Y_PO4*K_XPP Y_PO4*Mg_XPP (-Y_PO4) 1 +aero_storage_PP (-Y_PHA) ? -1 ? (-K_XPP) (-Mg_XPP) 1 (-Y_PHA) +PAO_aero_growth_PHA ? ? ? ? 1 (-1)/Y_PAO +PAO_lysis ? ? ? f_XI_PAO 1-f_XI_PAO -1 +PP_lysis ? 1 ? K_XPP Mg_XPP -1 +PHA_lysis 1 ? ? -1 +auto_aero_growth (Y_A-32/7)/Y_A ? 1/Y_A ? ? 1 +auto_lysis ? ? ? f_XI_AUT 1-f_XI_AUT -1 diff --git a/qsdsan/processes/_adm1_p_extension.py b/qsdsan/processes/_adm1_p_extension.py index b58896ed..8be34db5 100644 --- a/qsdsan/processes/_adm1_p_extension.py +++ b/qsdsan/processes/_adm1_p_extension.py @@ -59,10 +59,10 @@ def create_adm1_p_extension_cmps(set_thermo=True): S_IP = c2d.S_PO4.copy('S_IP') - c1.S_su.i_mass = c1.X_ch.i_mass = 0.9375 - c1.S_su.f_Vmass_Totmass = c1.X_ch.f_Vmass_Totmass = 0.68 - c1.X_li.i_mass = 0.6375 - c1.X_li.f_Vmass_Totmass = 1. + # c1.S_su.i_mass = c1.X_ch.i_mass = 0.9375 + # c1.S_su.f_Vmass_Totmass = c1.X_ch.f_Vmass_Totmass = 0.68 + # c1.X_li.i_mass = 0.6375 + # c1.X_li.f_Vmass_Totmass = 1. c1.S_aa.i_C = c1.X_pr.i_C = 0.36890 c1.S_aa.i_N = c1.X_pr.i_N = 0.11065 From cf204c83f3983b65994998229f3154fa465af553 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Thu, 6 Jun 2024 09:28:44 -0700 Subject: [PATCH 359/424] fix `mASM2d` --- qsdsan/data/_masm2d.xlsx | Bin 18185 -> 20265 bytes qsdsan/processes/_asm2d.py | 10 ++++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/qsdsan/data/_masm2d.xlsx b/qsdsan/data/_masm2d.xlsx index 2ce17ae9eea3be8004b4954b878104df044eae61..3a671c5129340fb4cdc6fad57394b205cd8f48ab 100644 GIT binary patch delta 10751 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 DJ+mhD diff --git a/qsdsan/processes/_asm2d.py b/qsdsan/processes/_asm2d.py index 16bdb10f..8f020149 100644 --- a/qsdsan/processes/_asm2d.py +++ b/qsdsan/processes/_asm2d.py @@ -16,7 +16,7 @@ from ..utils import ospath, data_path __all__ = ('create_asm2d_cmps', 'ASM2d', - 'create_masm2d_cmps', ) + 'create_masm2d_cmps', 'mASM2d') _path = ospath.join(data_path, 'process_data/_asm2d.tsv') _load_components = settings.get_default_chemicals @@ -421,7 +421,7 @@ def __new__(cls, components=None, Monod = lambda S, K: S/(S+K) rhos = np.zeros(19) # 19 biological processes, no precipitation/dissociation or gas stripping yet -def rhos_masm2d(state_arr, params, acceptor_dependent_decay=True): +def _rhos_masm2d(state_arr, params, acceptor_dependent_decay=True): if 'ks' not in params: k_h, mu_H, mu_PAO, mu_AUT, \ q_fe, q_PHA, q_PP, \ @@ -588,18 +588,19 @@ def __new__(cls, components=None, path=None, parameters=cls._stoichio_params, compile=False) - if path == _path: + if path == _mpath: _p12 = Process('anox_storage_PP', 'S_PO4 + [K_XPP]S_K + [Mg_XPP]S_Mg +[Y_PHA]X_PHA + [?]S_NO3 -> X_PP + [?]S_N2 + [?]S_NH4 + [?]S_IC', components=cmps, ref_component='X_PP', - conserved_for=('C', 'N', 'P', 'NOD',)) + conserved_for=('C', 'N', 'NOD', 'COD')) _p14 = Process('PAO_anox_growth', '[1/Y_PAO]X_PHA + [?]S_NO3 + [?]S_PO4 -> X_PAO + [?]S_N2 + [?]S_NH4 + [?]S_IC', components=cmps, ref_component='X_PAO', conserved_for=('C', 'N', 'P', 'NOD', 'COD')) + self.insert(11, _p12) self.insert(13, _p14) @@ -613,6 +614,7 @@ def __new__(cls, components=None, path=None, f_XI_H, f_XI_PAO, f_XI_AUT, cmps.X_PP.i_K, cmps.X_PP.i_Mg) dct['_parameters'] = dict(zip(cls._stoichio_params, stoichio_vals)) + rhos_masm2d = lambda state_arr, params: _rhos_masm2d(state_arr, params, cls.decay_dependon_electron_acceptor) self.set_rate_function(rhos_masm2d) kinetic_vals = (k_h, mu_H, mu_PAO, mu_AUT, q_fe, q_PHA, q_PP, From cdbcb17adcc3e2a5c512bd62e133d819a88b813f Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Fri, 7 Jun 2024 09:57:17 -0700 Subject: [PATCH 360/424] add new property --- qsdsan/processes/_asm2d.py | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/qsdsan/processes/_asm2d.py b/qsdsan/processes/_asm2d.py index 8f020149..43c74d7c 100644 --- a/qsdsan/processes/_asm2d.py +++ b/qsdsan/processes/_asm2d.py @@ -494,10 +494,11 @@ def _rhos_masm2d(state_arr, params, acceptor_dependent_decay=True): rhos[[2,7]] *= (1-aero[:2]) * (1-anox[:2]) # anaerobic/fermentation if X_H > 0: rhos[:3] *= Monod(X_S/X_H, Kx) - if S_F > 0: rhos[[3,5]] *= Monod(S_F, Kf) * S_F/(S_F+S_A) - else: rhos[[3,5]] = 0. - if S_A > 0: rhos[[4,6]] *= Monod(S_A, Ka_H) * S_A/(S_F+S_A) - else: rhos[[4,6]] = 0. + if S_F+S_A == 0: + rhos[3:7] = 0 + else: + rhos[[3,5]] *= Monod(S_F, Kf) * S_F/(S_F+S_A) + rhos[[4,6]] *= Monod(S_A, Ka_H) * S_A/(S_F+S_A) rhos[7] *= Monod(S_F, Kfe) if X_PAO > 0: @@ -556,9 +557,7 @@ class mASM2d(CompiledProcesses): # 'K_ALK_PRE' ) - decay_dependon_electron_acceptor = True - - def __new__(cls, components=None, path=None, + def __new__(cls, components=None, path=None, electron_acceptor_dependent_decay=True, f_SI=0.0, Y_H=0.625, Y_PAO=0.625, Y_PO4=0.4, Y_PHA=0.2, Y_A=0.24, f_XI_H=0.1, f_XI_PAO=0.1, f_XI_AUT=0.1, k_h=3.0, mu_H=6.0, mu_PAO=1.0, mu_AUT=1.0, @@ -614,7 +613,7 @@ def __new__(cls, components=None, path=None, f_XI_H, f_XI_PAO, f_XI_AUT, cmps.X_PP.i_K, cmps.X_PP.i_Mg) dct['_parameters'] = dict(zip(cls._stoichio_params, stoichio_vals)) - rhos_masm2d = lambda state_arr, params: _rhos_masm2d(state_arr, params, cls.decay_dependon_electron_acceptor) + rhos_masm2d = lambda state_arr, params: _rhos_masm2d(state_arr, params, electron_acceptor_dependent_decay) self.set_rate_function(rhos_masm2d) kinetic_vals = (k_h, mu_H, mu_PAO, mu_AUT, q_fe, q_PHA, q_PP, @@ -634,6 +633,16 @@ def __new__(cls, components=None, path=None, return self + @property + def electron_acceptor_dependent_decay(self): + '''[bool] Whether the decay rate is dependent on electron acceptor (O2, NO3-) concentrations''' + return self._edecay + @electron_acceptor_dependent_decay.setter + def electron_acceptor_dependent_decay(self, dependent): + self._edecay = edecay = bool(dependent) + rhos_masm2d = lambda state_arr, params: _rhos_masm2d(state_arr, params, edecay) + self.set_rate_function(rhos_masm2d) + def set_parameters(self, **parameters): stoichio = self._parameters kinetic = self.rate_function._params From f9a9efb24e8b2fc2b228751007fb93bb572fd168 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Tue, 11 Jun 2024 16:25:06 -0700 Subject: [PATCH 361/424] mASM2d rate function --- qsdsan/data/_masm2d.xlsx | Bin 20265 -> 44149 bytes qsdsan/data/process_data/_mmp.tsv | 8 ++ qsdsan/processes/_asm2d.py | 232 +++++++++++++++++++++++++----- 3 files changed, 207 insertions(+), 33 deletions(-) create mode 100644 qsdsan/data/process_data/_mmp.tsv diff --git a/qsdsan/data/_masm2d.xlsx b/qsdsan/data/_masm2d.xlsx index 3a671c5129340fb4cdc6fad57394b205cd8f48ab..3e707afba276337b3282d6500d27331153db36fa 100644 GIT binary patch delta 34598 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 Z)1Lw$s1hC$8VDLl_SeYv6#rB7{{h>Yh@t=h delta 10962 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 z 0: rhos[19] = (S_Ca * co3 - Ksp[0]) * f_dis[0] + else: rhos[19] = S_Ca * co3 + if X_struv > 0: rhos[20] = (S_Mg * nh4 * po4 - Ksp[1]) * f_dis[1] + else: rhos[20] = S_Mg * nh4 * po4 + if X_newb > 0: rhos[21] = (S_Mg * hpo4 - Ksp[2]) * f_dis[2] + else: rhos[21] = S_Mg * hpo4 + if X_ACP > 0: rhos[22] = (S_Ca**3 * po4**2 - Ksp[3]) * f_dis[3] + else: rhos[22] = S_Ca**3 * po4**2 + if X_MgCO3 > 0: rhos[23] = (S_Mg * co3 - Ksp[4]) * f_dis[4] + else: rhos[23] = S_Mg * co3 + rhos[24] = X_AlOH * po4 * Monod(X_AlOH, K_AlOH) + rhos[25] = X_FeOH * po4 * Monod(X_FeOH, K_FeOH) + rhos[19:26] *= k_mmp + + ########### gas stripping ########### + kLa_n2, kLa_co2 = params['kLa'] + KH_n2, KH_co2 = params['K_Henry'] # assume already temperature-corrected + rhos[26] = kLa_n2*(S_N2 - KH_n2*p_n2_air/mass2mol[1]*1e3) # M/atm * atm / mol/g * 1000 mg/g = mg/L + rhos[27] = kLa_co2*(co2 - KH_co2*p_co2_air/mass2mol[1]*1e3) # M/atm * atm / mol/g * 1000 mg/g = mg/L + return rhos @chemicals_user @@ -545,7 +655,6 @@ class mASM2d(CompiledProcesses): _kinetic_params = ('k_h', 'mu_H', 'mu_PAO', 'mu_AUT', 'q_fe', 'q_PHA', 'q_PP', 'b_H', 'b_PAO', 'b_PP', 'b_PHA', 'b_AUT', - # k_PRE, k_RED, 'eta_NO3', 'eta_fe', 'eta_NO3_H', 'eta_NO3_PAO', 'eta_NO3_Hl', 'eta_NO3_PAOl', 'eta_NO3_PPl', 'eta_NO3_PHAl', 'eta_NO3_AUTl', 'K_O2', 'K_O2_H', 'K_O2_PAO', 'K_O2_AUT', @@ -554,8 +663,16 @@ class mASM2d(CompiledProcesses): 'K_NH4_H', 'K_NH4_PAO', 'K_NH4_AUT', 'K_P_H', 'K_P_PAO', 'K_P_AUT', 'K_P_S', 'K_PP', 'K_MAX', 'K_IPP', 'K_PHA', - # 'K_ALK_PRE' - ) + 'k_mmp', 'Ksp', 'K_dis', 'K_AlOH', 'K_FeOH', + 'kLa', 'K_Henry', 'Ka', 'cmps') + + _acid_base_pairs = (('H+', 'OH-'), ('NH4+', 'NH3'), + ('CO2', 'HCO3-'), ('HCO3-', 'CO3-2'), + ('H3PO4', 'H2PO4-'), ('H2PO4-', 'HPO4-2'), ('HPO4-2', 'PO4-3'), + ('HAc', 'Ac-'),) + _precipitates = ('X_CaCO3', 'X_struv', 'X_newb', 'X_ACP', 'X_MgCO3', 'X_AlPO4', 'X_FePO4') + + _gas_stripping = ('S_N2', 'S_IC') def __new__(cls, components=None, path=None, electron_acceptor_dependent_decay=True, f_SI=0.0, Y_H=0.625, Y_PAO=0.625, Y_PO4=0.4, Y_PHA=0.2, Y_A=0.24, @@ -563,7 +680,6 @@ def __new__(cls, components=None, path=None, electron_acceptor_dependent_decay=T k_h=3.0, mu_H=6.0, mu_PAO=1.0, mu_AUT=1.0, q_fe=3.0, q_PHA=3.0, q_PP=1.5, b_H=0.4, b_PAO=0.2, b_PP=0.2, b_PHA=0.2, b_AUT=0.15, - # k_PRE=1.0, k_RED=0.6, eta_NO3=0.6, eta_fe=0.4, eta_NO3_H=0.8, eta_NO3_PAO=0.6, eta_NO3_Hl=0.5, eta_NO3_PAOl=0.33, eta_NO3_PPl=0.33, eta_NO3_PHAl=0.33, eta_NO3_AUTl=0.33, K_O2=0.2, K_O2_H=0.2, K_O2_PAO=0.2, K_O2_AUT=0.5, @@ -572,9 +688,14 @@ def __new__(cls, components=None, path=None, electron_acceptor_dependent_decay=T K_NH4_H=0.05, K_NH4_PAO=0.05, K_NH4_AUT=1.0, K_P_H=0.01, K_P_PAO=0.01, K_P_AUT=0.01, K_P_S=0.2, K_PP=0.01, K_MAX=0.34, K_IPP=0.02, K_PHA=0.01, - # K_ALK_PRE=0.5, #!!! kLa and/or solubility values for gas stripping #!!! precipitation kinetics + k_mmp=(5.0, 300, 0.05, 150, 50, 1.0, 1.0), + pKsp=(6.45, 13.16, 5.8, 23, 7, 21, 26), + K_dis=(1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0), + K_AlOH=0.001, K_FeOH=0.001, + kLa=(3.0, 3.0), K_Henry=(6.5e-4, 3.5e-2), + pKa=(14, 9.25, 6.37, 10.32, 2.12, 7.21, 12.32, 4.76), **kwargs): if not path: path = _mpath @@ -603,22 +724,51 @@ def __new__(cls, components=None, path=None, electron_acceptor_dependent_decay=T self.insert(11, _p12) self.insert(13, _p14) - #!!! add gas stripping + mmp = Processes.load_from_file(_mmp, components=cmps, + conserved_for=(), compile=False) + mmp_stoichio = {} + df = load_data(_mmp) + for i, j in df.iterrows(): + j.dropna(inplace=True) + key = j.index[j == 1][0] + j = j.to_dict() + j.pop(key) + mmp_stoichio[key] = j + mol_to_mass = cmps.chem_MW / cmps.i_mass + Ksp_mass = np.array([10**(-p) for p in pKsp]) # mass in mg/L or g/m3 + i = 0 + for pd, xid in zip(mmp, cls._precipitates): + for k,v in mmp_stoichio[xid].items(): + m2m = mol_to_mass[cmps.index(k)] * 1e3 + Ksp_mass[i] *= m2m**abs(v) + i += 1 + pd._stoichiometry *= mol_to_mass + pd.ref_component = xid + + self.extend(mmp) + + for gas in cls._gas_stripping: + new_p = Process('%s_stripping' % gas.lstrip('S_'), + reaction={gas:-1}, + ref_component=gas, + conserved_for=(),) + self.append(new_p) self.compile(to_class=cls) dct = self.__dict__ dct.update(kwargs) + dct['mmp_stoichio'] = mmp_stoichio stoichio_vals = (f_SI, Y_H, Y_PAO, Y_PO4, Y_PHA, Y_A, f_XI_H, f_XI_PAO, f_XI_AUT, cmps.X_PP.i_K, cmps.X_PP.i_Mg) dct['_parameters'] = dict(zip(cls._stoichio_params, stoichio_vals)) rhos_masm2d = lambda state_arr, params: _rhos_masm2d(state_arr, params, electron_acceptor_dependent_decay) self.set_rate_function(rhos_masm2d) + Ka = np.array([10**(-p) for p in pKa]) kinetic_vals = (k_h, mu_H, mu_PAO, mu_AUT, q_fe, q_PHA, q_PP, b_H, b_PAO, b_PP, b_PHA, b_AUT, - # k_PRE, k_RED, eta_NO3, eta_fe, eta_NO3_H, eta_NO3_PAO, eta_NO3_Hl, eta_NO3_PAOl, eta_NO3_PPl, eta_NO3_PHAl, eta_NO3_AUTl, K_O2, K_O2_H, K_O2_PAO, K_O2_AUT, @@ -627,10 +777,12 @@ def __new__(cls, components=None, path=None, electron_acceptor_dependent_decay=T K_NH4_H, K_NH4_PAO, K_NH4_AUT, K_P_H, K_P_PAO, K_P_AUT, K_P_S, K_PP, K_MAX, K_IPP, K_PHA, - # K_ALK_PRE + np.array(k_mmp), Ksp_mass, + np.array(K_dis), K_AlOH, K_FeOH, + np.array(kLa), np.array(K_Henry), Ka, cmps, ) self.rate_function._params = dict(zip(cls._kinetic_params, kinetic_vals)) - + return self @property @@ -653,4 +805,18 @@ def set_parameters(self, **parameters): if k in self._kinetic_params: kinetic[k] = v else: stoichio[k] = v if self._stoichio_lambdified is not None: - self.__dict__['_stoichio_lambdified'] = None \ No newline at end of file + self.__dict__['_stoichio_lambdified'] = None + + def set_pKsps(self, ps): + cmps = self.components + mol_to_mass = cmps.chem_MW / cmps.i_mass + idxer = cmps.index + stoichio = self.mmp_stoichio + Ksp_mass = [] # mass in mg/L or g/m3 + for xid, p in zip(self._precipitates, ps): + K = 10**(-p) + for cmp, v in stoichio[xid]: + m2m = mol_to_mass[idxer(cmp)] * 1e3 + K *= m2m**abs(v) + Ksp_mass.append(K) + self.rate_function._params['Ksp'] = np.array(Ksp_mass) From a4a4ac63fcd9c4c17141c7873c400b73eb837f7b Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Wed, 12 Jun 2024 12:47:38 -0700 Subject: [PATCH 362/424] ADM1p compatible with modified ASM2d --- qsdsan/data/_masm2d.xlsx | Bin 44149 -> 43955 bytes qsdsan/processes/_adm1_p_extension.py | 531 +++++++++++++++++++++++++- qsdsan/processes/_asm2d.py | 77 +++- 3 files changed, 598 insertions(+), 10 deletions(-) diff --git a/qsdsan/data/_masm2d.xlsx b/qsdsan/data/_masm2d.xlsx index 3e707afba276337b3282d6500d27331153db36fa..528638343c8f28dd9954cfb8aeca9b26186452ae 100644 GIT binary patch delta 13120 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 diff --git a/qsdsan/processes/_adm1_p_extension.py b/qsdsan/processes/_adm1_p_extension.py index 8be34db5..1d7793f4 100644 --- a/qsdsan/processes/_adm1_p_extension.py +++ b/qsdsan/processes/_adm1_p_extension.py @@ -21,6 +21,7 @@ from qsdsan import Component, Components, Process, Processes, CompiledProcesses from qsdsan.processes import ( create_adm1_cmps, + create_asm2d_cmps, create_masm2d_cmps, T_correction_factor, non_compet_inhibit, @@ -32,7 +33,7 @@ ADM1, TempState ) -from qsdsan.utils import ospath, data_path +from qsdsan.utils import ospath, data_path, load_data from scipy.optimize import brenth from warnings import warn import numpy as np @@ -56,6 +57,7 @@ def create_adm1_p_extension_cmps(set_thermo=True): c1 = create_adm1_cmps(False) c2d = create_masm2d_cmps(False) + _c2 = create_asm2d_cmps(False) S_IP = c2d.S_PO4.copy('S_IP') @@ -100,7 +102,7 @@ def create_adm1_p_extension_cmps(set_thermo=True): cmps_adm1p = Components([*Ss, S_IP, c1.S_I, *Xs, c2d.X_PHA, c2d.X_PP, c2d.X_PAO, - c2d.S_K, c2d.S_Mg, c2d.X_MeOH, c2d.X_MeP, + c2d.S_K, c2d.S_Mg, _c2.X_MeOH, _c2.X_MeP, *others]) cmps_adm1p.default_compile() if set_thermo: settings.set_thermo(cmps_adm1p) @@ -325,7 +327,8 @@ def grad_dydt_Sh2_AD(S_h2, state_arr, h, params, f_stoichio, V_liq, S_h2_in): @chemicals_user class ADM1_p_extension(ADM1): """ - Anaerobic Digestion Model No.1 with P extension. [1]_, [2]_, [3]_ + Anaerobic Digestion Model No.1 with P extension. [1]_, [2]_, [3]_. + Compatible with the original `ASM2d`. Parameters ---------- @@ -537,6 +540,528 @@ def set_KS_IP(self, K): self.rate_function._params['KS_IP'] = K * P_mw + def check_stoichiometric_parameters(self): + '''Check whether product COD fractions sum up to 1 for each process.''' + stoichio = self.parameters + subst = ('xb', 'su', 'aa', 'fa', 'va', 'bu', 'pro', 'PHA') + for s in subst: + f_tot = sum([stoichio[k] for k in self._stoichio_params[:-7] \ + if k.endswith(s)]) + if f_tot != 1: + raise ValueError(f"the sum of 'f_()_{s}' values must equal 1") + +#%% +# ============================================================================= +# ADM1p components, compatible with `mASM2d` +# ============================================================================= + +C_mw = get_mw({'C':1}) +N_mw = get_mw({'N':1}) +P_mw = get_mw({'P':1}) + +def create_adm1p_cmps(set_thermo=True): + c1 = create_adm1_cmps(False) + c2d = create_masm2d_cmps(False) + + S_IP = c2d.S_PO4.copy('S_IP') + + # c1.S_su.i_mass = c1.X_ch.i_mass = 0.9375 + # c1.S_su.f_Vmass_Totmass = c1.X_ch.f_Vmass_Totmass = 0.68 + # c1.X_li.i_mass = 0.6375 + # c1.X_li.f_Vmass_Totmass = 1. + + c1.S_aa.i_C = c1.X_pr.i_C = 0.36890 + c1.S_aa.i_N = c1.X_pr.i_N = 0.11065 + c1.S_aa.i_P = c1.X_pr.i_P = 0. + c1.S_aa.i_mass = c1.X_pr.i_mass = 0.737648 + c1.S_aa.f_Vmass_Totmass = c1.X_pr.f_Vmass_Totmass = 0.864 + + c1.S_fa._formula = None + c1.S_fa.chem_MW = 1. + c1.S_fa.i_C = 0.25685 + # c1.S_fa.i_mass = 1/2.9200 + + c1.S_I.i_C = c1.X_I.i_C = 0.36178 + c1.S_I.i_N = c1.X_I.i_N = 0.06003 + c1.S_I.i_P = c1.X_I.i_P = 6.49e-3 + c1.S_I.i_mass = c1.X_I.i_mass = 0.75 + c1.S_I.f_Vmass_Totmass = c1.X_I.f_Vmass_Totmass = 0.85 + + for cmp in (c1.S_aa, c1.X_pr, c1.S_fa, c1.S_I, c1.X_I): + cmp.i_NOD = None + + for cmp in (c1.X_su, c1.X_aa, c1.X_fa, c1.X_c4, c1.X_pro, c1.X_ac, c1.X_h2,): + cmp.i_C = 0.36612 + cmp.i_N = 0.08615 + cmp.i_P = 0.02154 + cmp.i_mass = 0.90 + cmp.f_Vmass_Totmass = 0.85 + cmp.i_NOD = None + + c1.refresh_constants() + c = [*c1] + Ss = c[:11] + Xs = c[13:-3] # X_c is excluded + + cmps_adm1p = Components([*Ss, S_IP, c1.S_I, *Xs, + c2d.X_PHA, c2d.X_PP, c2d.X_PAO, + c2d.S_K, c2d.S_Mg, c2d.S_Ca, c2d.X_CaCO3, + c2d.X_struv, c2d.X_newb, c2d.X_ACP, c2d.X_MgCO3, + c2d.X_AlOH, c2d.X_AlPO4, c2d.X_FeOH, c2d.X_FePO4, + c2d.S_Na, c2d.S_Cl, c2d.H2O]) + cmps_adm1p.default_compile() + if set_thermo: settings.set_thermo(cmps_adm1p) + return cmps_adm1p + + +#%% +# ============================================================================= +# kinetic rate functions +# ============================================================================= + +# def acid_base_rxn(h_ion, weak_acids_tot, Kas): +# # S_cat, S_K, S_Mg, S_an, S_IN, S_IP, S_IC, S_ac, S_pro, S_bu, S_va = weak_acids_tot # in M +# S_cat, S_K, S_Mg, S_an, S_IN, S_IP = weak_acids_tot[:6] +# # Kw, Ka_nh, Ka_h2po4, Ka_co2, Ka_ac, Ka_pr, Ka_bu, Ka_va = Kas +# Kw = Kas[0] +# oh_ion = Kw/h_ion +# nh3, hpo4, hco3, ac, pro, bu, va = Kas[1:] * weak_acids_tot[4:] / (Kas[1:] + h_ion) +# return S_cat + S_K + 2*S_Mg + h_ion + (S_IN - nh3) - S_an - oh_ion - hco3 - ac - pro - bu - va - 2*hpo4 - (S_IP - hpo4) + + +# rhos = np.zeros(35) # 28 kinetic processes (25 as defined in modified ADM1 + 7 mmp + 3 for gases) +# Cs = np.empty(25) # 25 processes as defined in modified ADM1 + +# def solve_pH(state_arr, Ka, unit_conversion): +# cmps_in_M = state_arr[:34] * unit_conversion +# # S_cat, S_K, S_Mg, S_an, S_IN, S_IP, S_IC, S_ac, S_pro, S_bu, S_va +# # Kw, Ka_nh, Ka_h2po4, Ka_co2, Ka_ac, Ka_pr, Ka_bu, Ka_va = Kas +# weak_acids = cmps_in_M[[31, 27, 28, 32, 10, 11, 9, 6, 5, 4, 3]] +# h = brenth(acid_base_rxn, 1e-14, 1.0, +# args=(weak_acids, Ka), +# xtol=1e-12, maxiter=100) +# return h + +# rhos_adm1_p_extension = lambda state_arr, params: _rhos_adm1p(state_arr, params, h=None) + +# def _rhos_adm1p(state_arr, params, h=None): +# ks = params['rate_constants'] +# Ks = params['half_sat_coeffs'] + +# cmps = params['components'] +# pH_ULs = params['pH_ULs'] +# pH_LLs = params['pH_LLs'] +# KS_IN = params['KS_IN'] +# KS_IP = params['KS_IP'] +# KI_nh3 = params['KI_nh3'] +# KIs_h2 = params['KIs_h2'] +# KHb = params['K_H_base'] +# Kab = params['Ka_base'] +# KH_dH = params['K_H_dH'] +# Ka_dH = params['Ka_dH'] +# kLa = params['kLa'] +# T_base = params['T_base'] +# root = params['root'] + +# if 'unit_conv' not in params: params['unit_conv'] = mass2mol_conversion(cmps) +# unit_conversion = params['unit_conv'] + +# # state_arr_cmps stated just for readability of code +# # 0: S_su +# # 1: S_aa +# # 2: S_fa +# # 3: S_va +# # 4: S_bu +# # 5: S_pro +# # 6: S_ac +# # 7: S_h2 +# # 8: S_ch4 +# # 9: S_IC +# # 10: S_IN +# # 11: S_IP +# # 12: S_I +# # 13: X_ch +# # 14: X_pr +# # 15: X_li +# # 16: X_su +# # 17: X_aa +# # 18: X_fa +# # 19: X_c4 +# # 20: X_pro +# # 21: X_ac +# # 22: X_h2 +# # 23: X_I +# # 24: X_PHA +# # 25: X_PP +# # 26: X_PAO +# # 27: S_K +# # 28: S_Mg +# # 29: S_Ca +# # 30: X_CaCO3 +# # 31: X_struv +# # 32: X_newb +# # 33: X_ACP +# # 34: X_MgCO3 +# # 35: X_AlOH +# # 36: X_AlPO4 +# # 37: X_FeOH +# # 38: X_FePO4 +# # 39: S_Na +# # 40: S_Cl +# # 41: H2O + +# Cs[:7] = state_arr[13:20] +# Cs[7:11] = state_arr[19:23] +# Cs[11:18] = state_arr[16:23] +# Cs[18:23] = X_PAO = state_arr[26] +# Cs[23] = X_PP = state_arr[25] +# Cs[24] = state_arr[24] + +# substrates = state_arr[:8] + +# S_va, S_bu, S_h2, S_IN, S_IP = state_arr[[3,4,7,10,11]] + +# T_op = state_arr[-1] +# if T_op == T_base: +# Ka = Kab +# KH = KHb / unit_conversion[7:10] +# else: +# T_temp = params.pop('T_op', None) +# if T_op != T_temp: +# params['Ka'] = Kab * T_correction_factor(T_base, T_op, Ka_dH) +# params['KH'] = KHb * T_correction_factor(T_base, T_op, KH_dH) / unit_conversion[7:10] +# params['T_op'] = T_op +# Ka = params['Ka'] +# KH = params['KH'] + +# rhos[:25] = ks * Cs +# rhos[3:11] *= substr_inhibit(substrates, Ks[:8]) +# if S_va > 0: rhos[6] *= 1/(1+S_bu/S_va) +# if S_bu > 0: rhos[7] *= 1/(1+S_va/S_bu) + +# vfas = state_arr[3:7] + +# if X_PAO > 0: +# K_A, K_PP = Ks[-2:] +# rhos[18:22] *= substr_inhibit(vfas, K_A) * substr_inhibit(X_PP/X_PAO, K_PP) + +# if sum(vfas) > 0: +# rhos[18:22] *= vfas/sum(vfas) + +# biogas_S = state_arr[7:10].copy() +# biogas_p = R * T_op * state_arr[42:45] + +# if h is None: h = solve_pH(state_arr, Ka, unit_conversion) +# nh3 = Ka[1] / (Ka[1] + h) * S_IN * unit_conversion[10] +# biogas_S[-1] = h / (Ka[3] + h) * state_arr[9] # co2 + +# Iph = Hill_inhibit(h, pH_ULs, pH_LLs) +# Iin = substr_inhibit(S_IN, KS_IN) +# Iip = substr_inhibit(S_IP, KS_IP) +# Ih2 = non_compet_inhibit(S_h2, KIs_h2) +# Inh3 = non_compet_inhibit(nh3, KI_nh3) +# root.data = { +# 'pH':-np.log10(h), +# 'Iph':Iph, +# 'Ih2':Ih2, +# 'Iin':Iin, +# 'Inh3':Inh3, +# } +# rhos[3:11] *= Iph * Iin * Iip +# rhos[5:9] *= Ih2 +# rhos[9] *= Inh3 +# rhos[-3:] = kLa * (biogas_S - KH * biogas_p) + +# # print(rhos) +# return rhos + +# def dydt_Sh2_AD(S_h2, state_arr, h, params, f_stoichio, V_liq, S_h2_in): +# state_arr[7] = S_h2 +# Q = state_arr[45] +# rxn = _rhos_adm1p(state_arr, params, h=h) +# stoichio = f_stoichio(state_arr) # should return the stoichiometric coefficients of S_h2 for all processes +# return Q/V_liq*(S_h2_in - S_h2) + np.dot(rxn, stoichio) + +# grad_rhos = np.zeros(5) +# X_bio = np.zeros(5) +# def grad_dydt_Sh2_AD(S_h2, state_arr, h, params, f_stoichio, V_liq, S_h2_in): +# state_arr[7] = S_h2 +# ks = params['rate_constants'][[5,6,7,8,10]] +# Ks = params['half_sat_coeffs'][2:6] +# K_h2 = params['half_sat_coeffs'][7] +# pH_ULs = params['pH_ULs'] +# pH_LLs = params['pH_LLs'] +# KS_IN = params['KS_IN'] +# KIs_h2 = params['KIs_h2'] +# kLa = params['kLa'] + +# X_bio[:] = state_arr[[18,19,19,20,22]] +# substrates = state_arr[2:6] +# S_va, S_bu, S_IN = state_arr[[3,4,10]] +# Iph = Hill_inhibit(h, pH_ULs, pH_LLs)[[2,3,4,5,7]] +# Iin = substr_inhibit(S_IN, KS_IN) +# grad_Ih2 = grad_non_compet_inhibit(S_h2, KIs_h2) + +# grad_rhos[:] = ks * X_bio * Iph * Iin +# grad_rhos[:-1] *= substr_inhibit(substrates, Ks) * grad_Ih2 +# if S_va > 0: grad_rhos[1] *= 1/(1+S_bu/S_va) +# if S_bu > 0: grad_rhos[2] *= 1/(1+S_va/S_bu) + +# grad_rhos[-1] *= grad_substr_inhibit(S_h2, K_h2) +# stoichio = f_stoichio(state_arr) + +# Q = state_arr[45] +# return -Q/V_liq + np.dot(grad_rhos, stoichio[[5,6,7,8,10]]) + kLa*stoichio[-3] + +#%% +# ============================================================================= +# ADM1_p_extension class +# ============================================================================= +_mmp = ospath.join(data_path, 'process_data/_mmp.tsv') + +@chemicals_user +class ADM1p(ADM1): + """ + Anaerobic Digestion Model No.1 with P extension. [1]_, [2]_, [3]_. + Compatible with `mASM2d`. + + Parameters + ---------- + components : class:`CompiledComponents`, optional + Components corresponding to each entry in the stoichiometry array, + defaults to thermosteam.settings.chemicals. + path : str, optional + Alternative file path for the Petersen matrix. The default is None. + f_sI_xb : float, optional + fraction of soluble inerts from biomass. The default is 0. + f_ch_xb : float, optional + fraction of carbohydrates from biomass. The default is 0.275. + f_pr_xb : float, optional + fraction of proteins from biomass. The default is 0.275. + f_li_xb : float, optional + fraction of lipids from biomass. The default is 0.35. + f_xI_xb : float, optional + fraction of particulate inerts from biomass. The default is 0.1. + f_ac_PHA : float, optional + Yield of acetate on PHA [kg COD/kg COD]. The default is 0.4. + f_bu_PHA : float, optional + Yield of butyrate on PHA [kg COD/kg COD]. The default is 0.1. + f_pro_PHA : float, optional + Yield of propionate on PHA [kg COD/kg COD]. The default is 0.4. + f_va_PHA : float, optional + Yield of valerate on PHA [kg COD/kg COD]. The default is 0.1. + Y_PO4 : float, optional + Yield of biomass on phosphate [kmol P/kg COD]. The default is 0.013. + K_A : float, optional + VFAs half saturation coefficient for PHA storage [kg COD/m3]. The default is 0.004. + K_PP : float, optional + Half saturation coefficient for polyphosphate [kmol PP/kg PAO COD]. + The default is 0.00032. + q_PHA : float, optional + Rate constant for storage of PHA [d^(-1)]. The default is 3. + b_PAO : float, optional + Lysis rate of PAOs [d^(-1)]. The default is 0.2. + b_PP : float, optional + Lysis rate of polyphosphates [d^(-1)]. The default is 0.2. + b_PHA : float, optional + Lysis rate of PHAs [d^(-1)]. The default is 0.2. + KS_IP : float, optional + P limitation for inorganic phosphorous [kmol P/m3]. + The default is 2e-5. + pKa_base : iterable[float], optional + pKa (equilibrium coefficient) values of acid-base pairs at the base + temperature, unitless, following the order of `ADM1p._acid_base_pairs`. + The default is [14, 9.25, 6.37, 10.32, 2.12, 7.21, 12.32, 4.76, 4.88, 4.82, 4.86]. + Ka_dH : iterable[float], optional + Heat of reaction of each acid-base pair at base temperature [J/mol], + following the order of `ADM1_p_extension._acid_base_pairs`. The default is + [55900, 51965, 17400, 14600, -7500, 3000, 15000, 0, 0, 0, 0]. + + See Also + -------- + :class:`qsdsan.processes.ADM1` + :class:`qsdsan.processes.mASM2d` + + Examples + -------- + >>> from qsdsan import processes as pc + >>> cmps = pc.create_adm1p_cmps() + >>> adm1_p = pc.ADM1p() + >>> adm1_p.show() + ADM1p([hydrolysis_carbs, hydrolysis_proteins, hydrolysis_lipids, uptake_sugars, uptake_amino_acids, uptake_LCFA, uptake_valerate, uptake_butyrate, uptake_propionate, uptake_acetate, uptake_h2, decay_Xsu, decay_Xaa, decay_Xfa, decay_Xc4, decay_Xpro, decay_Xac, decay_Xh2, storage_Sva_in_XPHA, storage_Sbu_in_XPHA, storage_Spro_in_XPHA, storage_Sac_in_XPHA, lysis_XPAO, lysis_XPP, lysis_XPHA, h2_transfer, ch4_transfer, IC_transfer]) + + References + ---------- + .. [1] Batstone, D. J.; Keller, J.; Angelidaki, I.; Kalyuzhnyi, S. V; + Pavlostathis, S. G.; Rozzi, A.; Sanders, W. T. M.; Siegrist, H.; + Vavilin, V. A. The IWA Anaerobic Digestion Model No 1 (ADM1). + Water Sci. Technol. 2002, 45 (10), 65–73. + .. [2] Rosen, C.; Jeppsson, U. Aspects on ADM1 Implementation within + the BSM2 Framework; Lund, 2006. + .. [3] Flores-Alsina, X.; Solon, K.; Kazadi Mbamba, C.; Tait, S.; + Gernaey, K. V.; Jeppsson, U.; Batstone, D. J. + Modelling phosphorus (P), sulfur (S) and iron (FE) interactions for + dynamic simulations of anaerobic digestion processes. Water Research. 2016, + 95, 370–382. + """ + + _stoichio_params = (*ADM1._stoichio_params[5:], + 'f_sI_xb', 'f_ch_xb', 'f_pr_xb', 'f_li_xb', 'f_xI_xb', + 'f_ac_PHA', 'f_bu_PHA', 'f_pro_PHA', 'f_va_PHA', + 'Y_PO4', 'K_XPP', 'Mg_XPP') + + _kinetic_params = (*ADM1._kinetic_params, 'KS_IP', ) + + _acid_base_pairs = (('H+', 'OH-'), ('NH4+', 'NH3'), + ('CO2', 'HCO3-'), ('HCO3-', 'CO3-2'), + ('H3PO4', 'H2PO4-'), ('H2PO4-', 'HPO4-2'), ('HPO4-2', 'PO4-3'), + ('HAc', 'Ac-'),('HPr', 'Pr-'), + ('HBu', 'Bu-'), ('HVa', 'Va-')) + _precipitates = ('X_CaCO3', 'X_struv', 'X_newb', 'X_ACP', 'X_MgCO3', 'X_AlPO4', 'X_FePO4') + + _biogas_IDs = ('S_h2', 'S_ch4', 'S_IC') + + def __new__(cls, components=None, path=None, + f_sI_xb=0, f_ch_xb=0.275, f_pr_xb=0.275, f_li_xb=0.350, + f_fa_li=0.95, f_bu_su=0.13, f_pro_su=0.27, f_ac_su=0.41, + f_va_aa=0.23, f_bu_aa=0.26, f_pro_aa=0.05, f_ac_aa=0.4, + f_ac_fa=0.7, f_pro_va=0.54, f_ac_va=0.31, f_ac_bu=0.8, f_ac_pro=0.57, + f_ac_PHA=0.4, f_bu_PHA=0.1, f_pro_PHA=0.4, + Y_su=0.1, Y_aa=0.08, Y_fa=0.06, Y_c4=0.06, Y_pro=0.04, Y_ac=0.05, Y_h2=0.06, Y_PO4=0.013, + q_dis=0.5, q_ch_hyd=10, q_pr_hyd=10, q_li_hyd=10, + k_su=30, k_aa=50, k_fa=6, k_c4=20, k_pro=13, k_ac=8, k_h2=35, + K_su=0.5, K_aa=0.3, K_fa=0.4, K_c4=0.2, K_pro=0.1, K_ac=0.15, K_h2=7e-6, + K_A=4e-3, K_PP=32e-5, + b_su=0.02, b_aa=0.02, b_fa=0.02, b_c4=0.02, b_pro=0.02, b_ac=0.02, b_h2=0.02, + q_PHA=3, b_PAO=0.2, b_PP=0.2, b_PHA=0.2, + KI_h2_fa=5e-6, KI_h2_c4=1e-5, KI_h2_pro=3.5e-6, KI_nh3=1.8e-3, KS_IN=1e-4, KS_IP=2e-5, + pH_limits_aa=(4,5.5), pH_limits_ac=(6,7), pH_limits_h2=(5,6), + T_base=298.15, pKa_base=[14, 9.25, 6.37, 10.32, 2.12, 7.21, 12.32, 4.76, 4.88, 4.82, 4.86], + Ka_dH=[55900, 51965, 17400, 14600, -7500, 3000, 15000, 0, 0, 0, 0], + kLa=200, K_H_base=[7.8e-4, 1.4e-3, 3.5e-2], + K_H_dH=[-4180, -14240, -19410], + k_mmp=(5.0, 300, 0.05, 150, 50, 1.0, 1.0), + pKsp=(6.45, 13.16, 5.8, 23, 7, 21, 26), + K_dis=(1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0), + K_AlOH=0.001, K_FeOH=0.001, + **kwargs): + + cmps = _load_components(components) + + if not path: path = _path + self = Processes.load_from_file(path, + components=cmps, + conserved_for=('C', 'N', 'P'), + parameters=cls._stoichio_params, + compile=False) + + mmp = Processes.load_from_file(_mmp, components=cmps, + conserved_for=(), compile=False) + mmp_stoichio = {} + df = load_data(_mmp) + for i, j in df.iterrows(): + j.dropna(inplace=True) + key = j.index[j == 1][0] + j = j.to_dict() + j.pop(key) + mmp_stoichio[key] = j + mol_to_mass = cmps.chem_MW / cmps.i_mass + Ksp_mass = np.array([10**(-p) for p in pKsp]) # mass in kg/m3 + i = 0 + for pd, xid in zip(mmp, cls._precipitates): + for k,v in mmp_stoichio[xid].items(): + m2m = mol_to_mass[cmps.index(k)] + Ksp_mass[i] *= m2m**abs(v) + i += 1 + pd._stoichiometry *= mol_to_mass + pd.ref_component = xid + self.extend(mmp) + + for i in cls._biogas_IDs: + new_p = Process('%s_transfer' % i.lstrip('S_'), + reaction={i:-1}, + ref_component=i, + conserved_for=()) + self.append(new_p) + self.compile(to_class=cls) + + stoichio_vals = (f_fa_li, f_bu_su, f_pro_su, f_ac_su, 1-f_bu_su-f_pro_su-f_ac_su, + f_va_aa, f_bu_aa, f_pro_aa, f_ac_aa, 1-f_va_aa-f_bu_aa-f_pro_aa-f_ac_aa, + f_ac_fa, 1-f_ac_fa, f_pro_va, f_ac_va, 1-f_pro_va-f_ac_va, + f_ac_bu, 1-f_ac_bu, f_ac_pro, 1-f_ac_pro, + Y_su, Y_aa, Y_fa, Y_c4, Y_pro, Y_ac, Y_h2, + # new parameters + f_sI_xb, f_ch_xb, f_pr_xb, f_li_xb, 1-f_sI_xb-f_ch_xb-f_pr_xb-f_li_xb, + f_ac_PHA, f_bu_PHA, f_pro_PHA, 1-f_ac_PHA-f_bu_PHA-f_pro_PHA, + Y_PO4*P_mw, cmps.X_PP.i_K, cmps.X_PP.i_Mg) + pH_LLs = np.array([pH_limits_aa[0]]*6 + [pH_limits_ac[0], pH_limits_h2[0]]) + pH_ULs = np.array([pH_limits_aa[1]]*6 + [pH_limits_ac[1], pH_limits_h2[1]]) + + ks = np.array((q_ch_hyd, q_pr_hyd, q_li_hyd, + k_su, k_aa, k_fa, k_c4, k_c4, k_pro, k_ac, k_h2, + b_su, b_aa, b_fa, b_c4, b_pro, b_ac, b_h2, + q_PHA, q_PHA, q_PHA, q_PHA, b_PAO, b_PP, b_PHA)) + + Ks = np.array((K_su, K_aa, K_fa, K_c4, K_c4, K_pro, K_ac, K_h2, + #!!! new + K_A, K_PP)) + + KIs_h2 = np.array((KI_h2_fa, KI_h2_c4, KI_h2_c4, KI_h2_pro)) + K_H_base = np.array(K_H_base) + K_H_dH = np.array(K_H_dH) + Ka_base = np.array([10**(-pKa) for pKa in pKa_base]) + Ka_dH = np.array(Ka_dH) + root = TempState() + dct = self.__dict__ + dct.update(kwargs) + + self.set_rate_function(rhos_adm1_p_extension) + dct['_parameters'] = dict(zip(cls._stoichio_params, stoichio_vals)) + self.rate_function._params = dict(zip(cls._kinetic_params, + [ks, Ks, pH_ULs, pH_LLs, KS_IN*N_mw, + KI_nh3, KIs_h2, Ka_base, Ka_dH, + K_H_base, K_H_dH, kLa, + T_base, self._components, root, + #!!! new parameter + KS_IP*P_mw])) + dct['solve_pH'] = solve_pH + dct['dydt_Sh2_AD'] = dydt_Sh2_AD + dct['grad_dydt_Sh2_AD'] = grad_dydt_Sh2_AD + return self + + def set_half_sat_K(self, K, process): + '''Set the substrate half saturation coefficient [kg/m3] for a process given its ID.''' + i = self._find_index(process) + if i < 11: + self.rate_function._params['half_sat_coeffs'][i-3] = K + else: + ValueError('To set "K_A", specify process = -2; to set "K_PP", specify process = -1,' + f'not {process}') + + def set_pH_inhibit_bounds(self, process, lower=None, upper=None): + '''Set the upper and/or lower limit(s) of pH inhibition [unitless] for a process given its ID.''' + i = self._find_index(process) - 3 + dct = self.rate_function._params + if lower is None: lower = dct['pH_LLs'][i] + else: dct['pH_LLs'][i] = lower + if upper is None: upper = dct['pH_ULs'][i] + else: dct['pH_ULs'][i] = upper + if lower >= upper: + raise ValueError(f'lower limit for pH inhibition of {process} must ' + f'be lower than the upper limit, not {[lower, upper]}') + + def set_h2_inhibit_K(self, KI, process): + '''Set the H2 inhibition coefficient [kg/m3] for a process given its ID.''' + i = self._find_index(process) + self.rate_function._params['KIs_h2'][i-5] = KI + + + def set_KS_IP(self, K): + '''Set inhibition coefficient for inorganic phosphorous as a secondary + substrate [M phosphorous].''' + self.rate_function._params['KS_IP'] = K * P_mw + + def check_stoichiometric_parameters(self): '''Check whether product COD fractions sum up to 1 for each process.''' stoichio = self.parameters diff --git a/qsdsan/processes/_asm2d.py b/qsdsan/processes/_asm2d.py index 47352101..438ddf0a 100644 --- a/qsdsan/processes/_asm2d.py +++ b/qsdsan/processes/_asm2d.py @@ -367,7 +367,6 @@ class ASM2d(CompiledProcesses): 'k_PRE', 'k_RED', 'K_ALK_PRE') - def __new__(cls, components=None, iN_SI=0.01, iN_SF=0.03, iN_XI=0.02, iN_XS=0.04, iN_BM=0.07, iP_SI=0.0, iP_SF=0.01, iP_XI=0.01, iP_XS=0.01, iP_BM=0.02, @@ -464,7 +463,7 @@ def acid_base_rxn(h_ion, ionic_states, Ka): ac = Ac * Kac/(Kac + h_ion) co2, hco3, co3 = ion_speciation(h_ion, Kc1, Kc2) * IC h3po4, h2po4, hpo4, po4 = ion_speciation(h_ion, Kp1, Kp2, Kp3) * IP - return K + 2*Mg + 2*Ca + Na + h_ion + nh4 - Cl - NOx - oh_ion - ac - hco3 - co3*2 - h2po4 - 2*hpo4 - 3*po4 + return K + 2*Mg + 2*Ca + Na + h_ion + nh4 - Cl - NOx - oh_ion - ac - hco3 - 2*co3 - h2po4 - 2*hpo4 - 3*po4 def ion_speciation(h_ion, *Kas): n = len(Kas) @@ -620,20 +619,85 @@ def _rhos_masm2d(state_arr, params, acceptor_dependent_decay=True): kLa_n2, kLa_co2 = params['kLa'] KH_n2, KH_co2 = params['K_Henry'] # assume already temperature-corrected rhos[26] = kLa_n2*(S_N2 - KH_n2*p_n2_air/mass2mol[1]*1e3) # M/atm * atm / mol/g * 1000 mg/g = mg/L - rhos[27] = kLa_co2*(co2 - KH_co2*p_co2_air/mass2mol[1]*1e3) # M/atm * atm / mol/g * 1000 mg/g = mg/L + rhos[27] = kLa_co2*(co2 - KH_co2*p_co2_air/mass2mol[8]*1e3) # M/atm * atm / mol/g * 1000 mg/g = mg/L return rhos +#%% @chemicals_user class mASM2d(CompiledProcesses): ''' Modified ASM2d. [1]_, [2]_ Compatible with `ADM1p` for plant-wide simulations. + Includes an algebraic pH solver, precipitation/dissolution of common minerals, + and gas stripping/dissolution. Parameters ---------- + components : :class:`CompiledComponents`, optional + Can be created with the `create_masm2d_cmps` function. + path : str, optional + File path for an alternative Petersen Matrix. The default is None. + electron_acceptor_dependent_decay : bool, optional + Whether biomass decay kinetics is dependent on concentrations of + electron acceptors. The default is True. + k_h : float, optional + Hydrolysis rate constant, in [d^(-1)]. The default is 3.0. + eta_NO3_Hl : float, optional + Anoxic reduction factor for endogenous respiration of heterotrophs, + unitless. The default is 0.5. + eta_NO3_PAOl : float, optional + Anoxic reduction factor for lysis of PAOs, unitless. The default is 0.33. + eta_NO3_PPl : float, optional + Anoxic reduction factor for lysis of PP, unitless. The default is 0.33. + eta_NO3_PHAl : float, optional + Anoxic reduction factor for lysis of PHA, unitless. The default is 0.33. + eta_NO3_AUTl : float, optional + Anoxic reduction factor for decay of autotrophs, unitless. The default is 0.33. + K_NO3_AUT : float, optional + Half saturation coefficient of NOx- for autotrophs [mg-N/L]. The default is 0.5. + K_P_S : float, optional + Half saturation coefficient of ortho-P for PP storage [mg-P/L]. The default is 0.2. + k_mmp : iterable[float], optional + Rate constants for multi-mineral precipitation/dissolution + [mg-precipitate/L/(unit of solubility product)/d]. Follows the exact order + of `mASM2d._precipitates`. The default is (5.0, 300, 0.05, 150, 50, 1.0, 1.0). + pKsp : iterable[float], optional + Solubility of minerals, in order of `mASM2d._precipitates`. + The default is (6.45, 13.16, 5.8, 23, 7, 21, 26). + K_dis : iterable[float], optional + Saturation coefficient for the switching function of mineral dissolution + [mg-precipitate/L]. The default is (1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0). + K_AlOH : float, optional + Half saturation coefficient of aluminum hydroxide for AlPO4 precipitation + [mg-Al(OH)3/L]. The default is 0.001. + K_FeOH : float, optional + Half saturation coefficient of ferric hydroxide for FePO4 precipitation + [mg-Fe(OH)3/L]. The default is 0.001. + kLa : iterable[float], optional + Gas transfer rate constant for gas stripping/dissolution [d^(-1)], + following the order of `mASM2d._gas_stripping`. The default is (3.0, 3.0). + K_Henry : iterable[float], optional + Henry's law constants [mol/L/atm], following the order of + `mASM2d._gas_stripping`. The default is (6.5e-4, 3.5e-2). + pKa : iterable[float], optional + Equilibrium coefficient values of acid-base pairs, unitless, + following the order of `mASM2d._acid_base_pairs`. + The default is (14, 9.25, 6.37, 10.32, 2.12, 7.21, 12.32, 4.76). + + + See Also + -------- + :class:`qsdsan.processes.ASM2d` + :class:`qsdsan.processes.ADM1p` Examples -------- + >>> import qsdsan.processes as pc + >>> cmps = pc.create_masm2d_cmps() + >>> asm = pc.mASM2d() + >>> asm.show() + mASM2d([aero_hydrolysis, anox_hydrolysis, anae_hydrolysis, hetero_growth_S_F, hetero_growth_S_A, denitri_S_F, denitri_S_A, ferment, hetero_lysis, storage_PHA, aero_storage_PP, anox_storage_PP, PAO_aero_growth_PHA, PAO_anox_growth, PAO_lysis, PP_lysis, PHA_lysis, auto_aero_growth, auto_lysis, CaCO3_precipitation_dissolution, struvite_precipitation_dissolution, newberyite_precipitation_dissolution, ACP_precipitation_dissolution, MgCO3_precipitation_dissolution, AlPO4_precipitation_dissolution, FePO4_precipitation_dissolution, N2_stripping, IC_stripping]) + References ---------- @@ -688,15 +752,14 @@ def __new__(cls, components=None, path=None, electron_acceptor_dependent_decay=T K_NH4_H=0.05, K_NH4_PAO=0.05, K_NH4_AUT=1.0, K_P_H=0.01, K_P_PAO=0.01, K_P_AUT=0.01, K_P_S=0.2, K_PP=0.01, K_MAX=0.34, K_IPP=0.02, K_PHA=0.01, - #!!! kLa and/or solubility values for gas stripping - #!!! precipitation kinetics k_mmp=(5.0, 300, 0.05, 150, 50, 1.0, 1.0), pKsp=(6.45, 13.16, 5.8, 23, 7, 21, 26), K_dis=(1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0), K_AlOH=0.001, K_FeOH=0.001, kLa=(3.0, 3.0), K_Henry=(6.5e-4, 3.5e-2), pKa=(14, 9.25, 6.37, 10.32, 2.12, 7.21, 12.32, 4.76), - **kwargs): + **kwargs): + if not path: path = _mpath @@ -779,7 +842,7 @@ def __new__(cls, components=None, path=None, electron_acceptor_dependent_decay=T K_PP, K_MAX, K_IPP, K_PHA, np.array(k_mmp), Ksp_mass, np.array(K_dis), K_AlOH, K_FeOH, - np.array(kLa), np.array(K_Henry), Ka, cmps, + kLa, K_Henry, Ka, cmps, ) self.rate_function._params = dict(zip(cls._kinetic_params, kinetic_vals)) From 386b7581828cfd20324a7731549eafb1fde73275 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Wed, 12 Jun 2024 12:57:40 -0700 Subject: [PATCH 363/424] temporary removal of ADM1p doctest --- qsdsan/processes/_adm1_p_extension.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/qsdsan/processes/_adm1_p_extension.py b/qsdsan/processes/_adm1_p_extension.py index 1d7793f4..318f1e9b 100644 --- a/qsdsan/processes/_adm1_p_extension.py +++ b/qsdsan/processes/_adm1_p_extension.py @@ -884,12 +884,7 @@ class ADM1p(ADM1): Examples -------- - >>> from qsdsan import processes as pc - >>> cmps = pc.create_adm1p_cmps() - >>> adm1_p = pc.ADM1p() - >>> adm1_p.show() - ADM1p([hydrolysis_carbs, hydrolysis_proteins, hydrolysis_lipids, uptake_sugars, uptake_amino_acids, uptake_LCFA, uptake_valerate, uptake_butyrate, uptake_propionate, uptake_acetate, uptake_h2, decay_Xsu, decay_Xaa, decay_Xfa, decay_Xc4, decay_Xpro, decay_Xac, decay_Xh2, storage_Sva_in_XPHA, storage_Sbu_in_XPHA, storage_Spro_in_XPHA, storage_Sac_in_XPHA, lysis_XPAO, lysis_XPP, lysis_XPHA, h2_transfer, ch4_transfer, IC_transfer]) - + References ---------- .. [1] Batstone, D. J.; Keller, J.; Angelidaki, I.; Kalyuzhnyi, S. V; From 25ed34ff1852b4cded33dcf0206632bc9bf885e4 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Thu, 13 Jun 2024 12:56:28 -0700 Subject: [PATCH 364/424] enable Processes initiation with data frame --- qsdsan/_process.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/qsdsan/_process.py b/qsdsan/_process.py index 05bf7597..7196b584 100644 --- a/qsdsan/_process.py +++ b/qsdsan/_process.py @@ -976,13 +976,14 @@ def __repr__(self): _default_data = None @classmethod - def load_from_file(cls, path='', components=None, + def load_from_file(cls, path='', components=None, data=None, conserved_for=('COD', 'N', 'P', 'charge'), parameters=(), use_default_data=False, store_data=False, compile=True, **compile_kwargs): """ Create :class:`CompiledProcesses` object from a table of process IDs, stoichiometric - coefficients, and rate equations stored in a .tsv, .csv, or Excel file. + coefficients, and rate equations stored in a .tsv, .csv, or Excel file, or as + a `DataFrame`. Parameters ---------- @@ -991,6 +992,8 @@ def load_from_file(cls, path='', components=None, components : :class:`CompiledComponents`, optional Components corresponding to the columns in the stoichiometry matrix, to all components set in the system (i.e., through :func:`set_thermo`). + data : :class:`pandas.DataFrame`, optional + Data frame of the Petersen matrix. conserved_for : tuple[str], optional Materials subject to conservation rules, must have corresponding 'i\_' attributes for the components. Applied to all processes. @@ -1023,16 +1026,18 @@ def load_from_file(cls, path='', components=None, """ if use_default_data and cls._default_data is not None: data = cls._default_data + elif path: + data = load_data(path=path, index_col=0, na_values=0) else: - data = load_data(path=path, index_col=None, na_values=0) - + if data is None: return None + cmps = _load_components(components) cmp_IDs = [i for i in data.columns if i in cmps.IDs] data.dropna(how='all', subset=cmp_IDs, inplace=True) new = cls(()) for i, proc in data.iterrows(): - ID = proc[0] + ID = i stoichio = proc[cmp_IDs] if data.columns[-1] in cmp_IDs: rate_eq = None else: From d5316d9bca17127e566be1d333cb82338161a4a5 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Thu, 13 Jun 2024 12:57:18 -0700 Subject: [PATCH 365/424] consolidate common functions in process models --- qsdsan/processes/__init__.py | 36 ++++++++++++++++++++++++++++- qsdsan/processes/_adm1.py | 45 ++++++++---------------------------- qsdsan/processes/_asm2d.py | 20 +++------------- 3 files changed, 47 insertions(+), 54 deletions(-) diff --git a/qsdsan/processes/__init__.py b/qsdsan/processes/__init__.py index 3566c3ed..dfeda1eb 100644 --- a/qsdsan/processes/__init__.py +++ b/qsdsan/processes/__init__.py @@ -7,12 +7,46 @@ This module is developed by: Yalin Li + + Joy Zhang This module is under the University of Illinois/NCSA Open Source License. Please refer to https://github.com/QSD-Group/QSDsan/blob/main/LICENSE.txt for license details. ''' +from numpy import arange, cumprod, exp + +def ion_speciation(h_ion, *Kas): + n = len(Kas) + out = h_ion ** arange(n, -1, -1) * cumprod([1.0, *Kas]) + return out/sum(out) + +substr_inhibit = Monod = lambda S, K: S/(S+K) + +non_compet_inhibit = lambda S, K: K/(K+S) + +grad_non_compet_inhibit = lambda S, K: -K/(K+S)**2 + +grad_substr_inhibit = lambda S, K: K/(K+S)**2 + +def mass2mol_conversion(cmps): + '''conversion factor from kg[measured_as]/m3 to mol[component]/L''' + return cmps.i_mass / cmps.chem_MW + +R = 8.3145e-2 # Universal gas constant, [bar/M/K] + +def T_correction_factor(T1, T2, delta_H): + """compute temperature correction factor for equilibrium constants based on + the Van't Holf equation.""" + if T1 == T2: return 1 + return exp(delta_H/(R*100) * (1/T1 - 1/T2)) # R converted to SI + +class TempState: + def __init__(self): + self.data = {} + +#%% from ._aeration import * from ._asm1 import * from ._asm2d import * @@ -45,4 +79,4 @@ *_decay.__all__, *_kinetic_reaction.__all__, *_pm2.__all__, - ) \ No newline at end of file + ) diff --git a/qsdsan/processes/_adm1.py b/qsdsan/processes/_adm1.py index 84f8a185..819b3212 100644 --- a/qsdsan/processes/_adm1.py +++ b/qsdsan/processes/_adm1.py @@ -15,19 +15,23 @@ from thermosteam.utils import chemicals_user from thermosteam import settings -from chemicals.elements import molecular_weight as get_mw +# from chemicals.elements import molecular_weight as get_mw from qsdsan import Component, Components, Process, Processes, CompiledProcesses import numpy as np from qsdsan.utils import ospath, data_path from scipy.optimize import brenth from warnings import warn +from . import ( + non_compet_inhibit, grad_non_compet_inhibit, + substr_inhibit, grad_substr_inhibit, + mass2mol_conversion, + T_correction_factor, R, + TempState + ) __all__ = ('create_adm1_cmps', 'ADM1', - 'non_compet_inhibit', 'grad_non_compet_inhibit', - 'substr_inhibit', 'grad_substr_inhibit', - 'mass2mol_conversion', 'T_correction_factor', 'pH_inhibit', 'Hill_inhibit', - 'rhos_adm1', 'TempState',) + 'rhos_adm1', ) _path = ospath.join(data_path, 'process_data/_adm1.tsv') _load_components = settings.get_default_chemicals @@ -177,31 +181,6 @@ def create_adm1_cmps(set_thermo=True): # kinetic rate functions # ============================================================================= -R = 8.3145e-2 # Universal gas constant, [bar/M/K] - -def non_compet_inhibit(Si, Ki): - return Ki/(Ki+Si) - -def grad_non_compet_inhibit(Si, Ki): - return -Ki/(Ki+Si)**2 - -def substr_inhibit(Si, Ki): - return Si/(Ki+Si) - -def grad_substr_inhibit(Si, Ki): - return Ki/(Ki+Si)**2 - -def mass2mol_conversion(cmps): - '''conversion factor from kg[measured_as]/m3 to mol[component]/L''' - return cmps.i_mass / cmps.chem_MW - -def T_correction_factor(T1, T2, delta_H): - """compute temperature correction factor for equilibrium constants based on - the Van't Holf equation.""" - if T1 == T2: return 1 - return np.exp(delta_H/(R*100) * (1/T1 - 1/T2)) # R converted to SI - - def acid_base_rxn(h_ion, weak_acids_tot, Kas): # h, nh4, hco3, ac, pr, bu, va = mols # S_cat, S_an, S_IN, S_IC, S_ac, S_pro, S_bu, S_va = weak_acids_tot # in M @@ -369,12 +348,6 @@ def grad_dydt_Sh2_AD(S_h2, state_arr, h, params, f_stoichio, V_liq, S_h2_in): # ============================================================================= # ADM1 class # ============================================================================= -class TempState: - def __init__(self): - self.data = {} - - # def append(self, value): - # self.data += [value] @chemicals_user class ADM1(CompiledProcesses): diff --git a/qsdsan/processes/_asm2d.py b/qsdsan/processes/_asm2d.py index 438ddf0a..b612eed8 100644 --- a/qsdsan/processes/_asm2d.py +++ b/qsdsan/processes/_asm2d.py @@ -14,8 +14,9 @@ from thermosteam import settings from qsdsan import Component, Components, Process, Processes, CompiledProcesses from ..utils import ospath, data_path, load_data +from . import Monod, ion_speciation from scipy.optimize import brenth -from math import log10 +# from math import log10 __all__ = ('create_asm2d_cmps', 'ASM2d', @@ -453,8 +454,6 @@ def __new__(cls, components=None, p_n2_air = 0.78 # atm p_co2_air = 3.947e-4 -Monod = lambda S, K: S/(S+K) - def acid_base_rxn(h_ion, ionic_states, Ka): K, Mg, Ca, Na, Cl, NOx, NH, IC, IP, Ac = ionic_states # in M Kw, Knh, Kc1, Kc2, Kp1, Kp2, Kp3, Kac = Ka @@ -465,11 +464,6 @@ def acid_base_rxn(h_ion, ionic_states, Ka): h3po4, h2po4, hpo4, po4 = ion_speciation(h_ion, Kp1, Kp2, Kp3) * IP return K + 2*Mg + 2*Ca + Na + h_ion + nh4 - Cl - NOx - oh_ion - ac - hco3 - 2*co3 - h2po4 - 2*hpo4 - 3*po4 -def ion_speciation(h_ion, *Kas): - n = len(Kas) - out = h_ion ** np.arange(n, -1, -1) * np.cumprod([1.0, *Kas]) - return out/sum(out) - def solve_pH(state_arr, Ka, unit_conversion): cmps_in_M = state_arr[:31] * unit_conversion *1e-3 # S_K, S_Mg, S_Ca, S_Na, S_Cl, S_NO3, S_NH4, S_IC, S_PO4, S_A @@ -540,9 +534,6 @@ def _rhos_masm2d(state_arr, params, acceptor_dependent_decay=True): X_AlOH, X_AlPO4, X_FeOH, X_FePO4, S_Na, S_Cl \ = state_arr[:30] - # S_O2, S_N2, S_NH4, S_NO3, S_PO4, S_F, S_A = state_arr[:7] - # X_S, X_H, X_PAO, X_PP, X_PHA, X_AUT = state_arr[12:18] - ############# biological processes ############### nutrients = Monod(S_NH4, Ks_nh4) * Monod(S_PO4, Ks_po4) @@ -578,12 +569,7 @@ def _rhos_masm2d(state_arr, params, acceptor_dependent_decay=True): if acceptor_dependent_decay: rhos[8] *= (aero[1] + eta_decay[0]*(1-aero[1])*anox[1]) rhos[14:17] *= (aero[3] +eta_decay[1:4]*(1-aero[3])*anox[3]) - rhos[18] *= (aero[5] + eta_decay[4]*(1-aero[5])*anox[5]) - - # S_IC, S_Mg = state_arr[[8,10]] - # S_Ca, X_CaCO3, X_struv, X_newb, X_ACP, X_MgCO3, \ - # X_AlOH, X_AlPO4, X_FeOH, X_FePO4 = \ - # state_arr[18:28] + rhos[18] *= (aero[5] + eta_decay[4]*(1-aero[5])*anox[5]) ########## pH ############ mass2mol = params['mass2mol'] From cea2a3c030ef28065ecd411e4ebc561855a1c9ba Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Thu, 13 Jun 2024 12:57:30 -0700 Subject: [PATCH 366/424] debug `ADM1p` --- qsdsan/processes/_adm1_p_extension.py | 509 +++++++++++++------------- 1 file changed, 259 insertions(+), 250 deletions(-) diff --git a/qsdsan/processes/_adm1_p_extension.py b/qsdsan/processes/_adm1_p_extension.py index 318f1e9b..007693ff 100644 --- a/qsdsan/processes/_adm1_p_extension.py +++ b/qsdsan/processes/_adm1_p_extension.py @@ -18,14 +18,16 @@ from thermosteam.utils import chemicals_user from thermosteam import settings from chemicals.elements import molecular_weight as get_mw -from qsdsan import Component, Components, Process, Processes, CompiledProcesses +from qsdsan import Components, Process, Processes from qsdsan.processes import ( create_adm1_cmps, create_asm2d_cmps, create_masm2d_cmps, - T_correction_factor, + T_correction_factor, R, + ion_speciation, non_compet_inhibit, grad_non_compet_inhibit, + Monod, substr_inhibit, grad_substr_inhibit, mass2mol_conversion, @@ -35,14 +37,17 @@ ) from qsdsan.utils import ospath, data_path, load_data from scipy.optimize import brenth -from warnings import warn import numpy as np __all__ = ('create_adm1_p_extension_cmps', 'ADM1_p_extension', - 'rhos_adm1_p_extension') + 'rhos_adm1_p_extension', + 'create_adm1p_cmps', + 'ADM1p', + 'rhos_adm1p') _path = ospath.join(data_path, 'process_data/_adm1_p_extension.tsv') +_mmp = ospath.join(data_path, 'process_data/_mmp.tsv') _load_components = settings.get_default_chemicals #%% @@ -61,40 +66,6 @@ def create_adm1_p_extension_cmps(set_thermo=True): S_IP = c2d.S_PO4.copy('S_IP') - # c1.S_su.i_mass = c1.X_ch.i_mass = 0.9375 - # c1.S_su.f_Vmass_Totmass = c1.X_ch.f_Vmass_Totmass = 0.68 - # c1.X_li.i_mass = 0.6375 - # c1.X_li.f_Vmass_Totmass = 1. - - c1.S_aa.i_C = c1.X_pr.i_C = 0.36890 - c1.S_aa.i_N = c1.X_pr.i_N = 0.11065 - c1.S_aa.i_P = c1.X_pr.i_P = 0. - c1.S_aa.i_mass = c1.X_pr.i_mass = 0.737648 - c1.S_aa.f_Vmass_Totmass = c1.X_pr.f_Vmass_Totmass = 0.864 - - c1.S_fa._formula = None - c1.S_fa.chem_MW = 1. - c1.S_fa.i_C = 0.25685 - # c1.S_fa.i_mass = 1/2.9200 - - c1.S_I.i_C = c1.X_I.i_C = 0.36178 - c1.S_I.i_N = c1.X_I.i_N = 0.06003 - c1.S_I.i_P = c1.X_I.i_P = 6.49e-3 - c1.S_I.i_mass = c1.X_I.i_mass = 0.75 - c1.S_I.f_Vmass_Totmass = c1.X_I.f_Vmass_Totmass = 0.85 - - for cmp in (c1.S_aa, c1.X_pr, c1.S_fa, c1.S_I, c1.X_I): - cmp.i_NOD = None - - for cmp in (c1.X_su, c1.X_aa, c1.X_fa, c1.X_c4, c1.X_pro, c1.X_ac, c1.X_h2,): - cmp.i_C = 0.36612 - cmp.i_N = 0.08615 - cmp.i_P = 0.02154 - cmp.i_mass = 0.90 - cmp.f_Vmass_Totmass = 0.85 - cmp.i_NOD = None - - c1.refresh_constants() c = [*c1] Ss = c[:11] Xs = c[13:-3] # X_c is excluded @@ -108,17 +79,12 @@ def create_adm1_p_extension_cmps(set_thermo=True): if set_thermo: settings.set_thermo(cmps_adm1p) return cmps_adm1p -# create_adm1_p_extension_cmps() - #%% # ============================================================================= # kinetic rate functions # ============================================================================= -R = 8.3145e-2 # Universal gas constant, [bar/M/K] - - def acid_base_rxn(h_ion, weak_acids_tot, Kas): # S_cat, S_K, S_Mg, S_an, S_IN, S_IP, S_IC, S_ac, S_pro, S_bu, S_va = weak_acids_tot # in M S_cat, S_K, S_Mg, S_an, S_IN, S_IP = weak_acids_tot[:6] @@ -152,9 +118,9 @@ def solve_pH(state_arr, Ka, unit_conversion): co2 = weak_acids[6] - Ka[3] * weak_acids[6] / (Ka[3] + h) return h, nh3, co2 -rhos_adm1_p_extension = lambda state_arr, params: _rhos_adm1p(state_arr, params, h=None) +rhos_adm1_p_extension = lambda state_arr, params: _rhos_adm1_p_extension(state_arr, params, h=None) -def _rhos_adm1p(state_arr, params, h=None): +def _rhos_adm1_p_extension(state_arr, params, h=None): ks = params['rate_constants'] Ks = params['half_sat_coeffs'] @@ -284,7 +250,7 @@ def _rhos_adm1p(state_arr, params, h=None): def dydt_Sh2_AD(S_h2, state_arr, h, params, f_stoichio, V_liq, S_h2_in): state_arr[7] = S_h2 Q = state_arr[37] - rxn = _rhos_adm1p(state_arr, params, h=h) + rxn = _rhos_adm1_p_extension(state_arr, params, h=h) stoichio = f_stoichio(state_arr) # should return the stoichiometric coefficients of S_h2 for all processes return Q/V_liq*(S_h2_in - S_h2) + np.dot(rxn, stoichio) @@ -555,9 +521,6 @@ def check_stoichiometric_parameters(self): # ADM1p components, compatible with `mASM2d` # ============================================================================= -C_mw = get_mw({'C':1}) -N_mw = get_mw({'N':1}) -P_mw = get_mw({'P':1}) def create_adm1p_cmps(set_thermo=True): c1 = create_adm1_cmps(False) @@ -618,206 +581,191 @@ def create_adm1p_cmps(set_thermo=True): # ============================================================================= # kinetic rate functions # ============================================================================= +def adm1p_acid_base_rxn(h_ion, ionic_states, Ka): + K, Mg, Ca, Na, Cl, IC, IN, IP = ionic_states[:8] # in M + Kw, Knh, Kc1, Kc2, Kp1, Kp2, Kp3 = Ka[:7] + oh_ion = Kw/h_ion + nh4 = IN * h_ion/(Knh + h_ion) + vfas = ionic_states[-4:] * Ka[-4:]/(Ka[-4:] + h_ion) + co2, hco3, co3 = ion_speciation(h_ion, Kc1, Kc2) * IC + h3po4, h2po4, hpo4, po4 = ion_speciation(h_ion, Kp1, Kp2, Kp3) * IP + return K + 2*Mg + 2*Ca + Na + h_ion + nh4 - Cl - oh_ion - sum(vfas) - hco3 - 2*co3 - h2po4 - 2*hpo4 - 3*po4 + +def adm1p_solve_pH(state_arr, Ka, unit_conversion): + cmps_in_M = state_arr[:42] * unit_conversion + # K, Mg, Ca, Na, Cl, IC, IN, IP, Ac, Pr, Bu, Va + ions = cmps_in_M[[27, 28, 29, 39, 40, 9, 10, 11, 6, 5, 4, 3]] + h = brenth(adm1p_acid_base_rxn, 1e-14, 1.0, + args=(ions, Ka), + xtol=1e-12, maxiter=100) + return h + +rhos_p = np.zeros(35) # 28 kinetic processes (25 as defined in modified ADM1 + 7 mmp + 3 for gases) +Cs_p = np.empty(25) # 25 processes as defined in modified ADM1 + +rhos_adm1p = lambda state_arr, params: _rhos_adm1p(state_arr, params, h=None) -# def acid_base_rxn(h_ion, weak_acids_tot, Kas): -# # S_cat, S_K, S_Mg, S_an, S_IN, S_IP, S_IC, S_ac, S_pro, S_bu, S_va = weak_acids_tot # in M -# S_cat, S_K, S_Mg, S_an, S_IN, S_IP = weak_acids_tot[:6] -# # Kw, Ka_nh, Ka_h2po4, Ka_co2, Ka_ac, Ka_pr, Ka_bu, Ka_va = Kas -# Kw = Kas[0] -# oh_ion = Kw/h_ion -# nh3, hpo4, hco3, ac, pro, bu, va = Kas[1:] * weak_acids_tot[4:] / (Kas[1:] + h_ion) -# return S_cat + S_K + 2*S_Mg + h_ion + (S_IN - nh3) - S_an - oh_ion - hco3 - ac - pro - bu - va - 2*hpo4 - (S_IP - hpo4) - - -# rhos = np.zeros(35) # 28 kinetic processes (25 as defined in modified ADM1 + 7 mmp + 3 for gases) -# Cs = np.empty(25) # 25 processes as defined in modified ADM1 - -# def solve_pH(state_arr, Ka, unit_conversion): -# cmps_in_M = state_arr[:34] * unit_conversion -# # S_cat, S_K, S_Mg, S_an, S_IN, S_IP, S_IC, S_ac, S_pro, S_bu, S_va -# # Kw, Ka_nh, Ka_h2po4, Ka_co2, Ka_ac, Ka_pr, Ka_bu, Ka_va = Kas -# weak_acids = cmps_in_M[[31, 27, 28, 32, 10, 11, 9, 6, 5, 4, 3]] -# h = brenth(acid_base_rxn, 1e-14, 1.0, -# args=(weak_acids, Ka), -# xtol=1e-12, maxiter=100) -# return h - -# rhos_adm1_p_extension = lambda state_arr, params: _rhos_adm1p(state_arr, params, h=None) - -# def _rhos_adm1p(state_arr, params, h=None): -# ks = params['rate_constants'] -# Ks = params['half_sat_coeffs'] - -# cmps = params['components'] -# pH_ULs = params['pH_ULs'] -# pH_LLs = params['pH_LLs'] -# KS_IN = params['KS_IN'] -# KS_IP = params['KS_IP'] -# KI_nh3 = params['KI_nh3'] -# KIs_h2 = params['KIs_h2'] -# KHb = params['K_H_base'] -# Kab = params['Ka_base'] -# KH_dH = params['K_H_dH'] -# Ka_dH = params['Ka_dH'] -# kLa = params['kLa'] -# T_base = params['T_base'] -# root = params['root'] - -# if 'unit_conv' not in params: params['unit_conv'] = mass2mol_conversion(cmps) -# unit_conversion = params['unit_conv'] - -# # state_arr_cmps stated just for readability of code -# # 0: S_su -# # 1: S_aa -# # 2: S_fa -# # 3: S_va -# # 4: S_bu -# # 5: S_pro -# # 6: S_ac -# # 7: S_h2 -# # 8: S_ch4 -# # 9: S_IC -# # 10: S_IN -# # 11: S_IP -# # 12: S_I -# # 13: X_ch -# # 14: X_pr -# # 15: X_li -# # 16: X_su -# # 17: X_aa -# # 18: X_fa -# # 19: X_c4 -# # 20: X_pro -# # 21: X_ac -# # 22: X_h2 -# # 23: X_I -# # 24: X_PHA -# # 25: X_PP -# # 26: X_PAO -# # 27: S_K -# # 28: S_Mg -# # 29: S_Ca -# # 30: X_CaCO3 -# # 31: X_struv -# # 32: X_newb -# # 33: X_ACP -# # 34: X_MgCO3 -# # 35: X_AlOH -# # 36: X_AlPO4 -# # 37: X_FeOH -# # 38: X_FePO4 -# # 39: S_Na -# # 40: S_Cl -# # 41: H2O - -# Cs[:7] = state_arr[13:20] -# Cs[7:11] = state_arr[19:23] -# Cs[11:18] = state_arr[16:23] -# Cs[18:23] = X_PAO = state_arr[26] -# Cs[23] = X_PP = state_arr[25] -# Cs[24] = state_arr[24] - -# substrates = state_arr[:8] +def _rhos_adm1p(state_arr, params, h=None): + ks = params['rate_constants'] + Ks = params['half_sat_coeffs'] -# S_va, S_bu, S_h2, S_IN, S_IP = state_arr[[3,4,7,10,11]] + cmps = params['components'] + pH_ULs = params['pH_ULs'] + pH_LLs = params['pH_LLs'] + KS_IN = params['KS_IN'] + KS_IP = params['KS_IP'] + KI_nh3 = params['KI_nh3'] + KIs_h2 = params['KIs_h2'] + KHb = params['K_H_base'] + Kab = params['Ka_base'] + KH_dH = params['K_H_dH'] + Ka_dH = params['Ka_dH'] + kLa = params['kLa'] + T_base = params['T_base'] + root = params['root'] -# T_op = state_arr[-1] -# if T_op == T_base: -# Ka = Kab -# KH = KHb / unit_conversion[7:10] -# else: -# T_temp = params.pop('T_op', None) -# if T_op != T_temp: -# params['Ka'] = Kab * T_correction_factor(T_base, T_op, Ka_dH) -# params['KH'] = KHb * T_correction_factor(T_base, T_op, KH_dH) / unit_conversion[7:10] -# params['T_op'] = T_op -# Ka = params['Ka'] -# KH = params['KH'] - -# rhos[:25] = ks * Cs -# rhos[3:11] *= substr_inhibit(substrates, Ks[:8]) -# if S_va > 0: rhos[6] *= 1/(1+S_bu/S_va) -# if S_bu > 0: rhos[7] *= 1/(1+S_va/S_bu) + if 'unit_conv' not in params: params['unit_conv'] = mass2mol_conversion(cmps) + unit_conversion = params['unit_conv'] -# vfas = state_arr[3:7] + # state_arr_cmps stated just for readability of code + # 0: S_su + # 1: S_aa + # 2: S_fa + # 3: S_va + # 4: S_bu + # 5: S_pro + # 6: S_ac + # 7: S_h2 + # 8: S_ch4 + # 9: S_IC + # 10: S_IN + # 11: S_IP + # 12: S_I + # 13: X_ch + # 14: X_pr + # 15: X_li + # 16: X_su + # 17: X_aa + # 18: X_fa + # 19: X_c4 + # 20: X_pro + # 21: X_ac + # 22: X_h2 + # 23: X_I + # 24: X_PHA + # 25: X_PP + # 26: X_PAO + # 27: S_K + # 28: S_Mg + # 29: S_Ca + # 30: X_CaCO3 + # 31: X_struv + # 32: X_newb + # 33: X_ACP + # 34: X_MgCO3 + # 35: X_AlOH + # 36: X_AlPO4 + # 37: X_FeOH + # 38: X_FePO4 + # 39: S_Na + # 40: S_Cl + # 41: H2O + + Cs_p[:7] = state_arr[13:20] + Cs_p[7:11] = state_arr[19:23] + Cs_p[11:18] = state_arr[16:23] + Cs_p[18:23] = X_PAO = state_arr[26] + Cs_p[23] = X_PP = state_arr[25] + Cs_p[24] = state_arr[24] -# if X_PAO > 0: -# K_A, K_PP = Ks[-2:] -# rhos[18:22] *= substr_inhibit(vfas, K_A) * substr_inhibit(X_PP/X_PAO, K_PP) + substrates = state_arr[:8] -# if sum(vfas) > 0: -# rhos[18:22] *= vfas/sum(vfas) + S_va, S_bu, S_h2, S_IC, S_IN, S_IP = state_arr[[3,4,7,9,10,11]] -# biogas_S = state_arr[7:10].copy() -# biogas_p = R * T_op * state_arr[42:45] + T_op = state_arr[-1] + if T_op == T_base: + Ka = Kab + KH = KHb / unit_conversion[7:10] + else: + T_temp = params.pop('T_op', None) + if T_op != T_temp: + params['Ka'] = Kab * T_correction_factor(T_base, T_op, Ka_dH) + params['KH'] = KHb * T_correction_factor(T_base, T_op, KH_dH) / unit_conversion[7:10] + params['T_op'] = T_op + Ka = params['Ka'] + KH = params['KH'] -# if h is None: h = solve_pH(state_arr, Ka, unit_conversion) -# nh3 = Ka[1] / (Ka[1] + h) * S_IN * unit_conversion[10] -# biogas_S[-1] = h / (Ka[3] + h) * state_arr[9] # co2 + rhos_p[:25] = ks * Cs_p + rhos_p[3:11] *= substr_inhibit(substrates, Ks[:8]) + if S_va > 0: rhos_p[6] *= 1/(1+S_bu/S_va) + if S_bu > 0: rhos_p[7] *= 1/(1+S_va/S_bu) -# Iph = Hill_inhibit(h, pH_ULs, pH_LLs) -# Iin = substr_inhibit(S_IN, KS_IN) -# Iip = substr_inhibit(S_IP, KS_IP) -# Ih2 = non_compet_inhibit(S_h2, KIs_h2) -# Inh3 = non_compet_inhibit(nh3, KI_nh3) -# root.data = { -# 'pH':-np.log10(h), -# 'Iph':Iph, -# 'Ih2':Ih2, -# 'Iin':Iin, -# 'Inh3':Inh3, -# } -# rhos[3:11] *= Iph * Iin * Iip -# rhos[5:9] *= Ih2 -# rhos[9] *= Inh3 -# rhos[-3:] = kLa * (biogas_S - KH * biogas_p) + vfas = state_arr[3:7] -# # print(rhos) -# return rhos - -# def dydt_Sh2_AD(S_h2, state_arr, h, params, f_stoichio, V_liq, S_h2_in): -# state_arr[7] = S_h2 -# Q = state_arr[45] -# rxn = _rhos_adm1p(state_arr, params, h=h) -# stoichio = f_stoichio(state_arr) # should return the stoichiometric coefficients of S_h2 for all processes -# return Q/V_liq*(S_h2_in - S_h2) + np.dot(rxn, stoichio) - -# grad_rhos = np.zeros(5) -# X_bio = np.zeros(5) -# def grad_dydt_Sh2_AD(S_h2, state_arr, h, params, f_stoichio, V_liq, S_h2_in): -# state_arr[7] = S_h2 -# ks = params['rate_constants'][[5,6,7,8,10]] -# Ks = params['half_sat_coeffs'][2:6] -# K_h2 = params['half_sat_coeffs'][7] -# pH_ULs = params['pH_ULs'] -# pH_LLs = params['pH_LLs'] -# KS_IN = params['KS_IN'] -# KIs_h2 = params['KIs_h2'] -# kLa = params['kLa'] + if X_PAO > 0: + K_A, K_PP = Ks[-2:] + rhos_p[18:22] *= substr_inhibit(vfas, K_A) * substr_inhibit(X_PP/X_PAO, K_PP) -# X_bio[:] = state_arr[[18,19,19,20,22]] -# substrates = state_arr[2:6] -# S_va, S_bu, S_IN = state_arr[[3,4,10]] -# Iph = Hill_inhibit(h, pH_ULs, pH_LLs)[[2,3,4,5,7]] -# Iin = substr_inhibit(S_IN, KS_IN) -# grad_Ih2 = grad_non_compet_inhibit(S_h2, KIs_h2) - -# grad_rhos[:] = ks * X_bio * Iph * Iin -# grad_rhos[:-1] *= substr_inhibit(substrates, Ks) * grad_Ih2 -# if S_va > 0: grad_rhos[1] *= 1/(1+S_bu/S_va) -# if S_bu > 0: grad_rhos[2] *= 1/(1+S_va/S_bu) + if sum(vfas) > 0: + rhos_p[18:22] *= vfas/sum(vfas) + + if h is None: h = adm1p_solve_pH(state_arr, Ka, unit_conversion) + Knh, Kc1, Kc2, Kp1, Kp2, Kp3 = Ka[1:7] + nh3 = Knh / (Knh + h) * S_IN * unit_conversion[10] # in mol/L + nh4 = h / (Knh + h) * S_IN # in kg-N/m3 + co2, hco3, co3 = S_IC * ion_speciation(h, Kc1, Kc2) + h3po4, h2po4, hpo4, po4 = S_IP * ion_speciation(h, Kp1, Kp2, Kp3) -# grad_rhos[-1] *= grad_substr_inhibit(S_h2, K_h2) -# stoichio = f_stoichio(state_arr) + Iph = Hill_inhibit(h, pH_ULs, pH_LLs) + Iin = substr_inhibit(S_IN, KS_IN) + Iip = substr_inhibit(S_IP, KS_IP) + Ih2 = non_compet_inhibit(S_h2, KIs_h2) + Inh3 = non_compet_inhibit(nh3, KI_nh3) + root.data = { + 'pH':-np.log10(h), + 'Iph':Iph, + 'Ih2':Ih2, + 'Iin':Iin, + 'Inh3':Inh3, + } + rhos_p[3:11] *= Iph * Iin * Iip + rhos_p[5:9] *= Ih2 + rhos_p[9] *= Inh3 + + ########## precipitation-dissolution ############# + k_mmp = params['k_mmp'] + Ksp = params['Ksp'] + K_dis = params['K_dis'] + K_AlOH = params['K_AlOH'] + K_FeOH = params['K_FeOH'] + S_Mg, S_Ca, X_CaCO3, X_struv, X_newb, X_ACP, X_MgCO3 = state_arr[28:35] + X_AlOH, X_FeOH = state_arr[[35,37]] + f_dis = Monod(state_arr[30:35], K_dis[:5]) + if X_CaCO3 > 0: rhos_p[25] = (S_Ca * co3 - Ksp[0]) * f_dis[0] + else: rhos_p[25] = S_Ca * co3 + if X_struv > 0: rhos_p[26] = (S_Mg * nh4 * po4 - Ksp[1]) * f_dis[1] + else: rhos_p[26] = S_Mg * nh4 * po4 + if X_newb > 0: rhos_p[27] = (S_Mg * hpo4 - Ksp[2]) * f_dis[2] + else: rhos_p[27] = S_Mg * hpo4 + if X_ACP > 0: rhos_p[28] = (S_Ca**3 * po4**2 - Ksp[3]) * f_dis[3] + else: rhos_p[28] = S_Ca**3 * po4**2 + if X_MgCO3 > 0: rhos_p[29] = (S_Mg * co3 - Ksp[4]) * f_dis[4] + else: rhos_p[29] = S_Mg * co3 + rhos_p[30] = X_AlOH * po4 * Monod(X_AlOH, K_AlOH) + rhos_p[31] = X_FeOH * po4 * Monod(X_FeOH, K_FeOH) + rhos_p[25:32] *= k_mmp -# Q = state_arr[45] -# return -Q/V_liq + np.dot(grad_rhos, stoichio[[5,6,7,8,10]]) + kLa*stoichio[-3] + biogas_S = state_arr[7:10].copy() + biogas_p = R * T_op * state_arr[42:45] + biogas_S[-1] = co2 + rhos_p[-3:] = kLa * (biogas_S - KH * biogas_p) + + return rhos_p #%% # ============================================================================= -# ADM1_p_extension class +# ADM1p class # ============================================================================= -_mmp = ospath.join(data_path, 'process_data/_mmp.tsv') @chemicals_user class ADM1p(ADM1): @@ -884,20 +832,26 @@ class ADM1p(ADM1): Examples -------- - + >>> import qsdsan.processes as pc + >>> cmps = pc.create_adm1p_cmps() + >>> adm = pc.ADM1p() + >>> adm.show() + ADM1p([hydrolysis_carbs, hydrolysis_proteins, hydrolysis_lipids, uptake_sugars, uptake_amino_acids, uptake_LCFA, uptake_valerate, uptake_butyrate, uptake_propionate, uptake_acetate, uptake_h2, decay_Xsu, decay_Xaa, decay_Xfa, decay_Xc4, decay_Xpro, decay_Xac, decay_Xh2, storage_Sva_in_XPHA, storage_Sbu_in_XPHA, storage_Spro_in_XPHA, storage_Sac_in_XPHA, lysis_XPAO, lysis_XPP, lysis_XPHA, CaCO3_precipitation_dissolution, struvite_precipitation_dissolution, newberyite_precipitation_dissolution, ACP_precipitation_dissolution, MgCO3_precipitation_dissolution, AlPO4_precipitation_dissolution, FePO4_precipitation_dissolution, h2_transfer, ch4_transfer, IC_transfer]) + + References ---------- - .. [1] Batstone, D. J.; Keller, J.; Angelidaki, I.; Kalyuzhnyi, S. V; - Pavlostathis, S. G.; Rozzi, A.; Sanders, W. T. M.; Siegrist, H.; - Vavilin, V. A. The IWA Anaerobic Digestion Model No 1 (ADM1). - Water Sci. Technol. 2002, 45 (10), 65–73. - .. [2] Rosen, C.; Jeppsson, U. Aspects on ADM1 Implementation within - the BSM2 Framework; Lund, 2006. - .. [3] Flores-Alsina, X.; Solon, K.; Kazadi Mbamba, C.; Tait, S.; - Gernaey, K. V.; Jeppsson, U.; Batstone, D. J. + .. [1] Flores-Alsina, X., Solon, K., Kazadi Mbamba, C., Tait, S., + Gernaey, K. V., Jeppsson, U., & Batstone, D. J. (2016). Modelling phosphorus (P), sulfur (S) and iron (FE) interactions for - dynamic simulations of anaerobic digestion processes. Water Research. 2016, + dynamic simulations of anaerobic digestion processes. Water Research, 95, 370–382. + .. [2] Solon, K., Flores-Alsina, X., Kazadi Mbamba, C., Ikumi, D., Volcke, + E. I. P., Vaneeckhaute, C., Ekama, G., Vanrolleghem, P. A., Batstone, + D. J., Gernaey, K. V., & Jeppsson, U. (2017). Plant-wide modelling + of phosphorus transformations in wastewater treatment systems: + Impacts of control and operational strategies. Water Research, 113, + 97–110. """ _stoichio_params = (*ADM1._stoichio_params[5:], @@ -905,7 +859,8 @@ class ADM1p(ADM1): 'f_ac_PHA', 'f_bu_PHA', 'f_pro_PHA', 'f_va_PHA', 'Y_PO4', 'K_XPP', 'Mg_XPP') - _kinetic_params = (*ADM1._kinetic_params, 'KS_IP', ) + _kinetic_params = (*ADM1._kinetic_params, 'KS_IP', + 'k_mmp', 'Ksp', 'K_dis', 'K_AlOH', 'K_FeOH') _acid_base_pairs = (('H+', 'OH-'), ('NH4+', 'NH3'), ('CO2', 'HCO3-'), ('HCO3-', 'CO3-2'), @@ -950,10 +905,11 @@ def __new__(cls, components=None, path=None, parameters=cls._stoichio_params, compile=False) - mmp = Processes.load_from_file(_mmp, components=cmps, - conserved_for=(), compile=False) mmp_stoichio = {} df = load_data(_mmp) + df.rename(columns={'S_NH4':'S_IN', 'S_PO4':'S_IP'}, inplace=True) + mmp = Processes.load_from_file(data=df, components=cmps, + conserved_for=(), compile=False) for i, j in df.iterrows(): j.dropna(inplace=True) key = j.index[j == 1][0] @@ -1009,8 +965,8 @@ def __new__(cls, components=None, path=None, root = TempState() dct = self.__dict__ dct.update(kwargs) - - self.set_rate_function(rhos_adm1_p_extension) + dct['mmp_stoichio'] = mmp_stoichio + self.set_rate_function(rhos_adm1p) dct['_parameters'] = dict(zip(cls._stoichio_params, stoichio_vals)) self.rate_function._params = dict(zip(cls._kinetic_params, [ks, Ks, pH_ULs, pH_LLs, KS_IN*N_mw, @@ -1018,8 +974,48 @@ def __new__(cls, components=None, path=None, K_H_base, K_H_dH, kLa, T_base, self._components, root, #!!! new parameter - KS_IP*P_mw])) - dct['solve_pH'] = solve_pH + KS_IP*P_mw, np.array(k_mmp), Ksp_mass, + np.array(K_dis), K_AlOH, K_FeOH])) + + def dydt_Sh2_AD(S_h2, state_arr, h, params, f_stoichio, V_liq, S_h2_in): + state_arr[7] = S_h2 + Q = state_arr[45] + rxn = _rhos_adm1p(state_arr, params, h=h) + stoichio = f_stoichio(state_arr) # should return the stoichiometric coefficients of S_h2 for all processes + return Q/V_liq*(S_h2_in - S_h2) + np.dot(rxn, stoichio) + + grad_rhosp = np.zeros(5) + X_biop = np.zeros(5) + def grad_dydt_Sh2_AD(S_h2, state_arr, h, params, f_stoichio, V_liq, S_h2_in): + state_arr[7] = S_h2 + ks = params['rate_constants'][[5,6,7,8,10]] + Ks = params['half_sat_coeffs'][2:6] + K_h2 = params['half_sat_coeffs'][7] + pH_ULs = params['pH_ULs'] + pH_LLs = params['pH_LLs'] + KS_IN = params['KS_IN'] + KIs_h2 = params['KIs_h2'] + kLa = params['kLa'] + + X_biop[:] = state_arr[[18,19,19,20,22]] + substrates = state_arr[2:6] + S_va, S_bu, S_IN = state_arr[[3,4,10]] + Iph = Hill_inhibit(h, pH_ULs, pH_LLs)[[2,3,4,5,7]] + Iin = substr_inhibit(S_IN, KS_IN) + grad_Ih2 = grad_non_compet_inhibit(S_h2, KIs_h2) + + grad_rhosp[:] = ks * X_biop * Iph * Iin + grad_rhosp[:-1] *= substr_inhibit(substrates, Ks) * grad_Ih2 + if S_va > 0: grad_rhosp[1] *= 1/(1+S_bu/S_va) + if S_bu > 0: grad_rhosp[2] *= 1/(1+S_va/S_bu) + + grad_rhosp[-1] *= grad_substr_inhibit(S_h2, K_h2) + stoichio = f_stoichio(state_arr) + + Q = state_arr[45] + return -Q/V_liq + np.dot(grad_rhos, stoichio[[5,6,7,8,10]]) + kLa*stoichio[-3] + + dct['solve_pH'] = adm1p_solve_pH dct['dydt_Sh2_AD'] = dydt_Sh2_AD dct['grad_dydt_Sh2_AD'] = grad_dydt_Sh2_AD return self @@ -1056,7 +1052,20 @@ def set_KS_IP(self, K): substrate [M phosphorous].''' self.rate_function._params['KS_IP'] = K * P_mw - + def set_pKsps(self, ps): + cmps = self.components + mol_to_mass = cmps.chem_MW / cmps.i_mass + idxer = cmps.index + stoichio = self.mmp_stoichio + Ksp_mass = [] # mass in kg/m3 + for xid, p in zip(self._precipitates, ps): + K = 10**(-p) + for cmp, v in stoichio[xid]: + m2m = mol_to_mass[idxer(cmp)] + K *= m2m**abs(v) + Ksp_mass.append(K) + self.rate_function._params['Ksp'] = np.array(Ksp_mass) + def check_stoichiometric_parameters(self): '''Check whether product COD fractions sum up to 1 for each process.''' stoichio = self.parameters From 2bd8487e7ab1ce3474843617b6f85facbb2caed7 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Fri, 14 Jun 2024 10:08:03 -0700 Subject: [PATCH 367/424] `mASM2d`-`ADM1p` interface models --- qsdsan/processes/_adm1_p_extension.py | 3 +- qsdsan/sanunits/_junction.py | 64 ++++++++++++++------------- 2 files changed, 35 insertions(+), 32 deletions(-) diff --git a/qsdsan/processes/_adm1_p_extension.py b/qsdsan/processes/_adm1_p_extension.py index 007693ff..b330fc7d 100644 --- a/qsdsan/processes/_adm1_p_extension.py +++ b/qsdsan/processes/_adm1_p_extension.py @@ -770,7 +770,7 @@ def _rhos_adm1p(state_arr, params, h=None): @chemicals_user class ADM1p(ADM1): """ - Anaerobic Digestion Model No.1 with P extension. [1]_, [2]_, [3]_. + Anaerobic Digestion Model No.1 with P extension. [1]_, [2]_. Compatible with `mASM2d`. Parameters @@ -867,6 +867,7 @@ class ADM1p(ADM1): ('H3PO4', 'H2PO4-'), ('H2PO4-', 'HPO4-2'), ('HPO4-2', 'PO4-3'), ('HAc', 'Ac-'),('HPr', 'Pr-'), ('HBu', 'Bu-'), ('HVa', 'Va-')) + _precipitates = ('X_CaCO3', 'X_struv', 'X_newb', 'X_ACP', 'X_MgCO3', 'X_AlPO4', 'X_FePO4') _biogas_IDs = ('S_h2', 'S_ch4', 'S_IC') diff --git a/qsdsan/sanunits/_junction.py b/qsdsan/sanunits/_junction.py index 80b3a6ef..0ea80ca8 100644 --- a/qsdsan/sanunits/_junction.py +++ b/qsdsan/sanunits/_junction.py @@ -31,8 +31,8 @@ 'ADM1toASM2d', 'ASM2dtomADM1', 'mADM1toASM2d', - 'ADM1ptoASM2d_A1', - 'ASM2dtoADM1p_A1' + 'ADM1ptomASM2d_A1', + 'mASM2dtoADM1p_A1' ) #%% Junction @@ -407,7 +407,7 @@ def asm2d_model(self): return self._asm2d_model @asm2d_model.setter def asm2d_model(self, model): - if not isinstance(model, pc.ASM2d): + if not isinstance(model, (pc.ASM2d, pc.mASM2d)): raise ValueError('`asm2d_model` must be an `ASM2d` object, ' f'the given object is {type(model).__name__}.') self._asm2d_model = model @@ -418,7 +418,7 @@ def adm1_model(self): return self._adm1_model @adm1_model.setter def adm1_model(self, model): - if not isinstance(model, pc.ADM1_p_extension): + if not isinstance(model, (pc.ADM1_p_extension, pc.ADM1p)): raise ValueError('`adm1_model` must be an `ADM1_p_extension` object, ' #!!! update error message f'the given object is {type(model).__name__}.') self._adm1_model = model @@ -3088,8 +3088,8 @@ def asm2d2madm1(asm_vals): self._reactions = asm2d2madm1 -#%% ADM1ptoASM2d_A1 -class ADM1ptoASM2d_A1(mADMjunction): +#%% ADM1ptomASM2d_A1 +class ADM1ptomASM2d_A1(mADMjunction): ''' Interface unit to convert ADM1 state variables to ASM2d components, following the A1 algorithm in [1]_. @@ -3118,7 +3118,7 @@ class ADM1ptoASM2d_A1(mADMjunction): -------- :class:`qsdsan.sanunits.mADMjunction` - :class:`qsdsan.sanunits.ASM2dtoADM1p_A1` + :class:`qsdsan.sanunits.mASM2dtoADM1p_A1` ''' def balance_cod_tkn(self, adm_vals, asm_vals): @@ -3243,7 +3243,7 @@ def _compile_reactions(self): f_corr = self.balance_cod_tkn # To convert components from ADM1p to ASM2d (A1) - def adm1p2asm2d(adm_vals): + def adm1p2masm2d(adm_vals): _adm_vals = adm_vals.copy() @@ -3255,29 +3255,30 @@ def adm1p2asm2d(adm_vals): S_su, S_aa, S_fa, S_va, S_bu, S_pro, S_ac, S_h2, S_ch4, S_IC, S_IN, S_IP, S_I, \ X_ch, X_pr, X_li, X_su, X_aa, X_fa, X_c4, X_pro, X_ac, X_h2, X_I, \ - X_PHA, X_PP, X_PAO, S_K, S_Mg, X_MeOH, X_MeP, S_cat, S_an, H2O = _adm_vals + X_PHA, X_PP, X_PAO, S_K, S_Mg, S_Ca, X_CaCO3, X_struv, \ + X_newb, X_ACP, X_MgCO3, X_AlOH, X_AlPO4, X_FeOH, X_FePO4, \ + S_Na, S_Cl, H2O = _adm_vals if S_h2 > 0 or S_ch4 > 0: warn('Ignored dissolved H2 or CH4.') - S_ALK = S_IC S_NH4 = S_IN S_PO4 = S_IP # CONV 1: convert X_pr, X_li, X_ch to X_S X_S = X_pr + X_li + X_ch - S_ALK += X_pr*C_pr + X_li*C_li + X_ch*C_ch - X_S*C_XS + S_IC += X_pr*C_pr + X_li*C_li + X_ch*C_ch - X_S*C_XS S_NH4 += X_pr*N_pr + X_li*N_li + X_ch*N_ch - X_S*N_XS S_PO4 += X_pr*P_pr + X_li*P_li + X_ch*P_ch - X_S*P_XS # CONV 2: convert S_su, S_aa, S_fa to S_F S_F = S_su + S_aa + S_fa - S_ALK += S_su*C_su + S_aa*C_aa + S_fa*C_fa - S_F*C_SF + S_IC += S_su*C_su + S_aa*C_aa + S_fa*C_fa - S_F*C_SF S_NH4 += S_su*N_su + S_aa*N_aa + S_fa*N_fa - S_F*N_SF S_PO4 += S_su*P_su + S_aa*P_aa + S_fa*P_fa - S_F*P_SF # CONV 3: convert VFAs to S_A S_A = S_va + S_bu + S_pro + S_ac - S_ALK += S_va*C_va + S_bu*C_bu + S_pro*C_pro + S_ac*C_ac - S_A*C_SA + S_IC += S_va*C_va + S_bu*C_bu + S_pro*C_pro + S_ac*C_ac - S_A*C_SA # S_NH4 += S_va*N_va + S_bu*N_bu + S_pro*N_pro + S_ac*N_ac - S_A*N_SA # S_PO4 += S_va*P_va + S_bu*P_bu + S_pro*P_pro + S_ac*P_ac - S_A*P_SA @@ -3286,21 +3287,20 @@ def adm1p2asm2d(adm_vals): S_NH4, 0, # S_NO3 S_PO4, S_F, S_A, S_I, - S_ALK, + S_IC, S_K, S_Mg, X_I, X_S, - 0, # X_H, - 0,0,0,# X_PAO, X_PP, X_PHA, - 0, # X_AUT, - X_MeOH, X_MeP, H2O])) # directly mapped + 0,0,0,0,0, # X_H, X_PAO, X_PP, X_PHA, X_AUT, + S_Ca, X_CaCO3, X_struv, X_newb, X_ACP, X_MgCO3, # directly mapped + X_AlOH, X_AlPO4, X_FeOH, X_FePO4, S_Na, S_Cl, H2O])) asm_vals = f_corr(adm_vals, asm_vals) return asm_vals - self._reactions = adm1p2asm2d + self._reactions = adm1p2masm2d -#%% ASM2dtoADM1p_A1 +#%% mASM2dtoADM1p_A1 -class ASM2dtoADM1p_A1(mADMjunction): +class mASM2dtoADM1p_A1(mADMjunction): ''' Interface unit to convert ASM2d state variables to ADM1 components, following the A1 scenario in [1]_. @@ -3332,7 +3332,7 @@ class ASM2dtoADM1p_A1(mADMjunction): -------- :class:`qsdsan.sanunits.mADMjunction` - :class:`qsdsan.sanunits.ADM1ptoASM2d_A1` + :class:`qsdsan.sanunits.ADM1ptomASM2d_A1` ''' # User defined values @@ -3459,8 +3459,10 @@ def _compile_reactions(self): p2_stoichio /= abs(p2_stoichio[S_NO3_idx]) p3_stoichio = np.array([adm.parameters[f'f_{k}_xb'] for k in ('sI', 'ch', 'pr', 'li', 'xI')]) + xs_to_li = self.xs_to_li + # To convert components from ASM2d to mADM1 (asm2d-2-madm1) - def asm2d2adm1p(asm_vals): + def masm2d2adm1p(asm_vals): _asm_vals = asm_vals.copy() # PROCESS 1: remove S_O2 with S_A with associated X_H growth (aerobic growth of X_H on S_A) @@ -3471,10 +3473,11 @@ def asm2d2adm1p(asm_vals): NO3_coddm = _asm_vals[S_NO3_idx] _asm_vals += NO3_coddm * p2_stoichio # makes S_NO3 = 0 - S_O2, S_N2, S_NH4, S_NO3, S_PO4, S_F, S_A, S_I, S_ALK, X_I, X_S, X_H, \ - X_PAO, X_PP, X_PHA, X_AUT, X_MeOH, X_MeP, H2O = _asm_vals + S_O2, S_N2, S_NH4, S_NO3, S_PO4, S_F, S_A, S_I, S_IC, S_K, S_Mg, \ + X_I, X_S, X_H, X_PAO, X_PP, X_PHA, X_AUT, S_Ca, X_CaCO3, \ + X_struv, X_newb, X_ACP, X_MgCO3, X_AlOH, X_AlPO4, \ + X_FeOH, X_FePO4, S_Na, S_Cl, H2O = _asm_vals - S_IC = S_ALK S_IN = S_NH4 S_IP = S_PO4 @@ -3511,7 +3514,7 @@ def asm2d2adm1p(asm_vals): X_li = X_ch = 0 else: X_pr = req_xcod - X_li = self.xs_to_li * (X_S - X_pr) + X_li = xs_to_li * (X_S - X_pr) X_ch = (X_S - X_pr) - X_li S_IN += X_ND - X_pr*N_pr @@ -3536,12 +3539,11 @@ def asm2d2adm1p(asm_vals): X_ch, X_pr, X_li, 0, 0, 0, 0, 0, 0, 0, # X_su, X_aa, X_fa, X_c4, X_pro, X_ac, X_h2, X_I, X_PHA, X_PP, X_PAO, - 0, 0, # S_K, S_Mg, - X_MeOH, X_MeP, - 0, 0, H2O]) # S_cat, S_an + S_K, S_Mg, S_Ca, X_CaCO3, X_struv, X_newb, X_ACP, X_MgCO3, + X_AlOH, X_AlPO4, X_FeOH, X_FePO4, S_Na, S_Cl, H2O]) # adm_vals = f_corr(asm_vals, adm_vals) adm_vals = f_corr(_asm_vals, adm_vals) return adm_vals - self._reactions = asm2d2adm1p + self._reactions = masm2d2adm1p From 8c7ec7c6d728822f6b5e5dbf43d92aee25282eb3 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Fri, 14 Jun 2024 14:08:14 -0700 Subject: [PATCH 368/424] rename A1 interface models --- qsdsan/processes/_adm1_p_extension.py | 5 + qsdsan/processes/_asm2d.py | 3 + qsdsan/sanunits/_junction.py | 157 +++++++++++++++++--------- 3 files changed, 112 insertions(+), 53 deletions(-) diff --git a/qsdsan/processes/_adm1_p_extension.py b/qsdsan/processes/_adm1_p_extension.py index b330fc7d..5012712c 100644 --- a/qsdsan/processes/_adm1_p_extension.py +++ b/qsdsan/processes/_adm1_p_extension.py @@ -550,6 +550,11 @@ def create_adm1p_cmps(set_thermo=True): c1.S_I.i_mass = c1.X_I.i_mass = 0.75 c1.S_I.f_Vmass_Totmass = c1.X_I.f_Vmass_Totmass = 0.85 + c1.X_li._formula = None + c1.X_li.chem_MW = 1. + c1.X_li.i_C = 0.263112 + c1.X_li.i_N = 0. + c1.X_li.i_P = 0.010664 for cmp in (c1.S_aa, c1.X_pr, c1.S_fa, c1.S_I, c1.X_I): cmp.i_NOD = None diff --git a/qsdsan/processes/_asm2d.py b/qsdsan/processes/_asm2d.py index b612eed8..0f9f3253 100644 --- a/qsdsan/processes/_asm2d.py +++ b/qsdsan/processes/_asm2d.py @@ -100,12 +100,15 @@ def create_masm2d_cmps(set_thermo=True): c2d.S_I.i_C = c2d.X_I.i_C = 0.36178 c2d.S_I.i_N = c2d.X_I.i_N = 0.06003 c2d.S_I.i_P = c2d.X_I.i_P = 6.49e-3 + c2d.S_I.i_K = c2d.X_I.i_K = 0.0 c2d.S_F.i_mass = c2d.X_S.i_mass = c2d.S_I.i_mass = c2d.X_I.i_mass = 0.75 c2d.S_F.f_Vmass_Totmass = c2d.X_S.f_Vmass_Totmass = c2d.S_I.f_Vmass_Totmass = c2d.X_I.f_Vmass_Totmass = 0.85 c2d.X_H.i_C = c2d.X_AUT.i_C = c2d.X_PAO.i_C = 0.36612 c2d.X_H.i_N = c2d.X_AUT.i_N = c2d.X_PAO.i_N = 0.08615 c2d.X_H.i_P = c2d.X_AUT.i_P = c2d.X_PAO.i_P = 0.02154 + c2d.X_H.i_K = c2d.X_AUT.i_K = c2d.X_PAO.i_K = 0.0 + c2d.X_H.i_Mg = c2d.X_AUT.i_Mg = c2d.X_PAO.i_Mg = 0.0 c2d.X_H.i_mass = c2d.X_AUT.i_mass = c2d.X_PAO.i_mass = 0.90 c2d.X_H.f_Vmass_Totmass = c2d.X_AUT.f_Vmass_Totmass = c2d.X_PAO.f_Vmass_Totmass = 0.85 diff --git a/qsdsan/sanunits/_junction.py b/qsdsan/sanunits/_junction.py index 0ea80ca8..9deec129 100644 --- a/qsdsan/sanunits/_junction.py +++ b/qsdsan/sanunits/_junction.py @@ -31,8 +31,9 @@ 'ADM1toASM2d', 'ASM2dtomADM1', 'mADM1toASM2d', - 'ADM1ptomASM2d_A1', - 'mASM2dtoADM1p_A1' + 'A1junction', + 'ADM1ptomASM2d', + 'mASM2dtoADM1p' ) #%% Junction @@ -472,46 +473,6 @@ def alpha_IC(self): @property def alpha_vfa(self): return 1.0/self.cod_vfa*(-1.0/(1.0 + 10**(self.pKa[4:]-self.pH))) - - def check_component_properties(self, cmps_asm, cmps_adm): - get = getattr - setv = setattr - for name in ('X_PHA', 'X_PP', 'X_PAO', 'X_MeOH', 'X_MeP'): - casm = get(cmps_asm, name) - cadm = get(cmps_adm, name) - for attr in ('measured_as', 'i_COD', 'i_N', 'i_P'): - vasm = get(casm, attr) - if get(cadm, attr) != vasm: - setv(cadm, attr, vasm) - warn(f"ADM component {name}'s {attr} is changed to match " - "the corresponding ASM component") - - for name in ('S_I', 'X_I'): - casm = get(cmps_asm, name) - cadm = get(cmps_adm, name) - for attr in ('measured_as', 'i_N', 'i_P'): - vadm = get(cadm, attr) - if get(casm, attr) != vadm: - setv(casm, attr, vadm) - warn(f"ASM component {name}'s {attr} is changed to match " - "the corresponding ADM component") - - for attr in ('i_N', 'i_P'): - vadm = get(cmps_adm.S_ac, attr) - if get(cmps_asm.S_A, attr) != vadm: - cmps_asm.S_A.i_N = vadm - warn(f"ASM component S_A's {attr} is changed to match " - "the ADM component S_ac.") - - if cmps_asm.S_ALK.measured_as != cmps_adm.S_IC.measured_as: - raise RuntimeError('S_ALK in ASM and S_IC in ADM must both be measured as "C".') - if cmps_asm.S_NH4.measured_as != cmps_adm.S_IN.measured_as: - raise RuntimeError('S_NH4 in ASM and S_IN in ADM must both be measured as "N".') - if cmps_asm.S_PO4.measured_as != cmps_adm.S_IP.measured_as: - raise RuntimeError('S_PO4 in ASM and S_IP in ADM must both be measured as "P".') - - cmps_asm.refresh_constants() - cmps_adm.refresh_constants() #%% ADMtoASM class ADMtoASM(ADMjunction): @@ -2336,7 +2297,6 @@ def madm12asm2d(adm_vals): #%% ASM2dtomADM1 - class ASM2dtomADM1(mADMjunction): ''' Interface unit to convert activated sludge model (ASM) components @@ -3088,8 +3048,99 @@ def asm2d2madm1(asm_vals): self._reactions = asm2d2madm1 -#%% ADM1ptomASM2d_A1 -class ADM1ptomASM2d_A1(mADMjunction): +#%% A1junction + +class A1junction(ADMjunction): + ''' + An abstract superclass holding common properties of modified ADM interface classes. + Users should use its subclasses (e.g., ``mASM2dtoADM1p``, ``ADM1ptomASM2d``) instead. + + See Also + -------- + :class:`qsdsan.sanunits.ADMJunction` + + :class:`qsdsan.sanunits.ADM1ptomASM2d` + + :class:`qsdsan.sanunits.mASM2dtoADM1p` + ''' + _parse_reactions = Junction._no_parse_reactions + rtol = 1e-2 + atol = 1e-6 + cod_vfa = np.array([64, 112, 160, 208]) + + def __init__(self, ID='', upstream=None, downstream=(), thermo=None, + init_with='WasteStream', F_BM_default=None, isdynamic=False, + adm1_model=None, asm2d_model=None): + self.asm2d_model = asm2d_model + super().__init__(ID=ID, upstream=upstream, downstream=downstream, + thermo=thermo, init_with=init_with, + F_BM_default=F_BM_default, isdynamic=isdynamic, + adm1_model=adm1_model) + + @property + def asm2d_model(self): + '''[qsdsan.CompiledProcesses] ASM2d process model.''' + return self._asm2d_model + @asm2d_model.setter + def asm2d_model(self, model): + if not isinstance(model, (pc.ASM2d, pc.mASM2d)): + raise ValueError('`asm2d_model` must be an `ASM2d` object, ' + f'the given object is {type(model).__name__}.') + self._asm2d_model = model + + @property + def adm1_model(self): + '''[qsdsan.CompiledProcesses] mADM1 process model.''' + return self._adm1_model + @adm1_model.setter + def adm1_model(self, model): + if not isinstance(model, (pc.ADM1_p_extension, pc.ADM1p)): + raise ValueError('`adm1_model` must be an `ADM1_p_extension` object, ' #!!! update error message + f'the given object is {type(model).__name__}.') + self._adm1_model = model + + def check_component_properties(self, cmps_asm, cmps_adm): + get = getattr + setv = setattr + for name in ('X_PHA', 'X_PP', 'X_PAO'): + casm = get(cmps_asm, name) + cadm = get(cmps_adm, name) + for attr in ('measured_as', 'i_COD', 'i_C', 'i_N', 'i_P'): + vasm = get(casm, attr) + if get(cadm, attr) != vasm: + setv(cadm, attr, vasm) + warn(f"ADM component {name}'s {attr} is changed to match " + "the corresponding ASM component") + + for name in ('S_I', 'X_I'): + casm = get(cmps_asm, name) + cadm = get(cmps_adm, name) + for attr in ('measured_as', 'i_C', 'i_N', 'i_P'): + vadm = get(cadm, attr) + if get(casm, attr) != vadm: + setv(casm, attr, vadm) + warn(f"ASM component {name}'s {attr} is changed to match " + "the corresponding ADM component") + + for attr in ('i_N', 'i_P'): + vadm = get(cmps_adm.S_ac, attr) + if get(cmps_asm.S_A, attr) != vadm: + cmps_asm.S_A.i_N = vadm + warn(f"ASM component S_A's {attr} is changed to match " + "the ADM component S_ac.") + + if cmps_asm.S_IC.measured_as != cmps_adm.S_IC.measured_as: + raise RuntimeError('S_ALK in ASM and S_IC in ADM must both be measured as "C".') + if cmps_asm.S_NH4.measured_as != cmps_adm.S_IN.measured_as: + raise RuntimeError('S_NH4 in ASM and S_IN in ADM must both be measured as "N".') + if cmps_asm.S_PO4.measured_as != cmps_adm.S_IP.measured_as: + raise RuntimeError('S_PO4 in ASM and S_IP in ADM must both be measured as "P".') + cmps_asm.refresh_constants() + cmps_adm.refresh_constants() + + +#%% ADM1ptomASM2d +class ADM1ptomASM2d(A1junction): ''' Interface unit to convert ADM1 state variables to ASM2d components, following the A1 algorithm in [1]_. @@ -3116,9 +3167,9 @@ class ADM1ptomASM2d_A1(mADMjunction): See Also -------- - :class:`qsdsan.sanunits.mADMjunction` + :class:`qsdsan.sanunits.A1junction` - :class:`qsdsan.sanunits.mASM2dtoADM1p_A1` + :class:`qsdsan.sanunits.mASM2dtoADM1p` ''' def balance_cod_tkn(self, adm_vals, asm_vals): @@ -3298,9 +3349,9 @@ def adm1p2masm2d(adm_vals): self._reactions = adm1p2masm2d -#%% mASM2dtoADM1p_A1 +#%% mASM2dtoADM1p -class mASM2dtoADM1p_A1(mADMjunction): +class mASM2dtoADM1p(A1junction): ''' Interface unit to convert ASM2d state variables to ADM1 components, following the A1 scenario in [1]_. @@ -3330,13 +3381,13 @@ class mASM2dtoADM1p_A1(mADMjunction): See Also -------- - :class:`qsdsan.sanunits.mADMjunction` + :class:`qsdsan.sanunits.A1junction` - :class:`qsdsan.sanunits.ADM1ptomASM2d_A1` + :class:`qsdsan.sanunits.ADM1ptomASM2d` ''' # User defined values - xs_to_li = 0.7 + xs_to_li = 0.6 def balance_cod_tkn_tp(self, asm_vals, adm_vals): cmps_asm = self.ins[0].components @@ -3428,7 +3479,7 @@ def balance_cod_tkn_tp(self, asm_vals, adm_vals): f'influent TKN is {asm_tkn}\n ' f'effluent TKN is {adm_tkn}. ') return adm_vals - + def _compile_reactions(self): cmps_asm = self.ins[0].components cmps_adm = self.outs[0].components From 081bf142b35e95ea950a0020c34a0db4fe4dc7cc Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Fri, 14 Jun 2024 15:42:17 -0700 Subject: [PATCH 369/424] debug ADM1p interface --- qsdsan/processes/_adm1_p_extension.py | 1 + qsdsan/sanunits/_junction.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/qsdsan/processes/_adm1_p_extension.py b/qsdsan/processes/_adm1_p_extension.py index 5012712c..95444763 100644 --- a/qsdsan/processes/_adm1_p_extension.py +++ b/qsdsan/processes/_adm1_p_extension.py @@ -562,6 +562,7 @@ def create_adm1p_cmps(set_thermo=True): cmp.i_C = 0.36612 cmp.i_N = 0.08615 cmp.i_P = 0.02154 + cmp.i_K = cmp.i_Mg = 0. cmp.i_mass = 0.90 cmp.f_Vmass_Totmass = 0.85 cmp.i_NOD = None diff --git a/qsdsan/sanunits/_junction.py b/qsdsan/sanunits/_junction.py index 9deec129..def25fd2 100644 --- a/qsdsan/sanunits/_junction.py +++ b/qsdsan/sanunits/_junction.py @@ -3313,7 +3313,7 @@ def adm1p2masm2d(adm_vals): if S_h2 > 0 or S_ch4 > 0: warn('Ignored dissolved H2 or CH4.') S_NH4 = S_IN - S_PO4 = S_IP + S_PO4 = S_IP # CONV 1: convert X_pr, X_li, X_ch to X_S X_S = X_pr + X_li + X_ch From 48372d2b13e0af3b34929c6f79f7ac9e2f29a896 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Fri, 14 Jun 2024 15:54:34 -0700 Subject: [PATCH 370/424] minor update to adm1 pH solvers --- qsdsan/processes/_adm1.py | 16 +++++----------- qsdsan/processes/_adm1_p_extension.py | 15 +++++---------- qsdsan/sanunits/_anaerobic_reactor.py | 2 +- 3 files changed, 11 insertions(+), 22 deletions(-) diff --git a/qsdsan/processes/_adm1.py b/qsdsan/processes/_adm1.py index 819b3212..8b23c125 100644 --- a/qsdsan/processes/_adm1.py +++ b/qsdsan/processes/_adm1.py @@ -219,10 +219,7 @@ def solve_pH(state_arr, Ka, unit_conversion): h = brenth(acid_base_rxn, 1e-14, 1.0, args=(weak_acids, Ka), xtol=1e-12, maxiter=100) - nh3 = Ka[1] * weak_acids[2] / (Ka[1] + h) - co2 = weak_acids[3] - Ka[2] * weak_acids[3] / (Ka[2] + h) - return h, nh3, co2 - + return h rhos_adm1 = lambda state_arr, params: _rhos_adm1(state_arr, params, h=None) def _rhos_adm1(state_arr, params, h=None): @@ -278,13 +275,10 @@ def _rhos_adm1(state_arr, params, h=None): if S_va > 0: rhos[7] *= 1/(1+S_bu/S_va) if S_bu > 0: rhos[8] *= 1/(1+S_va/S_bu) - if h is None: - h, nh3, co2 = solve_pH(state_arr, Ka, unit_conversion) - else: - nh3 = Ka[1] * S_IN * unit_conversion[10] / (Ka[1] + h) - S_IC = state_arr[9] * unit_conversion[9] - co2 = S_IC - Ka[2] * S_IC / (Ka[2] + h) - biogas_S[-1] = co2 / unit_conversion[9] + if h is None: h = solve_pH(state_arr, Ka, unit_conversion) + nh3 = S_IN * unit_conversion[10] * Ka[1] / (Ka[1] + h) + co2 = state_arr[9] * h / (Ka[2] + h) + biogas_S[-1] = co2 Iph = Hill_inhibit(h, pH_ULs, pH_LLs) Iin = substr_inhibit(S_IN, KS_IN) diff --git a/qsdsan/processes/_adm1_p_extension.py b/qsdsan/processes/_adm1_p_extension.py index 95444763..aeb9df6d 100644 --- a/qsdsan/processes/_adm1_p_extension.py +++ b/qsdsan/processes/_adm1_p_extension.py @@ -114,9 +114,7 @@ def solve_pH(state_arr, Ka, unit_conversion): h = brenth(acid_base_rxn, 1e-14, 1.0, args=(weak_acids, Ka), xtol=1e-12, maxiter=100) - nh3 = Ka[1] * weak_acids[4] / (Ka[1] + h) - co2 = weak_acids[6] - Ka[3] * weak_acids[6] / (Ka[3] + h) - return h, nh3, co2 + return h rhos_adm1_p_extension = lambda state_arr, params: _rhos_adm1_p_extension(state_arr, params, h=None) @@ -219,13 +217,10 @@ def _rhos_adm1_p_extension(state_arr, params, h=None): biogas_S = state_arr[7:10].copy() biogas_p = R * T_op * state_arr[34:37] - if h is None: - h, nh3, co2 = solve_pH(state_arr, Ka, unit_conversion) - else: - nh3 = Ka[1] * S_IN * unit_conversion[10] / (Ka[1] + h) - S_IC = state_arr[9] * unit_conversion[9] - co2 = S_IC - Ka[3] * S_IC / (Ka[3] + h) - biogas_S[-1] = co2 / unit_conversion[9] + if h is None: h = solve_pH(state_arr, Ka, unit_conversion) + nh3 = S_IN * unit_conversion[10] * Ka[1] / (Ka[1] + h) + co2 = state_arr[9] * h / (Ka[3] + h) + biogas_S[-1] = co2 Iph = Hill_inhibit(h, pH_ULs, pH_LLs) Iin = substr_inhibit(S_IN, KS_IN) diff --git a/qsdsan/sanunits/_anaerobic_reactor.py b/qsdsan/sanunits/_anaerobic_reactor.py index df356767..ac5d4549 100644 --- a/qsdsan/sanunits/_anaerobic_reactor.py +++ b/qsdsan/sanunits/_anaerobic_reactor.py @@ -571,7 +571,7 @@ def h2_stoichio(state_arr): grad_dydt_Sh2_AD = self.model.grad_dydt_Sh2_AD def solve_h2(QC, S_in, T): Ka = params['Ka_base'] * T_correction_factor(params['T_base'], T, params['Ka_dH']) - h, nh3, co2 = solve_pH(QC, Ka, unit_conversion) + h = solve_pH(QC, Ka, unit_conversion) S_h2_0 = QC[h2_idx] S_h2_in = S_in[h2_idx] S_h2 = newton(dydt_Sh2_AD, S_h2_0, grad_dydt_Sh2_AD, From 5069456d531c5631e2ad5cfa1f9c1df0c1620420 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Mon, 17 Jun 2024 10:18:21 -0700 Subject: [PATCH 371/424] minor bug fix --- qsdsan/processes/_asm2d.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qsdsan/processes/_asm2d.py b/qsdsan/processes/_asm2d.py index 0f9f3253..424dcc91 100644 --- a/qsdsan/processes/_asm2d.py +++ b/qsdsan/processes/_asm2d.py @@ -495,7 +495,7 @@ def _rhos_masm2d(state_arr, params, acceptor_dependent_decay=True): cmps = params['cmps'] params['mass2mol'] = cmps.i_mass / cmps.chem_MW - params['ks'] = ks = rhos * 0 + params['ks'] = ks = np.zeros(19) # rate constants ks[:3] = k_h ks[3:7] = mu_H From 2815f3c9fcc0640245bb53222e958e23c2a3279c Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Mon, 17 Jun 2024 17:11:03 -0700 Subject: [PATCH 372/424] base gas stripping kLa on aeration kLa --- qsdsan/processes/_aeration.py | 4 + qsdsan/processes/_asm2d.py | 50 ++++---- qsdsan/sanunits/_anaerobic_reactor.py | 1 + .../sanunits/_suspended_growth_bioreactor.py | 118 ++++++++++++++++-- 4 files changed, 138 insertions(+), 35 deletions(-) diff --git a/qsdsan/processes/_aeration.py b/qsdsan/processes/_aeration.py index 300a6573..d5a0ffec 100644 --- a/qsdsan/processes/_aeration.py +++ b/qsdsan/processes/_aeration.py @@ -183,6 +183,8 @@ def KLa_20(self, i): self._Q_air = None self.KLa = None + kLa_20 = KLa_20 + @property def Q_air(self): """[float] Airflow rate at field conditions, [m^3/d].""" @@ -344,6 +346,8 @@ def KLa(self, KLa): self._KLa = KLa or self._calc_KLa() self.set_parameters(KLa=self._KLa) + kLa = KLa + @property def DOsat(self): """ diff --git a/qsdsan/processes/_asm2d.py b/qsdsan/processes/_asm2d.py index 424dcc91..86312d74 100644 --- a/qsdsan/processes/_asm2d.py +++ b/qsdsan/processes/_asm2d.py @@ -453,10 +453,6 @@ def __new__(cls, components=None, _mpath = ospath.join(data_path, 'process_data/_masm2d.tsv') _mmp = ospath.join(data_path, 'process_data/_mmp.tsv') -# partial pressure in air -p_n2_air = 0.78 # atm -p_co2_air = 3.947e-4 - def acid_base_rxn(h_ion, ionic_states, Ka): K, Mg, Ca, Na, Cl, NOx, NH, IC, IP, Ac = ionic_states # in M Kw, Knh, Kc1, Kc2, Kp1, Kp2, Kp3, Kac = Ka @@ -476,7 +472,8 @@ def solve_pH(state_arr, Ka, unit_conversion): xtol=1e-12, maxiter=100) return h -rhos = np.zeros(19+7+2) # 19 biological processes, 7 precipitation/dissociation, 2 gas stripping +# rhos = np.zeros(19+7+2) # 19 biological processes, 7 precipitation/dissociation, 2 gas stripping +rhos = np.zeros(19+7) # 19 biological processes, 7 precipitation/dissociation def _rhos_masm2d(state_arr, params, acceptor_dependent_decay=True): if 'ks' not in params: k_h, mu_H, mu_PAO, mu_AUT, \ @@ -605,10 +602,10 @@ def _rhos_masm2d(state_arr, params, acceptor_dependent_decay=True): rhos[19:26] *= k_mmp ########### gas stripping ########### - kLa_n2, kLa_co2 = params['kLa'] - KH_n2, KH_co2 = params['K_Henry'] # assume already temperature-corrected - rhos[26] = kLa_n2*(S_N2 - KH_n2*p_n2_air/mass2mol[1]*1e3) # M/atm * atm / mol/g * 1000 mg/g = mg/L - rhos[27] = kLa_co2*(co2 - KH_co2*p_co2_air/mass2mol[8]*1e3) # M/atm * atm / mol/g * 1000 mg/g = mg/L + # kLa_n2, kLa_co2 = params['kLa'] + # KH_n2, KH_co2 = params['K_Henry'] # assume already temperature-corrected + # rhos[26] = kLa_n2*(S_N2 - KH_n2*p_n2_air/mass2mol[1]*1e3) # M/atm * atm / mol/g * 1000 mg/g = mg/L + # rhos[27] = kLa_co2*(co2 - KH_co2*p_co2_air/mass2mol[8]*1e3) # M/atm * atm / mol/g * 1000 mg/g = mg/L return rhos @@ -662,12 +659,6 @@ class mASM2d(CompiledProcesses): K_FeOH : float, optional Half saturation coefficient of ferric hydroxide for FePO4 precipitation [mg-Fe(OH)3/L]. The default is 0.001. - kLa : iterable[float], optional - Gas transfer rate constant for gas stripping/dissolution [d^(-1)], - following the order of `mASM2d._gas_stripping`. The default is (3.0, 3.0). - K_Henry : iterable[float], optional - Henry's law constants [mol/L/atm], following the order of - `mASM2d._gas_stripping`. The default is (6.5e-4, 3.5e-2). pKa : iterable[float], optional Equilibrium coefficient values of acid-base pairs, unitless, following the order of `mASM2d._acid_base_pairs`. @@ -717,7 +708,8 @@ class mASM2d(CompiledProcesses): 'K_P_H', 'K_P_PAO', 'K_P_AUT', 'K_P_S', 'K_PP', 'K_MAX', 'K_IPP', 'K_PHA', 'k_mmp', 'Ksp', 'K_dis', 'K_AlOH', 'K_FeOH', - 'kLa', 'K_Henry', 'Ka', 'cmps') + # 'kLa_min', 'f_kLa', 'K_Henry', + 'Ka', 'cmps') _acid_base_pairs = (('H+', 'OH-'), ('NH4+', 'NH3'), ('CO2', 'HCO3-'), ('HCO3-', 'CO3-2'), @@ -725,7 +717,11 @@ class mASM2d(CompiledProcesses): ('HAc', 'Ac-'),) _precipitates = ('X_CaCO3', 'X_struv', 'X_newb', 'X_ACP', 'X_MgCO3', 'X_AlPO4', 'X_FePO4') - _gas_stripping = ('S_N2', 'S_IC') + gas_IDs = ['S_N2', 'S_IC'] + kLa_min = [3.0, 3.0] + K_Henry = [6.5e-4, 3.5e-2] # 20 degree C + D_gas = [1.88e-9, 1.92e-9] # diffusivity + p_gas_atm = [0.78, 3.947e-4]# partial pressure in air def __new__(cls, components=None, path=None, electron_acceptor_dependent_decay=True, f_SI=0.0, Y_H=0.625, Y_PAO=0.625, Y_PO4=0.4, Y_PHA=0.2, Y_A=0.24, @@ -745,7 +741,7 @@ def __new__(cls, components=None, path=None, electron_acceptor_dependent_decay=T pKsp=(6.45, 13.16, 5.8, 23, 7, 21, 26), K_dis=(1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0), K_AlOH=0.001, K_FeOH=0.001, - kLa=(3.0, 3.0), K_Henry=(6.5e-4, 3.5e-2), + # kLa_min=(3.0, 3.0), K_Henry=(6.5e-4, 3.5e-2), pKa=(14, 9.25, 6.37, 10.32, 2.12, 7.21, 12.32, 4.76), **kwargs): @@ -799,13 +795,13 @@ def __new__(cls, components=None, path=None, electron_acceptor_dependent_decay=T self.extend(mmp) - for gas in cls._gas_stripping: - new_p = Process('%s_stripping' % gas.lstrip('S_'), - reaction={gas:-1}, - ref_component=gas, - conserved_for=(),) - self.append(new_p) - + # for gas in cls._gas_stripping: + # new_p = Process('%s_stripping' % gas.lstrip('S_'), + # reaction={gas:-1}, + # ref_component=gas, + # conserved_for=(),) + # self.append(new_p) + self.K_Henry = [K*mol_to_mass[cmps.index(i)]*1000 for K, i in zip(cls.K_Henry, cls.gas_IDs)] self.compile(to_class=cls) dct = self.__dict__ @@ -818,6 +814,7 @@ def __new__(cls, components=None, path=None, electron_acceptor_dependent_decay=T rhos_masm2d = lambda state_arr, params: _rhos_masm2d(state_arr, params, electron_acceptor_dependent_decay) self.set_rate_function(rhos_masm2d) Ka = np.array([10**(-p) for p in pKa]) + # f_kLa = np.array(cls.D_gas)/cls.D_O2 kinetic_vals = (k_h, mu_H, mu_PAO, mu_AUT, q_fe, q_PHA, q_PP, b_H, b_PAO, b_PP, b_PHA, b_AUT, @@ -831,7 +828,8 @@ def __new__(cls, components=None, path=None, electron_acceptor_dependent_decay=T K_PP, K_MAX, K_IPP, K_PHA, np.array(k_mmp), Ksp_mass, np.array(K_dis), K_AlOH, K_FeOH, - kLa, K_Henry, Ka, cmps, + # kLa_min, f_kLa, K_Henry, + Ka, cmps, ) self.rate_function._params = dict(zip(cls._kinetic_params, kinetic_vals)) diff --git a/qsdsan/sanunits/_anaerobic_reactor.py b/qsdsan/sanunits/_anaerobic_reactor.py index ac5d4549..ab824844 100644 --- a/qsdsan/sanunits/_anaerobic_reactor.py +++ b/qsdsan/sanunits/_anaerobic_reactor.py @@ -573,6 +573,7 @@ def solve_h2(QC, S_in, T): Ka = params['Ka_base'] * T_correction_factor(params['T_base'], T, params['Ka_dH']) h = solve_pH(QC, Ka, unit_conversion) S_h2_0 = QC[h2_idx] + # S_h2_0 = 2.8309E-07 S_h2_in = S_in[h2_idx] S_h2 = newton(dydt_Sh2_AD, S_h2_0, grad_dydt_Sh2_AD, args=(QC, h, params, h2_stoichio, V_liq, S_h2_in), diff --git a/qsdsan/sanunits/_suspended_growth_bioreactor.py b/qsdsan/sanunits/_suspended_growth_bioreactor.py index d2126e75..2b86d900 100644 --- a/qsdsan/sanunits/_suspended_growth_bioreactor.py +++ b/qsdsan/sanunits/_suspended_growth_bioreactor.py @@ -94,17 +94,28 @@ class CSTR(SanUnit): _N_outs = 1 _ins_size_is_fixed = False _outs_size_is_fixed = False + + _D_O2 = 2.10e-9 # m2/s def __init__(self, ID='', ins=None, outs=(), split=None, thermo=None, init_with='WasteStream', V_max=1000, W_tank = 6.4, D_tank = 3.65, freeboard = 0.61, t_wall = None, t_slab = None, aeration=2.0, - DO_ID='S_O2', suspended_growth_model=None, isdynamic=True, exogenous_vars=(), **kwargs): + DO_ID='S_O2', suspended_growth_model=None, + gas_stripping=False, gas_IDs=None, stripping_kLa_min=None, + K_Henry=None, D_gas=None, p_gas_atm=None, + isdynamic=True, exogenous_vars=(), **kwargs): SanUnit.__init__(self, ID, ins, outs, thermo, init_with, isdynamic=isdynamic, exogenous_vars=exogenous_vars, **kwargs) self._V_max = V_max self._aeration = aeration self._DO_ID = DO_ID self._model = suspended_growth_model + self.gas_IDs = gas_IDs + self.stripping_kLa_min = stripping_kLa_min + self.K_Henry = K_Henry + self.D_gas = D_gas + self.p_gas_atm = p_gas_atm + self.gas_stripping = gas_stripping self._concs = None self._mixed = WasteStream() self.split = split @@ -222,6 +233,26 @@ def DO_ID(self, doid): f'i.e., one of {self.components.IDs}.') self._DO_ID = doid + @property + def gas_stripping(self): + return self._gstrip + @gas_stripping.setter + def gas_stripping(self, strip): + self._gstrip = strip = bool(strip) + if strip: + if self.gas_IDs: + cmps = self.components.IDs + for i in self.gas_IDs: + if i not in cmps: + raise RuntimeError((f'gas ID {i} not in component set: {cmps}.')) + else: + mdl = self._model + self.gas_IDs = mdl.gas_IDs + self.stripping_kLa_min = mdl.kLa_min + self.D_gas = mdl.D_gas + self.K_Henry = mdl.K_Henry + self.p_gas_atm = mdl.p_gas_atm + @property def split(self): '''[numpy.1darray or NoneType] The volumetric split of outs.''' @@ -307,22 +338,31 @@ def _compile_ODE(self): isa = isinstance C = list(symbols(self.components.IDs)) m = len(C) + aer = self._aeration if self._model is None: warn(f'{self.ID} was initialized without a suspended growth model, ' f'and thus run as a non-reactive unit') r = lambda state_arr: np.zeros(m) else: - processes = _add_aeration_to_growth_model(self._aeration, self._model) + processes = _add_aeration_to_growth_model(aer, self._model) r = processes.production_rates_eval _dstate = self._dstate _update_dstate = self._update_dstate V = self._V_max + kLa = self.kLa + gstrip = self.gas_stripping + if gstrip: + gas_idx = self.components.indices(self.gas_IDs) + if isa(aer, Process): kLa = aer.kLa + else: kLa = 0. + S_gas_air = np.asarray(self.K_Henry)*np.asarray(self.p_gas_atm) + kLa_stripping = np.maximum(kLa*self.D_gas/self._D_O2, self.stripping_kLa_min) hasexo = bool(len(self._exovars)) f_exovars = self.eval_exo_dynamic_vars - if isa(self._aeration, (float, int)): + if isa(aer, (float, int)): i = self.components.index(self._DO_ID) fixed_DO = self._aeration def dy_dt(t, QC_ins, QC, dQC_ins): @@ -330,6 +370,7 @@ def dy_dt(t, QC_ins, QC, dQC_ins): dydt_cstr(QC_ins, QC, V, _dstate) if hasexo: QC = np.append(QC, f_exovars(t)) _dstate[:-1] += r(QC) + if gstrip: _dstate[gas_idx] -= kLa_stripping * (QC[gas_idx] - S_gas_air) _dstate[i] = 0 _update_dstate() else: @@ -337,6 +378,7 @@ def dy_dt(t, QC_ins, QC, dQC_ins): dydt_cstr(QC_ins, QC, V, _dstate) if hasexo: QC = np.append(QC, f_exovars(t)) _dstate[:-1] += r(QC) + if gstrip: _dstate[gas_idx] -= kLa_stripping * (QC[gas_idx] - S_gas_air) _update_dstate() self._ODE = dy_dt @@ -763,6 +805,21 @@ class PFR(SanUnit): The default is [0, 0, 120, 120, 60]. DO_sat : float, optional Saturation dissolved oxygen concentration [mg/L]. The default is 8.0. + gas_stripping : bool, optional + Whether to model gas stripping. The default is False. + gas_IDs : iterable[str], optional + Component IDs of stripped gases. The default is None. + stripping_kLa_min : iterable[float], optional + Minimum gas transfer rate constants [d^(-1)] of each stripped gas component. + The default is None. + K_Henry : iterable[float], optional + Henry's law constants [(conc)/atm], where "conc" indicate the concentration + unit for state variables in the suspended growth model. The default is None. + D_gas : iterable[float], optional + Gas diffusion coefficients in water [m2/s]. The default is None. + p_gas_atm : iterable[float], optional + Partial pressure of the stripped gases in the air [atm]. The default is None. + Examples -------- @@ -838,12 +895,16 @@ class PFR(SanUnit): _N_outs = 1 _ins_size_is_fixed = False _outs_size_is_fixed = True - + + _D_O2 = 2.10e-9 # m2/s + def __init__(self, ID='', ins=None, outs=(), thermo=None, init_with='WasteStream', N_tanks_in_series=5, V_tanks=[1500, 1500, 3000, 3000, 3000], influent_fractions=[[1.0, 0,0,0,0]], internal_recycles=[(4,0,35000),], DO_setpoints=[], kLa=[0, 0, 120, 120, 60], DO_sat=8.0, DO_ID='S_O2', suspended_growth_model=None, + gas_stripping=False, gas_IDs=None, stripping_kLa_min=None, + K_Henry=None, D_gas=None, p_gas_atm=None, isdynamic=True, **kwargs): exogenous_vars = kwargs.pop('exogenous_vars', None) @@ -860,6 +921,12 @@ def __init__(self, ID='', ins=None, outs=(), thermo=None, init_with='WasteStream self.DO_sat = DO_sat self.DO_ID = DO_ID self.suspended_growth_model = suspended_growth_model + self.gas_IDs = gas_IDs + self.stripping_kLa_min = stripping_kLa_min + self.K_Henry = K_Henry + self.D_gas = D_gas + self.p_gas_atm = p_gas_atm + self.gas_stripping = gas_stripping self._concs = None self._Qs = self.V_tanks * 0 @@ -951,14 +1018,13 @@ def kLa(self, ks): if ks != []: warn('kLa is ignored because DO setpoints have been specified. ' 'To specify kLa, first set DO_setpoints as []') - self._kLa = [] else: if not iter(ks): raise TypeError(f'V_tanks must be an iterable, not {type(ks).__name__}') elif len(ks) != self.N_tanks_in_series: raise RuntimeError(f'cannot set kLa of {self.N_tanks_in_series} tanks' f'in series with {len(ks)} value(s).') - else: self._kLa = np.asarray(ks) + self._kLa = np.asarray(ks) @property def suspended_growth_model(self): @@ -992,6 +1058,26 @@ def DO_ID(self, doid): raise ValueError(f'DO_ID must be in the set of `CompiledComponents` used to set thermo, ' f'i.e., one of {self.components.IDs}.') self._DO_ID = doid + + @property + def gas_stripping(self): + return self._gstrip + @gas_stripping.setter + def gas_stripping(self, strip): + self._gstrip = strip = bool(strip) + if strip: + if self.gas_IDs: + cmps = self.components.IDs + for i in self.gas_IDs: + if i not in cmps: + raise RuntimeError((f'gas ID {i} not in component set: {cmps}.')) + else: + mdl = self._model + self.gas_IDs = mdl.gas_IDs + self.stripping_kLa_min = mdl.kLa_min + self.D_gas = mdl.D_gas + self.K_Henry = mdl.K_Henry + self.p_gas_atm = mdl.p_gas_atm def _run(self): out, = self.outs @@ -1076,6 +1162,15 @@ def _compile_ODE(self): f_in = self.influent_fractions DO = self.DO_setpoints kLa = self.kLa + if not any(kLa): kLa = np.zeros(N) + gstrip = self.gas_stripping + if gstrip: + gas_idx = self.components.indices(self.gas_IDs) + S_gas_air = np.asarray(self.K_Henry)*np.asarray(self.p_gas_atm) + S_gas_air = np.tile(S_gas_air, (N, 1)) + D_O2 = self._D_O2 + kLa_stripping = np.array([np.maximum(kLa*D/D_O2, kmin) + for D, kmin in zip(self.D_gas, self.stripping_kLa_min)]).T rcy = self.internal_recycles DO_idx = self.components.index(self.DO_ID) DOsat = self.DO_sat @@ -1086,7 +1181,7 @@ def _compile_ODE(self): if self._model is None: warn(f'{self.ID} was initialized without a suspended growth model, ' f'and thus run as a non-reactive unit') - Rs = lambda Cs: 0. + Rs = lambda Cs : 0. else: f_rho = self._model.rate_function M_stoi = self._model.stoichio_eval() @@ -1117,11 +1212,13 @@ def dy_dt(t, QC_ins, QC, dQC_ins): dy = np.zeros_like(y) dy[:,:-1] = _1_ov_V @ (M_ins - M_outs) + rxn dy[aerated_zones, DO_idx] = 0. + if gstrip: + S_liq = Cs[:, gas_idx] + dy[:, gas_idx] -= kLa_stripping*(S_liq - S_gas_air) _dstate[:] = dy.flatten() _update_dstate() else: - if not any(kLa): kLa = np.zeros(N) # @njit def dy_dt(t, QC_ins, QC, dQC_ins): y = QC.reshape((N, ncol)) @@ -1139,7 +1236,10 @@ def dy_dt(t, QC_ins, QC, dQC_ins): rxn = Rs(Cs) dy = np.zeros_like(y) dy[:,:-1] = _1_ov_V @ (M_ins - M_outs) + rxn - dy[:,DO_idx] += aer + dy[:,DO_idx] += aer + if gstrip: + S_liq = Cs[:, gas_idx] + dy[:, gas_idx] -= kLa_stripping*(S_liq - S_gas_air) _dstate[:] = dy.flatten() _update_dstate() From 89341712a96554da4bd5099c14a2324c85ee1c58 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Wed, 19 Jun 2024 10:38:03 -0700 Subject: [PATCH 373/424] debug gas stripping algorithm --- qsdsan/processes/_asm2d.py | 2 +- qsdsan/sanunits/_anaerobic_reactor.py | 4 ++-- qsdsan/sanunits/_suspended_growth_bioreactor.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/qsdsan/processes/_asm2d.py b/qsdsan/processes/_asm2d.py index 86312d74..7425e58c 100644 --- a/qsdsan/processes/_asm2d.py +++ b/qsdsan/processes/_asm2d.py @@ -801,11 +801,11 @@ def __new__(cls, components=None, path=None, electron_acceptor_dependent_decay=T # ref_component=gas, # conserved_for=(),) # self.append(new_p) - self.K_Henry = [K*mol_to_mass[cmps.index(i)]*1000 for K, i in zip(cls.K_Henry, cls.gas_IDs)] self.compile(to_class=cls) dct = self.__dict__ dct.update(kwargs) + dct['K_Henry'] = [K*mol_to_mass[cmps.index(i)]*1000 for K, i in zip(cls.K_Henry, cls.gas_IDs)] dct['mmp_stoichio'] = mmp_stoichio stoichio_vals = (f_SI, Y_H, Y_PAO, Y_PO4, Y_PHA, Y_A, f_XI_H, f_XI_PAO, f_XI_AUT, diff --git a/qsdsan/sanunits/_anaerobic_reactor.py b/qsdsan/sanunits/_anaerobic_reactor.py index ab824844..5054feeb 100644 --- a/qsdsan/sanunits/_anaerobic_reactor.py +++ b/qsdsan/sanunits/_anaerobic_reactor.py @@ -572,8 +572,8 @@ def h2_stoichio(state_arr): def solve_h2(QC, S_in, T): Ka = params['Ka_base'] * T_correction_factor(params['T_base'], T, params['Ka_dH']) h = solve_pH(QC, Ka, unit_conversion) - S_h2_0 = QC[h2_idx] - # S_h2_0 = 2.8309E-07 + # S_h2_0 = QC[h2_idx] + S_h2_0 = 2.8309E-07 S_h2_in = S_in[h2_idx] S_h2 = newton(dydt_Sh2_AD, S_h2_0, grad_dydt_Sh2_AD, args=(QC, h, params, h2_stoichio, V_liq, S_h2_in), diff --git a/qsdsan/sanunits/_suspended_growth_bioreactor.py b/qsdsan/sanunits/_suspended_growth_bioreactor.py index 2b86d900..bbcc9129 100644 --- a/qsdsan/sanunits/_suspended_growth_bioreactor.py +++ b/qsdsan/sanunits/_suspended_growth_bioreactor.py @@ -1169,7 +1169,7 @@ def _compile_ODE(self): S_gas_air = np.asarray(self.K_Henry)*np.asarray(self.p_gas_atm) S_gas_air = np.tile(S_gas_air, (N, 1)) D_O2 = self._D_O2 - kLa_stripping = np.array([np.maximum(kLa*D/D_O2, kmin) + kLa_stripping = np.array([np.maximum(kLa*(D/D_O2)**0.5, kmin) for D, kmin in zip(self.D_gas, self.stripping_kLa_min)]).T rcy = self.internal_recycles DO_idx = self.components.index(self.DO_ID) @@ -1181,7 +1181,7 @@ def _compile_ODE(self): if self._model is None: warn(f'{self.ID} was initialized without a suspended growth model, ' f'and thus run as a non-reactive unit') - Rs = lambda Cs : 0. + Rs = lambda Cs: 0. else: f_rho = self._model.rate_function M_stoi = self._model.stoichio_eval() From b4a4118a3825f6d101311afa4db94542c1551369 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Mon, 24 Jun 2024 15:42:19 -0700 Subject: [PATCH 374/424] try alternative mmp kinetics --- qsdsan/processes/_adm1_p_extension.py | 60 +++++++++++++++++++-------- qsdsan/processes/_asm2d.py | 4 +- 2 files changed, 44 insertions(+), 20 deletions(-) diff --git a/qsdsan/processes/_adm1_p_extension.py b/qsdsan/processes/_adm1_p_extension.py index aeb9df6d..d4e7df39 100644 --- a/qsdsan/processes/_adm1_p_extension.py +++ b/qsdsan/processes/_adm1_p_extension.py @@ -736,24 +736,46 @@ def _rhos_adm1p(state_arr, params, h=None): ########## precipitation-dissolution ############# k_mmp = params['k_mmp'] Ksp = params['Ksp'] - K_dis = params['K_dis'] - K_AlOH = params['K_AlOH'] - K_FeOH = params['K_FeOH'] + # K_dis = params['K_dis'] + # K_AlOH = params['K_AlOH'] + # K_FeOH = params['K_FeOH'] S_Mg, S_Ca, X_CaCO3, X_struv, X_newb, X_ACP, X_MgCO3 = state_arr[28:35] X_AlOH, X_FeOH = state_arr[[35,37]] - f_dis = Monod(state_arr[30:35], K_dis[:5]) - if X_CaCO3 > 0: rhos_p[25] = (S_Ca * co3 - Ksp[0]) * f_dis[0] - else: rhos_p[25] = S_Ca * co3 - if X_struv > 0: rhos_p[26] = (S_Mg * nh4 * po4 - Ksp[1]) * f_dis[1] - else: rhos_p[26] = S_Mg * nh4 * po4 - if X_newb > 0: rhos_p[27] = (S_Mg * hpo4 - Ksp[2]) * f_dis[2] - else: rhos_p[27] = S_Mg * hpo4 - if X_ACP > 0: rhos_p[28] = (S_Ca**3 * po4**2 - Ksp[3]) * f_dis[3] - else: rhos_p[28] = S_Ca**3 * po4**2 - if X_MgCO3 > 0: rhos_p[29] = (S_Mg * co3 - Ksp[4]) * f_dis[4] - else: rhos_p[29] = S_Mg * co3 - rhos_p[30] = X_AlOH * po4 * Monod(X_AlOH, K_AlOH) - rhos_p[31] = X_FeOH * po4 * Monod(X_FeOH, K_FeOH) + # f_dis = Monod(state_arr[30:35], K_dis[:5]) + # if X_CaCO3 > 0: rhos_p[25] = (S_Ca * co3 - Ksp[0]) * f_dis[0] + # else: rhos_p[25] = S_Ca * co3 + # if X_struv > 0: rhos_p[26] = (S_Mg * nh4 * po4 - Ksp[1]) * f_dis[1] + # else: rhos_p[26] = S_Mg * nh4 * po4 + # if X_newb > 0: rhos_p[27] = (S_Mg * hpo4 - Ksp[2]) * f_dis[2] + # else: rhos_p[27] = S_Mg * hpo4 + # if X_ACP > 0: rhos_p[28] = (S_Ca**3 * po4**2 - Ksp[3]) * f_dis[3] + # else: rhos_p[28] = S_Ca**3 * po4**2 + # if X_MgCO3 > 0: rhos_p[29] = (S_Mg * co3 - Ksp[4]) * f_dis[4] + # else: rhos_p[29] = S_Mg * co3 + + # rhos_p[30] = X_AlOH * po4 * Monod(X_AlOH, K_AlOH) + # rhos_p[31] = X_FeOH * po4 * Monod(X_FeOH, K_FeOH) + + SI = (S_Ca * co3 / Ksp[0])**(1/2) + if SI > 1: rhos_p[25] = X_CaCO3 * (SI-1)**2 + else: rhos_p[25] = 0 + + SI = (S_Mg * nh4 * po4 / Ksp[1])**(1/3) + if SI > 1: rhos_p[26] = X_struv * (SI-1)**3 + else: rhos_p[26] = 0 + + SI = (S_Mg * hpo4 / Ksp[2])**(1/2) + if SI > 1: rhos_p[27] = X_newb * (SI-1)**2 + else: rhos_p[27] = 0 + + SI = (S_Ca**3 * po4**2 / Ksp[3])**(1/5) + if SI > 1: rhos_p[28] = X_ACP * (SI-1)**5 + else: rhos_p[28] = 0 + + SI = (S_Mg * co3 / Ksp[4])**(1/2) + if SI > 1: rhos_p[29] = X_MgCO3 * (SI-1)**2 + else: rhos_p[29] = 0 + rhos_p[25:32] *= k_mmp biogas_S = state_arr[7:10].copy() @@ -892,8 +914,10 @@ def __new__(cls, components=None, path=None, Ka_dH=[55900, 51965, 17400, 14600, -7500, 3000, 15000, 0, 0, 0, 0], kLa=200, K_H_base=[7.8e-4, 1.4e-3, 3.5e-2], K_H_dH=[-4180, -14240, -19410], - k_mmp=(5.0, 300, 0.05, 150, 50, 1.0, 1.0), - pKsp=(6.45, 13.16, 5.8, 23, 7, 21, 26), + # k_mmp=(5.0, 300, 0.05, 150, 50, 1.0, 1.0), + # pKsp=(6.45, 13.16, 5.8, 23, 7, 21, 26), + k_mmp=(0.024, 120, 0.024, 72, 0.024, 0.024, 0.024), # Flores-Alsina 2016 + pKsp=(8.3, 13.6, 18.175, 28.92, 7.46, 18.2, 37.76), # Flores-Alsina 2016 K_dis=(1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0), K_AlOH=0.001, K_FeOH=0.001, **kwargs): diff --git a/qsdsan/processes/_asm2d.py b/qsdsan/processes/_asm2d.py index 7425e58c..c82d43e7 100644 --- a/qsdsan/processes/_asm2d.py +++ b/qsdsan/processes/_asm2d.py @@ -78,8 +78,8 @@ def create_asm2d_cmps(set_thermo=True): def create_masm2d_cmps(set_thermo=True): c2d = create_asm2d_cmps(False) ion_kwargs = dict(particle_size='Soluble', - degradability='Undegradable', - organic=False) + degradability='Undegradable', + organic=False) mineral_kwargs = dict(particle_size='Particulate', degradability='Undegradable', organic=False) From 9a4da99ab80516f195620486137cc3079bcb7efc Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Tue, 25 Jun 2024 10:22:23 -0700 Subject: [PATCH 375/424] consistency --- qsdsan/sanunits/_anaerobic_reactor.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qsdsan/sanunits/_anaerobic_reactor.py b/qsdsan/sanunits/_anaerobic_reactor.py index 5054feeb..13601d17 100644 --- a/qsdsan/sanunits/_anaerobic_reactor.py +++ b/qsdsan/sanunits/_anaerobic_reactor.py @@ -587,6 +587,7 @@ def update_h2_dstate(dstate): pass def dy_dt(t, QC_ins, QC, dQC_ins): QC[QC < 2.2e-16] = 0. + _state[_state < 2.2e-16] = 0. Q_ins = QC_ins[:, -1] S_ins = QC_ins[:, :-1] * 1e-3 # mg/L to kg/m3 Q = sum(Q_ins) From abc01450e79320155db036a4d5981798a0e7ba37 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Tue, 25 Jun 2024 16:06:55 -0700 Subject: [PATCH 376/424] Ca conservation in `mASM2d` and `ADM1p` --- qsdsan/processes/_adm1_p_extension.py | 4 ++-- qsdsan/processes/_asm2d.py | 1 + qsdsan/sanunits/_anaerobic_reactor.py | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/qsdsan/processes/_adm1_p_extension.py b/qsdsan/processes/_adm1_p_extension.py index d4e7df39..050e0230 100644 --- a/qsdsan/processes/_adm1_p_extension.py +++ b/qsdsan/processes/_adm1_p_extension.py @@ -557,7 +557,7 @@ def create_adm1p_cmps(set_thermo=True): cmp.i_C = 0.36612 cmp.i_N = 0.08615 cmp.i_P = 0.02154 - cmp.i_K = cmp.i_Mg = 0. + cmp.i_K = cmp.i_Mg = cmp.i_Ca = 0. cmp.i_mass = 0.90 cmp.f_Vmass_Totmass = 0.85 cmp.i_NOD = None @@ -916,7 +916,7 @@ def __new__(cls, components=None, path=None, K_H_dH=[-4180, -14240, -19410], # k_mmp=(5.0, 300, 0.05, 150, 50, 1.0, 1.0), # pKsp=(6.45, 13.16, 5.8, 23, 7, 21, 26), - k_mmp=(0.024, 120, 0.024, 72, 0.024, 0.024, 0.024), # Flores-Alsina 2016 + k_mmp=(0.024, 120, 0.024, 72, 0.024, 0.024, 0.024), # Flores-Alsina 2016 pKsp=(8.3, 13.6, 18.175, 28.92, 7.46, 18.2, 37.76), # Flores-Alsina 2016 K_dis=(1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0), K_AlOH=0.001, K_FeOH=0.001, diff --git a/qsdsan/processes/_asm2d.py b/qsdsan/processes/_asm2d.py index c82d43e7..92f202d9 100644 --- a/qsdsan/processes/_asm2d.py +++ b/qsdsan/processes/_asm2d.py @@ -109,6 +109,7 @@ def create_masm2d_cmps(set_thermo=True): c2d.X_H.i_P = c2d.X_AUT.i_P = c2d.X_PAO.i_P = 0.02154 c2d.X_H.i_K = c2d.X_AUT.i_K = c2d.X_PAO.i_K = 0.0 c2d.X_H.i_Mg = c2d.X_AUT.i_Mg = c2d.X_PAO.i_Mg = 0.0 + c2d.X_H.i_Ca = c2d.X_AUT.i_Ca = c2d.X_PAO.i_Ca = 0.0 c2d.X_H.i_mass = c2d.X_AUT.i_mass = c2d.X_PAO.i_mass = 0.90 c2d.X_H.f_Vmass_Totmass = c2d.X_AUT.f_Vmass_Totmass = c2d.X_PAO.f_Vmass_Totmass = 0.85 diff --git a/qsdsan/sanunits/_anaerobic_reactor.py b/qsdsan/sanunits/_anaerobic_reactor.py index 13601d17..a21eccf8 100644 --- a/qsdsan/sanunits/_anaerobic_reactor.py +++ b/qsdsan/sanunits/_anaerobic_reactor.py @@ -448,6 +448,7 @@ def _init_state(self): def _update_state(self): y = self._state y[-1] = sum(ws.state[-1] for ws in self.ins) + y[y<0] = 0. f_rtn = self._f_retain i_mass = self.components.i_mass chem_MW = self.components.chem_MW @@ -587,7 +588,6 @@ def update_h2_dstate(dstate): pass def dy_dt(t, QC_ins, QC, dQC_ins): QC[QC < 2.2e-16] = 0. - _state[_state < 2.2e-16] = 0. Q_ins = QC_ins[:, -1] S_ins = QC_ins[:, :-1] * 1e-3 # mg/L to kg/m3 Q = sum(Q_ins) From d41a08c895639710c77c220fad5ebbef7162eb7e Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Tue, 25 Jun 2024 16:23:27 -0700 Subject: [PATCH 377/424] consistent MMP model between ASM and ADM --- qsdsan/processes/_adm1_p_extension.py | 8 ++--- qsdsan/processes/_asm2d.py | 44 +++++++++++++++++++-------- 2 files changed, 36 insertions(+), 16 deletions(-) diff --git a/qsdsan/processes/_adm1_p_extension.py b/qsdsan/processes/_adm1_p_extension.py index 050e0230..3500bff2 100644 --- a/qsdsan/processes/_adm1_p_extension.py +++ b/qsdsan/processes/_adm1_p_extension.py @@ -737,8 +737,8 @@ def _rhos_adm1p(state_arr, params, h=None): k_mmp = params['k_mmp'] Ksp = params['Ksp'] # K_dis = params['K_dis'] - # K_AlOH = params['K_AlOH'] - # K_FeOH = params['K_FeOH'] + K_AlOH = params['K_AlOH'] + K_FeOH = params['K_FeOH'] S_Mg, S_Ca, X_CaCO3, X_struv, X_newb, X_ACP, X_MgCO3 = state_arr[28:35] X_AlOH, X_FeOH = state_arr[[35,37]] # f_dis = Monod(state_arr[30:35], K_dis[:5]) @@ -753,8 +753,8 @@ def _rhos_adm1p(state_arr, params, h=None): # if X_MgCO3 > 0: rhos_p[29] = (S_Mg * co3 - Ksp[4]) * f_dis[4] # else: rhos_p[29] = S_Mg * co3 - # rhos_p[30] = X_AlOH * po4 * Monod(X_AlOH, K_AlOH) - # rhos_p[31] = X_FeOH * po4 * Monod(X_FeOH, K_FeOH) + rhos_p[30] = X_AlOH * po4 * Monod(X_AlOH, K_AlOH) + rhos_p[31] = X_FeOH * po4 * Monod(X_FeOH, K_FeOH) SI = (S_Ca * co3 / Ksp[0])**(1/2) if SI > 1: rhos_p[25] = X_CaCO3 * (SI-1)**2 diff --git a/qsdsan/processes/_asm2d.py b/qsdsan/processes/_asm2d.py index 92f202d9..d07ea6cd 100644 --- a/qsdsan/processes/_asm2d.py +++ b/qsdsan/processes/_asm2d.py @@ -584,20 +584,40 @@ def _rhos_masm2d(state_arr, params, acceptor_dependent_decay=True): ########## precipitation-dissolution ############# k_mmp = params['k_mmp'] Ksp = params['Ksp'] - K_dis = params['K_dis'] + # K_dis = params['K_dis'] K_AlOH = params['K_AlOH'] K_FeOH = params['K_FeOH'] - f_dis = Monod(state_arr[19:24], K_dis[:5]) - if X_CaCO3 > 0: rhos[19] = (S_Ca * co3 - Ksp[0]) * f_dis[0] - else: rhos[19] = S_Ca * co3 - if X_struv > 0: rhos[20] = (S_Mg * nh4 * po4 - Ksp[1]) * f_dis[1] - else: rhos[20] = S_Mg * nh4 * po4 - if X_newb > 0: rhos[21] = (S_Mg * hpo4 - Ksp[2]) * f_dis[2] - else: rhos[21] = S_Mg * hpo4 - if X_ACP > 0: rhos[22] = (S_Ca**3 * po4**2 - Ksp[3]) * f_dis[3] - else: rhos[22] = S_Ca**3 * po4**2 - if X_MgCO3 > 0: rhos[23] = (S_Mg * co3 - Ksp[4]) * f_dis[4] - else: rhos[23] = S_Mg * co3 + # f_dis = Monod(state_arr[19:24], K_dis[:5]) + # if X_CaCO3 > 0: rhos[19] = (S_Ca * co3 - Ksp[0]) * f_dis[0] + # else: rhos[19] = S_Ca * co3 + # if X_struv > 0: rhos[20] = (S_Mg * nh4 * po4 - Ksp[1]) * f_dis[1] + # else: rhos[20] = S_Mg * nh4 * po4 + # if X_newb > 0: rhos[21] = (S_Mg * hpo4 - Ksp[2]) * f_dis[2] + # else: rhos[21] = S_Mg * hpo4 + # if X_ACP > 0: rhos[22] = (S_Ca**3 * po4**2 - Ksp[3]) * f_dis[3] + # else: rhos[22] = S_Ca**3 * po4**2 + # if X_MgCO3 > 0: rhos[23] = (S_Mg * co3 - Ksp[4]) * f_dis[4] + # else: rhos[23] = S_Mg * co3 + SI = (S_Ca * co3 / Ksp[0])**(1/2) + if SI > 1: rhos[19] = X_CaCO3 * (SI-1)**2 + else: rhos[19] = 0 + + SI = (S_Mg * nh4 * po4 / Ksp[1])**(1/3) + if SI > 1: rhos[20] = X_struv * (SI-1)**3 + else: rhos[20] = 0 + + SI = (S_Mg * hpo4 / Ksp[2])**(1/2) + if SI > 1: rhos[21] = X_newb * (SI-1)**2 + else: rhos[21] = 0 + + SI = (S_Ca**3 * po4**2 / Ksp[3])**(1/5) + if SI > 1: rhos[22] = X_ACP * (SI-1)**5 + else: rhos[22] = 0 + + SI = (S_Mg * co3 / Ksp[4])**(1/2) + if SI > 1: rhos[23] = X_MgCO3 * (SI-1)**2 + else: rhos[23] = 0 + rhos[24] = X_AlOH * po4 * Monod(X_AlOH, K_AlOH) rhos[25] = X_FeOH * po4 * Monod(X_FeOH, K_FeOH) rhos[19:26] *= k_mmp From e12c69d2392a46fe450512cd3ee1651190c8648c Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Thu, 27 Jun 2024 09:31:52 -0700 Subject: [PATCH 378/424] checkpoint --- qsdsan/processes/_asm2d.py | 2 +- qsdsan/sanunits/_suspended_growth_bioreactor.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/qsdsan/processes/_asm2d.py b/qsdsan/processes/_asm2d.py index d07ea6cd..3e2544cd 100644 --- a/qsdsan/processes/_asm2d.py +++ b/qsdsan/processes/_asm2d.py @@ -853,7 +853,7 @@ def __new__(cls, components=None, path=None, electron_acceptor_dependent_decay=T Ka, cmps, ) self.rate_function._params = dict(zip(cls._kinetic_params, kinetic_vals)) - + dct['solve_pH'] = solve_pH return self @property diff --git a/qsdsan/sanunits/_suspended_growth_bioreactor.py b/qsdsan/sanunits/_suspended_growth_bioreactor.py index bbcc9129..959d5e77 100644 --- a/qsdsan/sanunits/_suspended_growth_bioreactor.py +++ b/qsdsan/sanunits/_suspended_growth_bioreactor.py @@ -351,7 +351,6 @@ def _compile_ODE(self): _dstate = self._dstate _update_dstate = self._update_dstate V = self._V_max - kLa = self.kLa gstrip = self.gas_stripping if gstrip: gas_idx = self.components.indices(self.gas_IDs) @@ -366,6 +365,7 @@ def _compile_ODE(self): i = self.components.index(self._DO_ID) fixed_DO = self._aeration def dy_dt(t, QC_ins, QC, dQC_ins): + # QC[QC < 2.2e-16] = 0. QC[i] = fixed_DO dydt_cstr(QC_ins, QC, V, _dstate) if hasexo: QC = np.append(QC, f_exovars(t)) @@ -375,6 +375,7 @@ def dy_dt(t, QC_ins, QC, dQC_ins): _update_dstate() else: def dy_dt(t, QC_ins, QC, dQC_ins): + # QC[QC < 2.2e-16] = 0. dydt_cstr(QC_ins, QC, V, _dstate) if hasexo: QC = np.append(QC, f_exovars(t)) _dstate[:-1] += r(QC) @@ -1199,6 +1200,7 @@ def Rs(Cs): def dy_dt(t, QC_ins, QC, dQC_ins): y = QC.reshape((N, ncol)) Cs = y[:,:-1] + Cs[Cs < 2.2e-16] = 0. Cs[aerated_zones, DO_idx] = aerated_DO _Qs[:] = Qs = np.dot(QC_ins[:,-1], f_in).cumsum() + Q_internal M_ins = f_in.T @ np.diag(QC_ins[:,-1]) @ QC_ins[:,:-1] # N * n_cmps @@ -1223,6 +1225,7 @@ def dy_dt(t, QC_ins, QC, dQC_ins): def dy_dt(t, QC_ins, QC, dQC_ins): y = QC.reshape((N, ncol)) Cs = y[:,:-1] + Cs[Cs < 2.2e-16] = 0. do = Cs[:, DO_idx] aer = kLa*(DOsat-do) _Qs[:] = Qs = np.dot(QC_ins[:,-1], f_in).cumsum() + Q_internal From 88d4069106fecefe8b3a9ff9cf26d3cf4cc41d1f Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Thu, 27 Jun 2024 14:33:47 -0700 Subject: [PATCH 379/424] checkpoint --- qsdsan/processes/_asm2d.py | 2 +- qsdsan/sanunits/_junction.py | 48 +++++++++++++++++++++++++++++++++--- 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/qsdsan/processes/_asm2d.py b/qsdsan/processes/_asm2d.py index 3e2544cd..8b103c5b 100644 --- a/qsdsan/processes/_asm2d.py +++ b/qsdsan/processes/_asm2d.py @@ -762,7 +762,6 @@ def __new__(cls, components=None, path=None, electron_acceptor_dependent_decay=T pKsp=(6.45, 13.16, 5.8, 23, 7, 21, 26), K_dis=(1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0), K_AlOH=0.001, K_FeOH=0.001, - # kLa_min=(3.0, 3.0), K_Henry=(6.5e-4, 3.5e-2), pKa=(14, 9.25, 6.37, 10.32, 2.12, 7.21, 12.32, 4.76), **kwargs): @@ -832,6 +831,7 @@ def __new__(cls, components=None, path=None, electron_acceptor_dependent_decay=T f_XI_H, f_XI_PAO, f_XI_AUT, cmps.X_PP.i_K, cmps.X_PP.i_Mg) dct['_parameters'] = dict(zip(cls._stoichio_params, stoichio_vals)) + dct['_edecay'] = bool(electron_acceptor_dependent_decay) rhos_masm2d = lambda state_arr, params: _rhos_masm2d(state_arr, params, electron_acceptor_dependent_decay) self.set_rate_function(rhos_masm2d) Ka = np.array([10**(-p) for p in pKa]) diff --git a/qsdsan/sanunits/_junction.py b/qsdsan/sanunits/_junction.py index def25fd2..8151d17d 100644 --- a/qsdsan/sanunits/_junction.py +++ b/qsdsan/sanunits/_junction.py @@ -473,6 +473,46 @@ def alpha_IC(self): @property def alpha_vfa(self): return 1.0/self.cod_vfa*(-1.0/(1.0 + 10**(self.pKa[4:]-self.pH))) + + def check_component_properties(self, cmps_asm, cmps_adm): + get = getattr + setv = setattr + for name in ('X_PHA', 'X_PP', 'X_PAO'): + casm = get(cmps_asm, name) + cadm = get(cmps_adm, name) + for attr in ('measured_as', 'i_COD', 'i_C', 'i_N', 'i_P'): + vasm = get(casm, attr) + if get(cadm, attr) != vasm: + setv(cadm, attr, vasm) + warn(f"ADM component {name}'s {attr} is changed to match " + "the corresponding ASM component") + + for name in ('S_I', 'X_I'): + casm = get(cmps_asm, name) + cadm = get(cmps_adm, name) + for attr in ('measured_as', 'i_C', 'i_N', 'i_P'): + vadm = get(cadm, attr) + if get(casm, attr) != vadm: + setv(casm, attr, vadm) + warn(f"ASM component {name}'s {attr} is changed to match " + "the corresponding ADM component") + + for attr in ('i_N', 'i_P'): + vadm = get(cmps_adm.S_ac, attr) + if get(cmps_asm.S_A, attr) != vadm: + cmps_asm.S_A.i_N = vadm + warn(f"ASM component S_A's {attr} is changed to match " + "the ADM component S_ac.") + + if cmps_asm.S_ALK.measured_as != cmps_adm.S_IC.measured_as: + raise RuntimeError('S_ALK in ASM and S_IC in ADM must both be measured as "C".') + if cmps_asm.S_NH4.measured_as != cmps_adm.S_IN.measured_as: + raise RuntimeError('S_NH4 in ASM and S_IN in ADM must both be measured as "N".') + if cmps_asm.S_PO4.measured_as != cmps_adm.S_IP.measured_as: + raise RuntimeError('S_PO4 in ASM and S_IP in ADM must both be measured as "P".') + cmps_asm.refresh_constants() + cmps_adm.refresh_constants() + #%% ADMtoASM class ADMtoASM(ADMjunction): @@ -3291,7 +3331,7 @@ def _compile_reactions(self): 'X_PAO', 'X_PP', 'X_PHA')) decay_idx = [i for i in adm.IDs if i.startswith(('decay', 'lysis'))] decay_stoichio = np.asarray(adm.stoichiometry.loc[decay_idx]) - f_corr = self.balance_cod_tkn + # f_corr = self.balance_cod_tkn # To convert components from ADM1p to ASM2d (A1) def adm1p2masm2d(adm_vals): @@ -3344,7 +3384,7 @@ def adm1p2masm2d(adm_vals): S_Ca, X_CaCO3, X_struv, X_newb, X_ACP, X_MgCO3, # directly mapped X_AlOH, X_AlPO4, X_FeOH, X_FePO4, S_Na, S_Cl, H2O])) - asm_vals = f_corr(adm_vals, asm_vals) + # asm_vals = f_corr(adm_vals, asm_vals) return asm_vals self._reactions = adm1p2masm2d @@ -3500,7 +3540,7 @@ def _compile_reactions(self): P_aa, P_su, P_pr, P_li, P_ch = cmps_adm.i_P[_adm_ids] S_O2_idx, S_NO3_idx = cmps_asm.indices(['S_O2', 'S_NO3']) - f_corr = self.balance_cod_tkn_tp + # f_corr = self.balance_cod_tkn_tp asm = self.asm2d_model adm = self.adm1_model @@ -3594,7 +3634,7 @@ def masm2d2adm1p(asm_vals): X_AlOH, X_AlPO4, X_FeOH, X_FePO4, S_Na, S_Cl, H2O]) # adm_vals = f_corr(asm_vals, adm_vals) - adm_vals = f_corr(_asm_vals, adm_vals) + # adm_vals = f_corr(_asm_vals, adm_vals) return adm_vals self._reactions = masm2d2adm1p From 4801ca1c1f5d85a5b28dc47c94e8f037417c47d5 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Wed, 10 Jul 2024 10:33:46 -0700 Subject: [PATCH 380/424] debug electron acceptor COD --- qsdsan/data/process_data/_masm2d.tsv | 4 +--- qsdsan/processes/_asm2d.py | 16 +++++++++++++--- qsdsan/utils/cod.py | 12 +++++++----- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/qsdsan/data/process_data/_masm2d.tsv b/qsdsan/data/process_data/_masm2d.tsv index 705d2560..6d094dfc 100644 --- a/qsdsan/data/process_data/_masm2d.tsv +++ b/qsdsan/data/process_data/_masm2d.tsv @@ -4,8 +4,6 @@ anox_hydrolysis 1-f_SI f_SI ? ? ? -1 anae_hydrolysis 1-f_SI f_SI ? ? ? -1 hetero_growth_S_F 1-1/Y_H (-1)/Y_H ? ? ? 1 hetero_growth_S_A 1-1/Y_H (-1)/Y_H ? ? ? 1 -denitri_S_F (-1)/Y_H ? (1-Y_H)/(20/7*Y_H) (Y_H-1)/(20/7*Y_H) ? ? 1 -denitri_S_A (-1)/Y_H ? (1-Y_H)/(20/7*Y_H) (Y_H-1)/(20/7*Y_H) ? ? 1 ferment -1 1 ? ? ? hetero_lysis ? ? ? f_XI_H 1-f_XI_H -1 storage_PHA -1 ? Y_PO4 ? Y_PO4*K_XPP Y_PO4*Mg_XPP (-Y_PO4) 1 @@ -14,5 +12,5 @@ PAO_aero_growth_PHA ? ? ? ? 1 (-1)/Y_PAO PAO_lysis ? ? ? f_XI_PAO 1-f_XI_PAO -1 PP_lysis ? 1 ? K_XPP Mg_XPP -1 PHA_lysis 1 ? ? -1 -auto_aero_growth (Y_A-32/7)/Y_A ? 1/Y_A ? ? 1 +auto_aero_growth ? ? 1/Y_A ? ? 1 auto_lysis ? ? ? f_XI_AUT 1-f_XI_AUT -1 diff --git a/qsdsan/processes/_asm2d.py b/qsdsan/processes/_asm2d.py index 8b103c5b..f829e234 100644 --- a/qsdsan/processes/_asm2d.py +++ b/qsdsan/processes/_asm2d.py @@ -772,23 +772,33 @@ def __new__(cls, components=None, path=None, electron_acceptor_dependent_decay=T self = Processes.load_from_file(path, components=cmps, - conserved_for=('COD', 'C', 'N', 'P',), + conserved_for=('COD', 'C', 'N', 'P'), parameters=cls._stoichio_params, compile=False) if path == _mpath: + _p6 = Process('denitri_S_F', + '[1/Y_H]S_F + [?]S_NH4 + [?]S_IC + [?]S_PO4 + [?]S_NO3 -> [?]S_N2 + X_H', + components=cmps, + ref_component='X_H', + conserved_for=('C', 'N', 'P', 'NOD', 'COD')) + _p7 = Process('denitri_S_A', + '[1/Y_H]S_A + [?]S_NH4 + [?]S_IC + [?]S_PO4 + [?]S_NO3 -> [?]S_N2 + X_H', + components=cmps, + ref_component='X_H', + conserved_for=('C', 'N', 'P', 'NOD', 'COD')) _p12 = Process('anox_storage_PP', 'S_PO4 + [K_XPP]S_K + [Mg_XPP]S_Mg +[Y_PHA]X_PHA + [?]S_NO3 -> X_PP + [?]S_N2 + [?]S_NH4 + [?]S_IC', components=cmps, ref_component='X_PP', conserved_for=('C', 'N', 'NOD', 'COD')) - _p14 = Process('PAO_anox_growth', '[1/Y_PAO]X_PHA + [?]S_NO3 + [?]S_PO4 -> X_PAO + [?]S_N2 + [?]S_NH4 + [?]S_IC', components=cmps, ref_component='X_PAO', conserved_for=('C', 'N', 'P', 'NOD', 'COD')) - + self.insert(5, _p6) + self.insert(6, _p7) self.insert(11, _p12) self.insert(13, _p14) diff --git a/qsdsan/utils/cod.py b/qsdsan/utils/cod.py index 6203eb6a..60abca24 100644 --- a/qsdsan/utils/cod.py +++ b/qsdsan/utils/cod.py @@ -158,9 +158,11 @@ def electron_acceptor_cod(atoms, charge=0): r''' .. math:: - NO_2^- + 3e^- + 4H^+ -> \frac{1}{2}N_2 + 2H_2O + NO_2^- + 6e^- + 8H^+ -> NH_4^+ + 2H_2O - NO_3^- + 5e^- + 6H^+ -> \frac{1}{2}N_2 + 3H_2O + NO_3^- + 8e^- + 10H^+ -> NH_4^+ + 3H_2O + + N_2 + 6e^- + 8H^+ -> 2NH_4^+ O_2 + 4e^- + 4H^+ -> 2H_2O @@ -168,11 +170,11 @@ def electron_acceptor_cod(atoms, charge=0): if atoms == {'O':2}: return -1 elif atoms == {'N':2}: - return 0 + return -6/4 elif atoms == {'N':1, 'O':2} and charge == -1: - return -3/4 + return -6/4 elif atoms == {'N':1, 'O':3} and charge == -1: - return -5/4 + return -8/4 From 3551072d2fa38565e08833d70c22ebf621c40072 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Wed, 10 Jul 2024 12:34:44 -0700 Subject: [PATCH 381/424] debug ASM2d stoichiometry --- qsdsan/_component.py | 2 ++ qsdsan/data/process_data/_asm2d.tsv | 8 +++-- qsdsan/data/process_data/_masm2d.tsv | 6 +++- qsdsan/processes/_asm2d.py | 53 +++------------------------- 4 files changed, 17 insertions(+), 52 deletions(-) diff --git a/qsdsan/_component.py b/qsdsan/_component.py index 98f43b0a..345c413e 100644 --- a/qsdsan/_component.py +++ b/qsdsan/_component.py @@ -561,6 +561,8 @@ def i_NOD(self, i): if i == None: if self.degradability in ('Readily', 'Slowly') or self.formula in ('H3N', 'NH4', 'NH3', 'NH4+', 'H4N+'): i = self.i_N * molecular_weight({'O':4}) / molecular_weight({'N':1}) + elif self.formula == 'N2': + i = self.i_N * molecular_weight({'O':5}) / molecular_weight({'N':2}) elif self.formula in ('NO2-', 'HNO2'): i = self.i_N * molecular_weight({'O':1}) / molecular_weight({'N':1}) else: diff --git a/qsdsan/data/process_data/_asm2d.tsv b/qsdsan/data/process_data/_asm2d.tsv index 4a8fdd8b..bc909809 100644 --- a/qsdsan/data/process_data/_asm2d.tsv +++ b/qsdsan/data/process_data/_asm2d.tsv @@ -4,17 +4,19 @@ anox_hydrolysis 1-f_SI f_SI ? ? ? -1 K_h*eta_NO3*K_O2/(K_O2+S_O2)*S_ anae_hydrolysis 1-f_SI f_SI ? ? ? -1 K_h*eta_fe*K_O2/(K_O2+S_O2)*K_NO3/(K_NO3+S_NO3)*(X_S/X_H)/(K_X+X_S/X_H)*X_H hetero_growth_S_F 1-1/Y_H (-1)/Y_H ? ? ? 1 mu_H*S_O2/(K_O2_H+S_O2)*S_F/(K_F+S_F)*S_F/(S_F+S_A)*S_NH4/(K_NH4_H+S_NH4)*S_PO4/(K_P_H+S_PO4)*S_ALK/(K_ALK_H+S_ALK)*X_H hetero_growth_S_A 1-1/Y_H (-1)/Y_H ? ? ? 1 mu_H*S_O2/(K_O2_H+S_O2)*S_A/(K_A_H+S_A)*S_A/(S_F+S_A)*S_NH4/(K_NH4_H+S_NH4)*S_PO4/(K_P_H+S_PO4)*S_ALK/(K_ALK_H+S_ALK)*X_H -denitri_S_F (-1)/Y_H ? (1-Y_H)/(20/7*Y_H) (Y_H-1)/(20/7*Y_H) ? ? 1 mu_H*eta_NO3_H*K_O2_H/(K_O2_H+S_O2)*S_NO3/(K_NO3_H+S_NO3)*S_F/(K_F+S_F)*S_F/(S_F+S_A)*S_NH4/(K_NH4_H+S_NH4)*S_PO4/(K_P_H+S_PO4)*S_ALK/(K_ALK_H+S_ALK)*X_H -denitri_S_A (-1)/Y_H ? (1-Y_H)/(20/7*Y_H) (Y_H-1)/(20/7*Y_H) ? ? 1 mu_H*eta_NO3_H*K_O2_H/(K_O2_H+S_O2)*S_NO3/(K_NO3_H+S_NO3)*S_A/(K_A_H+S_A)*S_A/(S_F+S_A)*S_NH4/(K_NH4_H+S_NH4)*S_PO4/(K_P_H+S_PO4)*S_ALK/(K_ALK_H+S_ALK)*X_H +denitri_S_F (-1)/Y_H ? (1-Y_H)/(COD_deN*Y_H) (Y_H-1)/(COD_deN*Y_H) ? ? 1 mu_H*eta_NO3_H*K_O2_H/(K_O2_H+S_O2)*S_NO3/(K_NO3_H+S_NO3)*S_F/(K_F+S_F)*S_F/(S_F+S_A)*S_NH4/(K_NH4_H+S_NH4)*S_PO4/(K_P_H+S_PO4)*S_ALK/(K_ALK_H+S_ALK)*X_H +denitri_S_A (-1)/Y_H ? (1-Y_H)/(COD_deN*Y_H) (Y_H-1)/(COD_deN*Y_H) ? ? 1 mu_H*eta_NO3_H*K_O2_H/(K_O2_H+S_O2)*S_NO3/(K_NO3_H+S_NO3)*S_A/(K_A_H+S_A)*S_A/(S_F+S_A)*S_NH4/(K_NH4_H+S_NH4)*S_PO4/(K_P_H+S_PO4)*S_ALK/(K_ALK_H+S_ALK)*X_H ferment -1 1 ? ? ? q_fe*K_O2_H/(K_O2_H+S_O2)*K_NO3_H/(K_NO3_H+S_NO3)*S_F/(K_fe+S_F)*S_ALK/(K_ALK_H+S_ALK)*X_H hetero_lysis ? ? ? f_XI_H 1-f_XI_H -1 b_H*X_H PAO_storage_PHA -1 ? Y_PO4 ? (-Y_PO4) 1 q_PHA*S_A/(K_A_PAO+S_A)*S_ALK/(K_ALK_PAO+S_ALK)*(X_PP/X_PAO)/(K_PP+X_PP/X_PAO)*X_PAO aero_storage_PP (-Y_PHA) ? -1 ? 1 (-Y_PHA) q_PP*S_O2/(K_O2_PAO+S_O2)*S_PO4/(K_PS+S_PO4)*S_ALK/(K_ALK_PAO+S_ALK)*(X_PHA/X_PAO)/(K_PHA+X_PHA/X_PAO)*(K_MAX-X_PP/X_PAO)/(K_IPP+K_MAX-X_PP/X_PAO)*X_PAO +anox_storage_PP ? Y_PHA/COD_deN (-Y_PHA)/COD_deN -1 ? 1 (-Y_PHA) q_PP*S_O2/(K_O2_PAO+S_O2)*S_PO4/(K_PS+S_PO4)*S_ALK/(K_ALK_PAO+S_ALK)*(X_PHA/X_PAO)/(K_PHA+X_PHA/X_PAO)*(K_MAX-X_PP/X_PAO)/(K_IPP+K_MAX-X_PP/X_PAO)*X_PAO*eta_NO3_PAO*K_O2_PAO/S_O2*S_NO3/(K_NO3_PAO+S_NO3) PAO_aero_growth_PHA ? ? ? ? 1 (-1)/Y_PAO mu_PAO*S_O2/(K_O2_PAO+S_O2)*S_NH4/(K_NH4_PAO+S_NH4)*S_PO4/(K_P_PAO+S_PO4)*S_ALK/(K_ALK_PAO+S_ALK)*(X_PHA/X_PAO)/(K_PHA+X_PHA/X_PAO)*X_PAO +PAO_anox_growth ? (1-Y_PAO)/(COD_deN*Y_PAO) (Y_PAO-1)/(COD_deN*Y_PAO) ? ? 1 (-1)/Y_PAO mu_PAO*S_O2/(K_O2_PAO+S_O2)*S_NH4/(K_NH4_PAO+S_NH4)*S_PO4/(K_P_PAO+S_PO4)*S_ALK/(K_ALK_PAO+S_ALK)*(X_PHA/X_PAO)/(K_PHA+X_PHA/X_PAO)*X_PAO* eta_NO3_PAO*K_O2_PAO/S_O2*S_NO3/(K_NO3_PAO + S_NO3) PAO_lysis ? ? ? f_XI_PAO 1-f_XI_PAO -1 b_PAO*X_PAO*S_ALK/(K_ALK_PAO+S_ALK) PP_lysis ? 1 ? -1 b_PP*X_PP*S_ALK/(K_ALK_PAO+S_ALK) PHA_lysis 1 ? ? -1 b_PHA*X_PHA*S_ALK/(K_ALK_PAO+S_ALK) -auto_aero_growth (Y_A-32/7)/Y_A ? 1/Y_A ? ? 1 mu_AUT*S_O2/(K_O2_AUT+S_O2)*S_NH4/(K_NH4_AUT+S_NH4)*S_PO4/(K_P_AUT+S_PO4)*S_ALK/(K_ALK_AUT+S_ALK)*X_AUT +auto_aero_growth ? ? 1/Y_A ? ? 1 mu_AUT*S_O2/(K_O2_AUT+S_O2)*S_NH4/(K_NH4_AUT+S_NH4)*S_PO4/(K_P_AUT+S_PO4)*S_ALK/(K_ALK_AUT+S_ALK)*X_AUT auto_lysis ? ? ? f_XI_AUT 1-f_XI_AUT -1 b_AUT*X_AUT precipitation -1 ? -3.45 ? k_PRE*S_PO4*X_MeOH redissolution 1 ? 3.45 ? k_RED*X_MeP*S_ALK/(K_ALK_PRE+S_ALK) diff --git a/qsdsan/data/process_data/_masm2d.tsv b/qsdsan/data/process_data/_masm2d.tsv index 6d094dfc..2fa8baee 100644 --- a/qsdsan/data/process_data/_masm2d.tsv +++ b/qsdsan/data/process_data/_masm2d.tsv @@ -4,11 +4,15 @@ anox_hydrolysis 1-f_SI f_SI ? ? ? -1 anae_hydrolysis 1-f_SI f_SI ? ? ? -1 hetero_growth_S_F 1-1/Y_H (-1)/Y_H ? ? ? 1 hetero_growth_S_A 1-1/Y_H (-1)/Y_H ? ? ? 1 +denitri_S_F (-1)/Y_H ? (1-Y_H)/(COD_deN*Y_H) (Y_H-1)/(COD_deN*Y_H) ? ? 1 +denitri_S_A (-1)/Y_H ? (1-Y_H)/(COD_deN*Y_H) (Y_H-1)/(COD_deN*Y_H) ? ? 1 ferment -1 1 ? ? ? hetero_lysis ? ? ? f_XI_H 1-f_XI_H -1 storage_PHA -1 ? Y_PO4 ? Y_PO4*K_XPP Y_PO4*Mg_XPP (-Y_PO4) 1 -aero_storage_PP (-Y_PHA) ? -1 ? (-K_XPP) (-Mg_XPP) 1 (-Y_PHA) +aero_storage_PP (-Y_PHA) -1 ? (-K_XPP) (-Mg_XPP) 1 (-Y_PHA) +anox_storage_PP Y_PHA/COD_deN (-Y_PHA)/COD_deN -1 ? (-K_XPP) (-Mg_XPP) 1 (-Y_PHA) PAO_aero_growth_PHA ? ? ? ? 1 (-1)/Y_PAO +PAO_anox_growth ? (1-Y_PAO)/(COD_deN*Y_PAO) (Y_PAO-1)/(COD_deN*Y_PAO) ? ? 1 (-1)/Y_PAO PAO_lysis ? ? ? f_XI_PAO 1-f_XI_PAO -1 PP_lysis ? 1 ? K_XPP Mg_XPP -1 PHA_lysis 1 ? ? -1 diff --git a/qsdsan/processes/_asm2d.py b/qsdsan/processes/_asm2d.py index f829e234..96a102af 100644 --- a/qsdsan/processes/_asm2d.py +++ b/qsdsan/processes/_asm2d.py @@ -369,7 +369,7 @@ class ASM2d(CompiledProcesses): 'K_O2_PAO', 'K_NO3_PAO', 'K_A_PAO', 'K_NH4_PAO', 'K_PS','K_P_PAO', 'K_ALK_PAO', 'K_PP', 'K_MAX', 'K_IPP', 'K_PHA', 'mu_AUT', 'b_AUT', 'K_O2_AUT', 'K_NH4_AUT', 'K_ALK_AUT', 'K_P_AUT', - 'k_PRE', 'k_RED', 'K_ALK_PRE') + 'k_PRE', 'k_RED', 'K_ALK_PRE', 'COD_deN') def __new__(cls, components=None, @@ -413,24 +413,6 @@ def __new__(cls, components=None, parameters=cls._params, compile=False) - if path == _path: - _p12 = Process('anox_storage_PP', - 'S_PO4 + [Y_PHA]X_PHA + [?]S_NO3 -> X_PP + [?]S_N2 + [?]S_NH4 + [?]S_ALK', - components=cmps, - ref_component='X_PP', - rate_equation='q_PP * S_O2/(K_O2_PAO+S_O2) * S_PO4/(K_PS+S_PO4) * S_ALK/(K_ALK_PAO+S_ALK) * (X_PHA/X_PAO)/(K_PHA+X_PHA/X_PAO) * (K_MAX-X_PP/X_PAO)/(K_IPP+K_MAX-X_PP/X_PAO) * X_PAO * eta_NO3_PAO * K_O2_PAO/S_O2 * S_NO3/(K_NO3_PAO+S_NO3)', - parameters=('Y_PHA', 'q_PP', 'K_O2_PAO', 'K_PS', 'K_ALK_PAO', 'K_PHA', 'eta_NO3_PAO', 'K_IPP', 'K_NO3_PAO'), - conserved_for=('COD', 'N', 'P', 'NOD', 'charge')) - - _p14 = Process('PAO_anox_growth', - '[1/Y_PAO]X_PHA + [?]S_NO3 + [?]S_PO4 -> X_PAO + [?]S_N2 + [?]S_NH4 + [?]S_ALK', - components=cmps, - ref_component='X_PAO', - rate_equation='mu_PAO * S_O2/(K_O2_PAO + S_O2) * S_NH4/(K_NH4_PAO + S_NH4) * S_PO4/(K_P_PAO + S_PO4) * S_ALK/(K_ALK_PAO + S_ALK) * (X_PHA/X_PAO)/(K_PHA + X_PHA/X_PAO) * X_PAO * eta_NO3_PAO * K_O2_PAO/S_O2 * S_NO3/(K_NO3_PAO + S_NO3)', - parameters=('Y_PAO', 'mu_PAO', 'K_O2_PAO', 'K_NH4_PAO', 'K_P_PAO', 'K_ALK_PAO', 'K_PHA', 'eta_NO3_PAO', 'K_NO3_PAO'), - conserved_for=('COD', 'N', 'P', 'NOD', 'charge')) - self.extend([_p12, _p14]) - self.compile(to_class=cls) self.set_parameters(f_SI=f_SI, Y_H=Y_H, f_XI_H=f_XI_H, Y_PAO=Y_PAO, Y_PO4=Y_PO4, Y_PHA=Y_PHA, f_XI_PAO=f_XI_PAO, Y_A=Y_A, f_XI_AUT=f_XI_AUT, @@ -446,7 +428,8 @@ def __new__(cls, components=None, K_MAX=K_MAX, K_IPP=K_IPP, K_PHA=K_PHA, mu_AUT=mu_AUT, b_AUT=b_AUT, K_O2_AUT=K_O2_AUT, K_NH4_AUT=K_NH4_AUT, K_ALK_AUT=K_ALK_AUT*12, K_P_AUT=K_P_AUT, - k_PRE=k_PRE, k_RED=k_RED, K_ALK_PRE=K_ALK_PRE*12, + k_PRE=k_PRE, k_RED=k_RED, K_ALK_PRE=K_ALK_PRE*12, + COD_deN=cmps.S_N2.i_COD-cmps.S_NO3.i_COD, **kwargs) return self @@ -716,7 +699,7 @@ class mASM2d(CompiledProcesses): ''' _stoichio_params = ('f_SI', 'Y_H', 'Y_PAO', 'Y_PO4', 'Y_PHA', 'Y_A', - 'f_XI_H', 'f_XI_PAO', 'f_XI_AUT','K_XPP', 'Mg_XPP') + 'f_XI_H', 'f_XI_PAO', 'f_XI_AUT', 'COD_deN', 'K_XPP', 'Mg_XPP') _kinetic_params = ('k_h', 'mu_H', 'mu_PAO', 'mu_AUT', 'q_fe', 'q_PHA', 'q_PP', 'b_H', 'b_PAO', 'b_PP', 'b_PHA', 'b_AUT', @@ -776,32 +759,6 @@ def __new__(cls, components=None, path=None, electron_acceptor_dependent_decay=T parameters=cls._stoichio_params, compile=False) - if path == _mpath: - _p6 = Process('denitri_S_F', - '[1/Y_H]S_F + [?]S_NH4 + [?]S_IC + [?]S_PO4 + [?]S_NO3 -> [?]S_N2 + X_H', - components=cmps, - ref_component='X_H', - conserved_for=('C', 'N', 'P', 'NOD', 'COD')) - _p7 = Process('denitri_S_A', - '[1/Y_H]S_A + [?]S_NH4 + [?]S_IC + [?]S_PO4 + [?]S_NO3 -> [?]S_N2 + X_H', - components=cmps, - ref_component='X_H', - conserved_for=('C', 'N', 'P', 'NOD', 'COD')) - _p12 = Process('anox_storage_PP', - 'S_PO4 + [K_XPP]S_K + [Mg_XPP]S_Mg +[Y_PHA]X_PHA + [?]S_NO3 -> X_PP + [?]S_N2 + [?]S_NH4 + [?]S_IC', - components=cmps, - ref_component='X_PP', - conserved_for=('C', 'N', 'NOD', 'COD')) - _p14 = Process('PAO_anox_growth', - '[1/Y_PAO]X_PHA + [?]S_NO3 + [?]S_PO4 -> X_PAO + [?]S_N2 + [?]S_NH4 + [?]S_IC', - components=cmps, - ref_component='X_PAO', - conserved_for=('C', 'N', 'P', 'NOD', 'COD')) - self.insert(5, _p6) - self.insert(6, _p7) - self.insert(11, _p12) - self.insert(13, _p14) - mmp = Processes.load_from_file(_mmp, components=cmps, conserved_for=(), compile=False) mmp_stoichio = {} @@ -838,7 +795,7 @@ def __new__(cls, components=None, path=None, electron_acceptor_dependent_decay=T dct['K_Henry'] = [K*mol_to_mass[cmps.index(i)]*1000 for K, i in zip(cls.K_Henry, cls.gas_IDs)] dct['mmp_stoichio'] = mmp_stoichio stoichio_vals = (f_SI, Y_H, Y_PAO, Y_PO4, Y_PHA, Y_A, - f_XI_H, f_XI_PAO, f_XI_AUT, + f_XI_H, f_XI_PAO, f_XI_AUT, cmps.S_N2.i_COD-cmps.S_NO3.i_COD, cmps.X_PP.i_K, cmps.X_PP.i_Mg) dct['_parameters'] = dict(zip(cls._stoichio_params, stoichio_vals)) dct['_edecay'] = bool(electron_acceptor_dependent_decay) From f44d0ff5708d4f9e8911711f76867ce3f1c582f8 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Fri, 12 Jul 2024 11:02:20 -0700 Subject: [PATCH 382/424] temporary disable mmp --- qsdsan/processes/_asm2d.py | 110 ++++++++++++++++++------------------- 1 file changed, 53 insertions(+), 57 deletions(-) diff --git a/qsdsan/processes/_asm2d.py b/qsdsan/processes/_asm2d.py index 96a102af..4f7b0e5d 100644 --- a/qsdsan/processes/_asm2d.py +++ b/qsdsan/processes/_asm2d.py @@ -555,61 +555,55 @@ def _rhos_masm2d(state_arr, params, acceptor_dependent_decay=True): rhos[14:17] *= (aero[3] +eta_decay[1:4]*(1-aero[3])*anox[3]) rhos[18] *= (aero[5] + eta_decay[4]*(1-aero[5])*anox[5]) - ########## pH ############ - mass2mol = params['mass2mol'] - Ka = params['Ka'] - Kw, Knh, Kc1, Kc2, Kp1, Kp2, Kp3, Kac = Ka - h = solve_pH(state_arr, Ka, mass2mol) - nh4 = state_arr[2] * h/(Knh + h) - co2, hco3, co3 = state_arr[8] * ion_speciation(h, Kc1, Kc2) - h3po4, h2po4, hpo4, po4 = state_arr[4] * ion_speciation(h, Kp1, Kp2, Kp3) - - ########## precipitation-dissolution ############# - k_mmp = params['k_mmp'] - Ksp = params['Ksp'] - # K_dis = params['K_dis'] - K_AlOH = params['K_AlOH'] - K_FeOH = params['K_FeOH'] - # f_dis = Monod(state_arr[19:24], K_dis[:5]) - # if X_CaCO3 > 0: rhos[19] = (S_Ca * co3 - Ksp[0]) * f_dis[0] - # else: rhos[19] = S_Ca * co3 - # if X_struv > 0: rhos[20] = (S_Mg * nh4 * po4 - Ksp[1]) * f_dis[1] - # else: rhos[20] = S_Mg * nh4 * po4 - # if X_newb > 0: rhos[21] = (S_Mg * hpo4 - Ksp[2]) * f_dis[2] - # else: rhos[21] = S_Mg * hpo4 - # if X_ACP > 0: rhos[22] = (S_Ca**3 * po4**2 - Ksp[3]) * f_dis[3] - # else: rhos[22] = S_Ca**3 * po4**2 - # if X_MgCO3 > 0: rhos[23] = (S_Mg * co3 - Ksp[4]) * f_dis[4] - # else: rhos[23] = S_Mg * co3 - SI = (S_Ca * co3 / Ksp[0])**(1/2) - if SI > 1: rhos[19] = X_CaCO3 * (SI-1)**2 - else: rhos[19] = 0 - - SI = (S_Mg * nh4 * po4 / Ksp[1])**(1/3) - if SI > 1: rhos[20] = X_struv * (SI-1)**3 - else: rhos[20] = 0 - - SI = (S_Mg * hpo4 / Ksp[2])**(1/2) - if SI > 1: rhos[21] = X_newb * (SI-1)**2 - else: rhos[21] = 0 - - SI = (S_Ca**3 * po4**2 / Ksp[3])**(1/5) - if SI > 1: rhos[22] = X_ACP * (SI-1)**5 - else: rhos[22] = 0 - - SI = (S_Mg * co3 / Ksp[4])**(1/2) - if SI > 1: rhos[23] = X_MgCO3 * (SI-1)**2 - else: rhos[23] = 0 - - rhos[24] = X_AlOH * po4 * Monod(X_AlOH, K_AlOH) - rhos[25] = X_FeOH * po4 * Monod(X_FeOH, K_FeOH) - rhos[19:26] *= k_mmp - - ########### gas stripping ########### - # kLa_n2, kLa_co2 = params['kLa'] - # KH_n2, KH_co2 = params['K_Henry'] # assume already temperature-corrected - # rhos[26] = kLa_n2*(S_N2 - KH_n2*p_n2_air/mass2mol[1]*1e3) # M/atm * atm / mol/g * 1000 mg/g = mg/L - # rhos[27] = kLa_co2*(co2 - KH_co2*p_co2_air/mass2mol[8]*1e3) # M/atm * atm / mol/g * 1000 mg/g = mg/L + # ########## pH ############ + # mass2mol = params['mass2mol'] + # Ka = params['Ka'] + # Kw, Knh, Kc1, Kc2, Kp1, Kp2, Kp3, Kac = Ka + # h = solve_pH(state_arr, Ka, mass2mol) + # nh4 = state_arr[2] * h/(Knh + h) + # co2, hco3, co3 = state_arr[8] * ion_speciation(h, Kc1, Kc2) + # h3po4, h2po4, hpo4, po4 = state_arr[4] * ion_speciation(h, Kp1, Kp2, Kp3) + + # ########## precipitation-dissolution ############# + # k_mmp = params['k_mmp'] + # Ksp = params['Ksp'] + # # K_dis = params['K_dis'] + # K_AlOH = params['K_AlOH'] + # K_FeOH = params['K_FeOH'] + # # f_dis = Monod(state_arr[19:24], K_dis[:5]) + # # if X_CaCO3 > 0: rhos[19] = (S_Ca * co3 - Ksp[0]) * f_dis[0] + # # else: rhos[19] = S_Ca * co3 + # # if X_struv > 0: rhos[20] = (S_Mg * nh4 * po4 - Ksp[1]) * f_dis[1] + # # else: rhos[20] = S_Mg * nh4 * po4 + # # if X_newb > 0: rhos[21] = (S_Mg * hpo4 - Ksp[2]) * f_dis[2] + # # else: rhos[21] = S_Mg * hpo4 + # # if X_ACP > 0: rhos[22] = (S_Ca**3 * po4**2 - Ksp[3]) * f_dis[3] + # # else: rhos[22] = S_Ca**3 * po4**2 + # # if X_MgCO3 > 0: rhos[23] = (S_Mg * co3 - Ksp[4]) * f_dis[4] + # # else: rhos[23] = S_Mg * co3 + # SI = (S_Ca * co3 / Ksp[0])**(1/2) + # if SI > 1: rhos[19] = X_CaCO3 * (SI-1)**2 + # else: rhos[19] = 0 + + # SI = (S_Mg * nh4 * po4 / Ksp[1])**(1/3) + # if SI > 1: rhos[20] = X_struv * (SI-1)**3 + # else: rhos[20] = 0 + + # SI = (S_Mg * hpo4 / Ksp[2])**(1/2) + # if SI > 1: rhos[21] = X_newb * (SI-1)**2 + # else: rhos[21] = 0 + + # SI = (S_Ca**3 * po4**2 / Ksp[3])**(1/5) + # if SI > 1: rhos[22] = X_ACP * (SI-1)**5 + # else: rhos[22] = 0 + + # SI = (S_Mg * co3 / Ksp[4])**(1/2) + # if SI > 1: rhos[23] = X_MgCO3 * (SI-1)**2 + # else: rhos[23] = 0 + + # rhos[24] = X_AlOH * po4 * Monod(X_AlOH, K_AlOH) + # rhos[25] = X_FeOH * po4 * Monod(X_FeOH, K_FeOH) + # rhos[19:26] *= k_mmp return rhos @@ -741,8 +735,10 @@ def __new__(cls, components=None, path=None, electron_acceptor_dependent_decay=T K_NH4_H=0.05, K_NH4_PAO=0.05, K_NH4_AUT=1.0, K_P_H=0.01, K_P_PAO=0.01, K_P_AUT=0.01, K_P_S=0.2, K_PP=0.01, K_MAX=0.34, K_IPP=0.02, K_PHA=0.01, - k_mmp=(5.0, 300, 0.05, 150, 50, 1.0, 1.0), - pKsp=(6.45, 13.16, 5.8, 23, 7, 21, 26), + # k_mmp=(5.0, 300, 0.05, 150, 50, 1.0, 1.0), + # pKsp=(6.45, 13.16, 5.8, 23, 7, 21, 26), + k_mmp=(0.024, 120, 0.024, 72, 0.024, 0.024, 0.024), # Flores-Alsina 2016 + pKsp=(8.3, 13.6, 18.175, 28.92, 7.46, 18.2, 37.76), # Flores-Alsina 2016 K_dis=(1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0), K_AlOH=0.001, K_FeOH=0.001, pKa=(14, 9.25, 6.37, 10.32, 2.12, 7.21, 12.32, 4.76), From c785da2417d761154ace08763e8fa4b3420cd39e Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Fri, 12 Jul 2024 11:02:38 -0700 Subject: [PATCH 383/424] fix minor bug --- qsdsan/processes/_adm1_p_extension.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qsdsan/processes/_adm1_p_extension.py b/qsdsan/processes/_adm1_p_extension.py index 3500bff2..52d0e3b3 100644 --- a/qsdsan/processes/_adm1_p_extension.py +++ b/qsdsan/processes/_adm1_p_extension.py @@ -919,7 +919,7 @@ def __new__(cls, components=None, path=None, k_mmp=(0.024, 120, 0.024, 72, 0.024, 0.024, 0.024), # Flores-Alsina 2016 pKsp=(8.3, 13.6, 18.175, 28.92, 7.46, 18.2, 37.76), # Flores-Alsina 2016 K_dis=(1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0), - K_AlOH=0.001, K_FeOH=0.001, + K_AlOH=1.0e-6, K_FeOH=1.0e-6, # kg/m3 **kwargs): cmps = _load_components(components) From d54b975c3d4c63ecb6b020e838c188705d4bc17e Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Mon, 15 Jul 2024 09:18:40 -0700 Subject: [PATCH 384/424] fix tests --- qsdsan/processes/_asm2d.py | 7 +++---- tests/test_component.py | 4 ++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/qsdsan/processes/_asm2d.py b/qsdsan/processes/_asm2d.py index 4f7b0e5d..89a43eb4 100644 --- a/qsdsan/processes/_asm2d.py +++ b/qsdsan/processes/_asm2d.py @@ -348,7 +348,7 @@ class ASM2d(CompiledProcesses): >>> cmps = pc.create_asm2d_cmps() >>> asm2d = pc.ASM2d() >>> asm2d.show() - ASM2d([aero_hydrolysis, anox_hydrolysis, anae_hydrolysis, hetero_growth_S_F, hetero_growth_S_A, denitri_S_F, denitri_S_A, ferment, hetero_lysis, PAO_storage_PHA, aero_storage_PP, PAO_aero_growth_PHA, PAO_lysis, PP_lysis, PHA_lysis, auto_aero_growth, auto_lysis, precipitation, redissolution, anox_storage_PP, PAO_anox_growth]) + ASM2d([aero_hydrolysis, anox_hydrolysis, anae_hydrolysis, hetero_growth_S_F, hetero_growth_S_A, denitri_S_F, denitri_S_A, ferment, hetero_lysis, PAO_storage_PHA, aero_storage_PP, anox_storage_PP, PAO_aero_growth_PHA, PAO_anox_growth, PAO_lysis, PP_lysis, PHA_lysis, auto_aero_growth, auto_lysis, precipitation, redissolution]) References ---------- @@ -612,8 +612,7 @@ def _rhos_masm2d(state_arr, params, acceptor_dependent_decay=True): class mASM2d(CompiledProcesses): ''' Modified ASM2d. [1]_, [2]_ Compatible with `ADM1p` for plant-wide simulations. - Includes an algebraic pH solver, precipitation/dissolution of common minerals, - and gas stripping/dissolution. + Includes an algebraic pH solver and precipitation/dissolution of common minerals. Parameters ---------- @@ -674,7 +673,7 @@ class mASM2d(CompiledProcesses): >>> cmps = pc.create_masm2d_cmps() >>> asm = pc.mASM2d() >>> asm.show() - mASM2d([aero_hydrolysis, anox_hydrolysis, anae_hydrolysis, hetero_growth_S_F, hetero_growth_S_A, denitri_S_F, denitri_S_A, ferment, hetero_lysis, storage_PHA, aero_storage_PP, anox_storage_PP, PAO_aero_growth_PHA, PAO_anox_growth, PAO_lysis, PP_lysis, PHA_lysis, auto_aero_growth, auto_lysis, CaCO3_precipitation_dissolution, struvite_precipitation_dissolution, newberyite_precipitation_dissolution, ACP_precipitation_dissolution, MgCO3_precipitation_dissolution, AlPO4_precipitation_dissolution, FePO4_precipitation_dissolution, N2_stripping, IC_stripping]) + mASM2d([aero_hydrolysis, anox_hydrolysis, anae_hydrolysis, hetero_growth_S_F, hetero_growth_S_A, denitri_S_F, denitri_S_A, ferment, hetero_lysis, storage_PHA, aero_storage_PP, anox_storage_PP, PAO_aero_growth_PHA, PAO_anox_growth, PAO_lysis, PP_lysis, PHA_lysis, auto_aero_growth, auto_lysis, CaCO3_precipitation_dissolution, struvite_precipitation_dissolution, newberyite_precipitation_dissolution, ACP_precipitation_dissolution, MgCO3_precipitation_dissolution, AlPO4_precipitation_dissolution, FePO4_precipitation_dissolution]) References diff --git a/tests/test_component.py b/tests/test_component.py index 8c53109d..97550ada 100644 --- a/tests/test_component.py +++ b/tests/test_component.py @@ -68,8 +68,8 @@ def test_component(): cmps3 = Components.load_default() assert cmps3.S_H2.measured_as == 'COD' assert cmps3.S_H2.i_COD == 1 - assert isclose(cmps3.S_NO2.i_COD, - 3*molecular_weight({'O':2})/(4*molecular_weight({'N':1})), rel_tol=1e-3) - assert isclose(cmps3.S_NO3.i_COD, - 5*molecular_weight({'O':2})/(4*molecular_weight({'N':1})), rel_tol=1e-3) + assert isclose(cmps3.S_NO2.i_COD, -3.4268, rel_tol=1e-3) + assert isclose(cmps3.S_NO3.i_COD, -4.569, rel_tol=1e-3) set_thermo(cmps3) # Check if the default groups are up-to-date From 9c9db81a4ae32aeb71342344edbbb9de76350423 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Thu, 18 Jul 2024 09:18:23 -0700 Subject: [PATCH 385/424] minor bug fix --- qsdsan/sanunits/_sludge_treatment.py | 2 +- qsdsan/sanunits/_suspended_growth_bioreactor.py | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py index 2616d704..a349adbd 100644 --- a/qsdsan/sanunits/_sludge_treatment.py +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -68,7 +68,7 @@ class Thickener(SanUnit): ins : class:`WasteStream` Influent to the clarifier. Expected number of influent is 1. outs : class:`WasteStream` - Treated effluent and sludge. + Thickened sludge and effluent. thickener_perc : float The percentage of solids in the underflow of the thickener.[1] TSS_removal_perc : float diff --git a/qsdsan/sanunits/_suspended_growth_bioreactor.py b/qsdsan/sanunits/_suspended_growth_bioreactor.py index 959d5e77..0f98bbc6 100644 --- a/qsdsan/sanunits/_suspended_growth_bioreactor.py +++ b/qsdsan/sanunits/_suspended_growth_bioreactor.py @@ -1135,6 +1135,7 @@ def _init_state(self): def _update_state(self): out, = self.outs ncol = self._ncol + self._state[self._state < 2.2e-16] = 0. self._state[self._Qs_idx] = self._Qs if out.state is None: out.state = np.zeros(ncol) out.state[:-1] = self._state[-ncol:-1] @@ -1248,5 +1249,11 @@ def dy_dt(t, QC_ins, QC, dQC_ins): self._ODE = dy_dt + def get_retained_mass(self, biomass_IDs): + cmps = self.components + idx = cmps.indices(biomass_IDs) + mass = self.state.iloc[:,idx] @ cmps.i_mass[idx] + return mass @ self.V_tanks + def _design(self): pass \ No newline at end of file From c71c506c4467fc19132b9a03d71fef3c1a8d11a1 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Thu, 18 Jul 2024 13:03:46 -0700 Subject: [PATCH 386/424] debug mASM2d --- qsdsan/data/_masm2d.xlsx | Bin 43955 -> 47080 bytes qsdsan/processes/_asm2d.py | 3 ++- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/qsdsan/data/_masm2d.xlsx b/qsdsan/data/_masm2d.xlsx index 528638343c8f28dd9954cfb8aeca9b26186452ae..445d1725a58be35779c53ea3734a7a8d499457b2 100644 GIT binary patch delta 37707 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�Zcr;y> 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 Bl1u;q diff --git a/qsdsan/processes/_asm2d.py b/qsdsan/processes/_asm2d.py index 89a43eb4..28f110bc 100644 --- a/qsdsan/processes/_asm2d.py +++ b/qsdsan/processes/_asm2d.py @@ -522,7 +522,8 @@ def _rhos_masm2d(state_arr, params, acceptor_dependent_decay=True): nutrients = Monod(S_NH4, Ks_nh4) * Monod(S_PO4, Ks_po4) rhos[:19] = ks - rhos[:9] *= X_H * nutrients[0] + rhos[:9] *= X_H + rhos[3:7] *= nutrients[0] rhos[9:15] *= X_PAO rhos[12:14] *= nutrients[1] rhos[15] *= X_PP From 0014758fda536380212aef7fb269aed44c6b8315 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Tue, 23 Jul 2024 14:57:42 -0700 Subject: [PATCH 387/424] more fail-proof modeling of aeration in CSTR --- qsdsan/_process.py | 2 +- .../sanunits/_suspended_growth_bioreactor.py | 93 ++++++++++--------- 2 files changed, 52 insertions(+), 43 deletions(-) diff --git a/qsdsan/_process.py b/qsdsan/_process.py index 7196b584..5a5e768b 100644 --- a/qsdsan/_process.py +++ b/qsdsan/_process.py @@ -1229,7 +1229,7 @@ def f(): stoichio_arr = self.stoichiometry.to_numpy(dtype=float) except TypeError: isa = isinstance - undefined = [k for k, v in dct_vals if not isa(v, (float, int))] + undefined = [k for k, v in dct_vals.items() if not isa(v, (float, int))] raise TypeError(f'Undefined static parameters: {undefined}') self.__dict__['_stoichio_lambdified'] = lambda : stoichio_arr diff --git a/qsdsan/sanunits/_suspended_growth_bioreactor.py b/qsdsan/sanunits/_suspended_growth_bioreactor.py index 0f98bbc6..82b8bb96 100644 --- a/qsdsan/sanunits/_suspended_growth_bioreactor.py +++ b/qsdsan/sanunits/_suspended_growth_bioreactor.py @@ -27,17 +27,15 @@ 'PFR', ) -def _add_aeration_to_growth_model(aer, model): - if isinstance(aer, Process): - processes = Processes(model.tuple) - processes.append(aer) - processes.compile() - else: - processes = model - processes.compile() - return processes - - +# def _add_aeration_to_growth_model(aer, model): +# if isinstance(aer, Process): +# processes = Processes(model.tuple) +# processes.append(aer) +# processes.compile() +# else: +# processes = model +# processes.compile() +# return processes #%% class CSTR(SanUnit): @@ -248,10 +246,10 @@ def gas_stripping(self, strip): else: mdl = self._model self.gas_IDs = mdl.gas_IDs - self.stripping_kLa_min = mdl.kLa_min - self.D_gas = mdl.D_gas - self.K_Henry = mdl.K_Henry - self.p_gas_atm = mdl.p_gas_atm + self.stripping_kLa_min = np.array(mdl.kLa_min) + self.D_gas = np.array(mdl.D_gas) + self.K_Henry = np.array(mdl.K_Henry) + self.p_gas_atm = np.array(mdl.p_gas_atm) @property def split(self): @@ -295,6 +293,7 @@ def _init_state(self): def _update_state(self): arr = self._state + arr[arr < 2.2e-16] = 0. arr[-1] = sum(ws.state[-1] for ws in self.ins) if self.split is None: self._outs[0].state = arr else: @@ -336,8 +335,8 @@ def ODE(self): def _compile_ODE(self): isa = isinstance - C = list(symbols(self.components.IDs)) - m = len(C) + cmps = self.components + m = cmps.size aer = self._aeration if self._model is None: warn(f'{self.ID} was initialized without a suspended growth model, ' @@ -345,8 +344,8 @@ def _compile_ODE(self): r = lambda state_arr: np.zeros(m) else: - processes = _add_aeration_to_growth_model(aer, self._model) - r = processes.production_rates_eval + # processes = _add_aeration_to_growth_model(aer, self._model) + r = self._model.production_rates_eval _dstate = self._dstate _update_dstate = self._update_dstate @@ -373,6 +372,16 @@ def dy_dt(t, QC_ins, QC, dQC_ins): if gstrip: _dstate[gas_idx] -= kLa_stripping * (QC[gas_idx] - S_gas_air) _dstate[i] = 0 _update_dstate() + elif isa(aer, Process): + aer_stoi = aer._stoichiometry + aer_frho = aer.rate_function + def dy_dt(t, QC_ins, QC, dQC_ins): + # QC[QC < 2.2e-16] = 0. + dydt_cstr(QC_ins, QC, V, _dstate) + if hasexo: QC = np.append(QC, f_exovars(t)) + _dstate[:-1] += r(QC) + aer_stoi * aer_frho(QC) + if gstrip: _dstate[gas_idx] -= kLa_stripping * (QC[gas_idx] - S_gas_air) + _update_dstate() else: def dy_dt(t, QC_ins, QC, dQC_ins): # QC[QC < 2.2e-16] = 0. @@ -752,29 +761,29 @@ def dX_dt(t, X): def _design(self): pass - def _compile_dC_dt(self, V0, Qin, Cin, C, fill, aer): - isa = isinstance - processes = _add_aeration_to_growth_model(aer, self._model) - if fill: - t = symbols('t') - mass_balance_terms = list(zip(Cin, C, processes.production_rates.rate_of_production)) - C_dot_eqs = [(cin-c)/(t+V0/Qin) + r for cin, c, r in mass_balance_terms] - if isa(aer, (float, int)): C_dot_eqs[self.components.index(self._DO_ID)] = 0 - def dC_dt(t, y): - C_dot = lambdify([t]+C, C_dot_eqs) - return C_dot(t, *y) - J = Matrix(dC_dt(t, C)).jacobian(C) - else: - C_dot_eqs = processes.production_rates.rate_of_production - if isa(aer, (float, int)): C_dot_eqs[self.components.index(self._DO_ID)] = 0 - def dC_dt(t, y): - C_dot = lambdify(C, C_dot_eqs) - return C_dot(*y) - J = Matrix(dC_dt(None, C)).jacobian(C) - def J_func(t, y): - J_func = lambdify(C, J) - return J_func(*y) - return (dC_dt, J_func) + # def _compile_dC_dt(self, V0, Qin, Cin, C, fill, aer): + # isa = isinstance + # processes = _add_aeration_to_growth_model(aer, self._model) + # if fill: + # t = symbols('t') + # mass_balance_terms = list(zip(Cin, C, processes.production_rates.rate_of_production)) + # C_dot_eqs = [(cin-c)/(t+V0/Qin) + r for cin, c, r in mass_balance_terms] + # if isa(aer, (float, int)): C_dot_eqs[self.components.index(self._DO_ID)] = 0 + # def dC_dt(t, y): + # C_dot = lambdify([t]+C, C_dot_eqs) + # return C_dot(t, *y) + # J = Matrix(dC_dt(t, C)).jacobian(C) + # else: + # C_dot_eqs = processes.production_rates.rate_of_production + # if isa(aer, (float, int)): C_dot_eqs[self.components.index(self._DO_ID)] = 0 + # def dC_dt(t, y): + # C_dot = lambdify(C, C_dot_eqs) + # return C_dot(*y) + # J = Matrix(dC_dt(None, C)).jacobian(C) + # def J_func(t, y): + # J_func = lambdify(C, J) + # return J_func(*y) + # return (dC_dt, J_func) #%% class PFR(SanUnit): From c6da4a2b634da5ddbdce419256d3498dea66972b Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Fri, 26 Jul 2024 16:19:40 -0700 Subject: [PATCH 388/424] set pH & minor fix to `PFR` --- qsdsan/data/_masm2d.xlsx | Bin 47080 -> 54762 bytes qsdsan/processes/_asm2d.py | 106 +++++++++--------- .../sanunits/_suspended_growth_bioreactor.py | 1 + 3 files changed, 55 insertions(+), 52 deletions(-) diff --git a/qsdsan/data/_masm2d.xlsx b/qsdsan/data/_masm2d.xlsx index 445d1725a58be35779c53ea3734a7a8d499457b2..8d642e8f639c9f390493653a62713e507c907cca 100644 GIT binary patch delta 26494 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 Q^pAi#8^~bTCw_1IAGlCz>;M1& delta 18759 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 0: rhos[19] = (S_Ca * co3 - Ksp[0]) * f_dis[0] - # # else: rhos[19] = S_Ca * co3 - # # if X_struv > 0: rhos[20] = (S_Mg * nh4 * po4 - Ksp[1]) * f_dis[1] - # # else: rhos[20] = S_Mg * nh4 * po4 - # # if X_newb > 0: rhos[21] = (S_Mg * hpo4 - Ksp[2]) * f_dis[2] - # # else: rhos[21] = S_Mg * hpo4 - # # if X_ACP > 0: rhos[22] = (S_Ca**3 * po4**2 - Ksp[3]) * f_dis[3] - # # else: rhos[22] = S_Ca**3 * po4**2 - # # if X_MgCO3 > 0: rhos[23] = (S_Mg * co3 - Ksp[4]) * f_dis[4] - # # else: rhos[23] = S_Mg * co3 - # SI = (S_Ca * co3 / Ksp[0])**(1/2) - # if SI > 1: rhos[19] = X_CaCO3 * (SI-1)**2 - # else: rhos[19] = 0 - - # SI = (S_Mg * nh4 * po4 / Ksp[1])**(1/3) - # if SI > 1: rhos[20] = X_struv * (SI-1)**3 - # else: rhos[20] = 0 - - # SI = (S_Mg * hpo4 / Ksp[2])**(1/2) - # if SI > 1: rhos[21] = X_newb * (SI-1)**2 - # else: rhos[21] = 0 - - # SI = (S_Ca**3 * po4**2 / Ksp[3])**(1/5) - # if SI > 1: rhos[22] = X_ACP * (SI-1)**5 - # else: rhos[22] = 0 - - # SI = (S_Mg * co3 / Ksp[4])**(1/2) - # if SI > 1: rhos[23] = X_MgCO3 * (SI-1)**2 - # else: rhos[23] = 0 - - # rhos[24] = X_AlOH * po4 * Monod(X_AlOH, K_AlOH) - # rhos[25] = X_FeOH * po4 * Monod(X_FeOH, K_FeOH) - # rhos[19:26] *= k_mmp + h = 1e-7 + nh4 = state_arr[2] * h/(Knh + h) + co2, hco3, co3 = state_arr[8] * ion_speciation(h, Kc1, Kc2) + h3po4, h2po4, hpo4, po4 = state_arr[4] * ion_speciation(h, Kp1, Kp2, Kp3) + + ########## precipitation-dissolution ############# + k_mmp = params['k_mmp'] + Ksp = params['Ksp'] + # K_dis = params['K_dis'] + K_AlOH = params['K_AlOH'] + K_FeOH = params['K_FeOH'] + # f_dis = Monod(state_arr[19:24], K_dis[:5]) + # if X_CaCO3 > 0: rhos[19] = (S_Ca * co3 - Ksp[0]) * f_dis[0] + # else: rhos[19] = S_Ca * co3 + # if X_struv > 0: rhos[20] = (S_Mg * nh4 * po4 - Ksp[1]) * f_dis[1] + # else: rhos[20] = S_Mg * nh4 * po4 + # if X_newb > 0: rhos[21] = (S_Mg * hpo4 - Ksp[2]) * f_dis[2] + # else: rhos[21] = S_Mg * hpo4 + # if X_ACP > 0: rhos[22] = (S_Ca**3 * po4**2 - Ksp[3]) * f_dis[3] + # else: rhos[22] = S_Ca**3 * po4**2 + # if X_MgCO3 > 0: rhos[23] = (S_Mg * co3 - Ksp[4]) * f_dis[4] + # else: rhos[23] = S_Mg * co3 + SI = (S_Ca * co3 / Ksp[0])**(1/2) + if SI > 1: rhos[19] = X_CaCO3 * (SI-1)**2 + else: rhos[19] = 0 + + SI = (S_Mg * nh4 * po4 / Ksp[1])**(1/3) + if SI > 1: rhos[20] = X_struv * (SI-1)**3 + else: rhos[20] = 0 + + SI = (S_Mg * hpo4 / Ksp[2])**(1/2) + if SI > 1: rhos[21] = X_newb * (SI-1)**2 + else: rhos[21] = 0 + + SI = (S_Ca**3 * po4**2 / Ksp[3])**(1/5) + if SI > 1: rhos[22] = X_ACP * (SI-1)**5 + else: rhos[22] = 0 + + SI = (S_Mg * co3 / Ksp[4])**(1/2) + if SI > 1: rhos[23] = X_MgCO3 * (SI-1)**2 + else: rhos[23] = 0 + + rhos[24] = X_AlOH * po4 * Monod(X_AlOH, K_AlOH) + rhos[25] = X_FeOH * po4 * Monod(X_FeOH, K_FeOH) + rhos[19:26] *= k_mmp return rhos @@ -735,10 +737,10 @@ def __new__(cls, components=None, path=None, electron_acceptor_dependent_decay=T K_NH4_H=0.05, K_NH4_PAO=0.05, K_NH4_AUT=1.0, K_P_H=0.01, K_P_PAO=0.01, K_P_AUT=0.01, K_P_S=0.2, K_PP=0.01, K_MAX=0.34, K_IPP=0.02, K_PHA=0.01, - # k_mmp=(5.0, 300, 0.05, 150, 50, 1.0, 1.0), - # pKsp=(6.45, 13.16, 5.8, 23, 7, 21, 26), - k_mmp=(0.024, 120, 0.024, 72, 0.024, 0.024, 0.024), # Flores-Alsina 2016 - pKsp=(8.3, 13.6, 18.175, 28.92, 7.46, 18.2, 37.76), # Flores-Alsina 2016 + k_mmp=(5.0, 300, 0.05, 150, 50, 1.0, 1.0), + pKsp=(6.45, 13.16, 5.8, 23, 7, 21, 26), + # k_mmp=(0.024, 120, 0.024, 72, 0.024, 0.024, 0.024), # Flores-Alsina 2016 + # pKsp=(8.3, 13.6, 18.175, 28.92, 7.46, 18.2, 37.76), # Flores-Alsina 2016 K_dis=(1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0), K_AlOH=0.001, K_FeOH=0.001, pKa=(14, 9.25, 6.37, 10.32, 2.12, 7.21, 12.32, 4.76), diff --git a/qsdsan/sanunits/_suspended_growth_bioreactor.py b/qsdsan/sanunits/_suspended_growth_bioreactor.py index 82b8bb96..d727b659 100644 --- a/qsdsan/sanunits/_suspended_growth_bioreactor.py +++ b/qsdsan/sanunits/_suspended_growth_bioreactor.py @@ -1028,6 +1028,7 @@ def kLa(self, ks): if ks != []: warn('kLa is ignored because DO setpoints have been specified. ' 'To specify kLa, first set DO_setpoints as []') + ks = [] else: if not iter(ks): raise TypeError(f'V_tanks must be an iterable, not {type(ks).__name__}') From 112b7dfc4136ef356a828fcc14f664c3d6440d94 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Mon, 29 Jul 2024 10:57:45 -0700 Subject: [PATCH 389/424] allow to set `maximum_nonsettleable_solids` in Takacs clarifier --- qsdsan/sanunits/_clarifier.py | 21 +++++++++++++++---- .../sanunits/_suspended_growth_bioreactor.py | 2 +- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index 85faa0f5..61150653 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -108,7 +108,8 @@ class FlatBottomCircularClarifier(SanUnit): fns : float, optional Non-settleable fraction of the suspended solids, dimensionless. Must be within [0, 1]. The default is 2.28e-3. - + maximum_nonsettleable_solids : float, optional + Maximum non-settleable solids concentration, in mgTSS/L. The default is None. downward_flow_velocity : float, optional Speed on the basis of which center feed diameter is designed [m/hr]. The default is 42 m/hr (0.7 m/min). [2] design_influent_TSS : float, optional @@ -146,7 +147,9 @@ def __init__(self, ID='', ins=None, outs=(), thermo=None, init_with='WasteStream', underflow=2000, wastage=385, surface_area=1500, height=4, N_layer=10, feed_layer=4, X_threshold=3000, v_max=474, v_max_practical=250, - rh=5.76e-4, rp=2.86e-3, fns=2.28e-3, F_BM_default=default_F_BM, isdynamic=True, + rh=5.76e-4, rp=2.86e-3, fns=2.28e-3, + maximum_nonsettleable_solids=None, + F_BM_default=default_F_BM, isdynamic=True, downward_flow_velocity=42, design_influent_TSS = None, design_influent_flow = None, design_solids_loading_rate = 6, **kwargs): @@ -173,6 +176,7 @@ def __init__(self, ID='', ins=None, outs=(), thermo=None, self._rh = rh self._rp = rp self._fns = fns + self.maximum_nonsettleable_solids = maximum_nonsettleable_solids self._solids = None self._solubles = None self._X_comp = np.zeros(len(self.components)) @@ -326,7 +330,15 @@ def fns(self): def fns(self, fns): if fns < 0 or fns > 1: raise ValueError('fns must be within [0,1].') self._fns = fns - + + @property + def maximum_nonsettleable_solids(self): + '''[float] Maximum non-settleable solids concentration, in mgTSS/L.''' + return self._max_ns + @maximum_nonsettleable_solids.setter + def maximum_nonsettleable_solids(self, ns): + self._max_ns = ns + @property def solids_loading_rate(self): '''solids_loading_rate is the loading in the clarifier''' @@ -469,6 +481,7 @@ def _compile_ODE(self): m = len(x) imass = self.components.i_mass fns = self._fns + max_ns = self._max_ns or 1e6 Q_s = self._Qras + self._Qwas dQC = self._dstate @@ -512,7 +525,7 @@ def dy_dt(t, QC_ins, QC, dQC_ins): Z_in = C_in*(1-x) X_in = sum(C_in*imass*x) # influent TSS dX_in = sum(dC_in*imass*x) - X_min_arr[:] = X_in * fns + X_min_arr[:] = min(X_in * fns, max_ns) X = QC[-n:] # (n, ), TSS for each layer Z = QC[:m] * (1-x) #***********TSS************* diff --git a/qsdsan/sanunits/_suspended_growth_bioreactor.py b/qsdsan/sanunits/_suspended_growth_bioreactor.py index d727b659..0b46da8a 100644 --- a/qsdsan/sanunits/_suspended_growth_bioreactor.py +++ b/qsdsan/sanunits/_suspended_growth_bioreactor.py @@ -1028,7 +1028,7 @@ def kLa(self, ks): if ks != []: warn('kLa is ignored because DO setpoints have been specified. ' 'To specify kLa, first set DO_setpoints as []') - ks = [] + # ks = [] else: if not iter(ks): raise TypeError(f'V_tanks must be an iterable, not {type(ks).__name__}') From fbfb59a68616371748ed4138e913c78679b4f490 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Mon, 29 Jul 2024 14:08:03 -0700 Subject: [PATCH 390/424] enable general pH control in `AnaerobicCSTR` --- qsdsan/processes/_adm1.py | 1 + qsdsan/processes/_adm1_p_extension.py | 2 ++ qsdsan/sanunits/_anaerobic_reactor.py | 14 ++++++++++---- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/qsdsan/processes/_adm1.py b/qsdsan/processes/_adm1.py index 8b23c125..e6a0ed13 100644 --- a/qsdsan/processes/_adm1.py +++ b/qsdsan/processes/_adm1.py @@ -629,6 +629,7 @@ def __new__(cls, components=None, path=None, N_xc=2.686e-3, N_I=4.286e-3, N_aa=7 K_H_base, K_H_dH, kLa, T_base, self._components, root])) + dct['flex_rhos'] = _rhos_adm1 dct['solve_pH'] = solve_pH dct['dydt_Sh2_AD'] = dydt_Sh2_AD dct['grad_dydt_Sh2_AD'] = grad_dydt_Sh2_AD diff --git a/qsdsan/processes/_adm1_p_extension.py b/qsdsan/processes/_adm1_p_extension.py index 52d0e3b3..b3dcf1d1 100644 --- a/qsdsan/processes/_adm1_p_extension.py +++ b/qsdsan/processes/_adm1_p_extension.py @@ -463,6 +463,7 @@ def __new__(cls, components=None, path=None, T_base, self._components, root, #!!! new parameter KS_IP*P_mw])) + dct['flex_rhos'] = _rhos_adm1_p_extension dct['solve_pH'] = solve_pH dct['dydt_Sh2_AD'] = dydt_Sh2_AD dct['grad_dydt_Sh2_AD'] = grad_dydt_Sh2_AD @@ -1041,6 +1042,7 @@ def grad_dydt_Sh2_AD(S_h2, state_arr, h, params, f_stoichio, V_liq, S_h2_in): Q = state_arr[45] return -Q/V_liq + np.dot(grad_rhos, stoichio[[5,6,7,8,10]]) + kLa*stoichio[-3] + dct['flex_rhos'] = _rhos_adm1p dct['solve_pH'] = adm1p_solve_pH dct['dydt_Sh2_AD'] = dydt_Sh2_AD dct['grad_dydt_Sh2_AD'] = grad_dydt_Sh2_AD diff --git a/qsdsan/sanunits/_anaerobic_reactor.py b/qsdsan/sanunits/_anaerobic_reactor.py index a21eccf8..346cff74 100644 --- a/qsdsan/sanunits/_anaerobic_reactor.py +++ b/qsdsan/sanunits/_anaerobic_reactor.py @@ -270,7 +270,8 @@ def __init__(self, ID='', ins=None, outs=(), thermo=None, T=308.15, headspace_P=1.013, external_P=1.013, pipe_resistance=5.0e4, fixed_headspace_P=False, retain_cmps=(), fraction_retain=0.95, - isdynamic=True, exogenous_vars=(), **kwargs): + isdynamic=True, exogenous_vars=(), + pH_ctrl=None, **kwargs): if len(exogenous_vars) == 0: exogenous_vars = (EDV('T', function=lambda t: T), ) super().__init__(ID=ID, ins=ins, outs=outs, thermo=thermo, @@ -293,6 +294,7 @@ def __init__(self, ID='', ins=None, outs=(), thermo=None, self.fixed_headspace_P = fixed_headspace_P self._f_retain = np.array([fraction_retain if cmp.ID in retain_cmps \ else 0 for cmp in self.components]) + self.pH_ctrl = pH_ctrl self._mixed = WasteStream() self._tempstate = {} @@ -523,10 +525,10 @@ def f_q_gas_var_P_headspace(self, rhoTs, S_gas, T): @property def ODE(self): if self._ODE is None: - self._compile_ODE(self.algebraic_h2) + self._compile_ODE(self.algebraic_h2, self.pH_ctrl) return self._ODE - def _compile_ODE(self, algebraic_h2=True): + def _compile_ODE(self, algebraic_h2=True, pH_ctrl=None): if self._model is None: CSTR._compile_ODE(self) else: @@ -535,7 +537,11 @@ def _compile_ODE(self, algebraic_h2=True): _state = self._state _dstate = self._dstate _update_dstate = self._update_dstate - _f_rhos = self.model.rate_function + if pH_ctrl: + _params = self.model.rate_function.params + _f_rhos = lambda state_arr: self.model.flex_rhos(state_arr, _params, h=10**(-pH_ctrl)) + else: + _f_rhos = self.model.rate_function _f_param = self.model.params_eval _M_stoichio = self.model.stoichio_eval n_cmps = len(cmps) From 8b926bd0d0124b70dd8aa93e0143fd5874092cba Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Mon, 29 Jul 2024 14:13:24 -0700 Subject: [PATCH 391/424] enable general pH control for AS process in `mASM2d` --- qsdsan/processes/_asm2d.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/qsdsan/processes/_asm2d.py b/qsdsan/processes/_asm2d.py index 2db98858..751ef1b6 100644 --- a/qsdsan/processes/_asm2d.py +++ b/qsdsan/processes/_asm2d.py @@ -458,7 +458,7 @@ def solve_pH(state_arr, Ka, unit_conversion): # rhos = np.zeros(19+7+2) # 19 biological processes, 7 precipitation/dissociation, 2 gas stripping rhos = np.zeros(19+7) # 19 biological processes, 7 precipitation/dissociation -def _rhos_masm2d(state_arr, params, acceptor_dependent_decay=True): +def _rhos_masm2d(state_arr, params, acceptor_dependent_decay=True, h=None): if 'ks' not in params: k_h, mu_H, mu_PAO, mu_AUT, \ q_fe, q_PHA, q_PP, \ @@ -561,8 +561,7 @@ def _rhos_masm2d(state_arr, params, acceptor_dependent_decay=True): mass2mol = params['mass2mol'] Ka = params['Ka'] Kw, Knh, Kc1, Kc2, Kp1, Kp2, Kp3, Kac = Ka - # h = solve_pH(state_arr, Ka, mass2mol) - h = 1e-7 + if h == None: h = solve_pH(state_arr, Ka, mass2mol) nh4 = state_arr[2] * h/(Knh + h) co2, hco3, co3 = state_arr[8] * ion_speciation(h, Kc1, Kc2) h3po4, h2po4, hpo4, po4 = state_arr[4] * ion_speciation(h, Kp1, Kp2, Kp3) @@ -723,7 +722,8 @@ class mASM2d(CompiledProcesses): D_gas = [1.88e-9, 1.92e-9] # diffusivity p_gas_atm = [0.78, 3.947e-4]# partial pressure in air - def __new__(cls, components=None, path=None, electron_acceptor_dependent_decay=True, + def __new__(cls, components=None, path=None, + electron_acceptor_dependent_decay=True, pH_ctrl=7.0, f_SI=0.0, Y_H=0.625, Y_PAO=0.625, Y_PO4=0.4, Y_PHA=0.2, Y_A=0.24, f_XI_H=0.1, f_XI_PAO=0.1, f_XI_AUT=0.1, k_h=3.0, mu_H=6.0, mu_PAO=1.0, mu_AUT=1.0, @@ -797,7 +797,10 @@ def __new__(cls, components=None, path=None, electron_acceptor_dependent_decay=T cmps.X_PP.i_K, cmps.X_PP.i_Mg) dct['_parameters'] = dict(zip(cls._stoichio_params, stoichio_vals)) dct['_edecay'] = bool(electron_acceptor_dependent_decay) - rhos_masm2d = lambda state_arr, params: _rhos_masm2d(state_arr, params, electron_acceptor_dependent_decay) + dct['pH_ctrl'] = pH_ctrl + if pH_ctrl: h = 10**(-pH_ctrl) + else: h = None + rhos_masm2d = lambda state_arr, params: _rhos_masm2d(state_arr, params, electron_acceptor_dependent_decay, h) self.set_rate_function(rhos_masm2d) Ka = np.array([10**(-p) for p in pKa]) # f_kLa = np.array(cls.D_gas)/cls.D_O2 From 72e69dd3d038b649653ddcf9ab756e1618bff571 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Tue, 30 Jul 2024 09:22:57 -0700 Subject: [PATCH 392/424] debug `AnaerobicCSTR` pH control --- qsdsan/sanunits/_anaerobic_reactor.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/qsdsan/sanunits/_anaerobic_reactor.py b/qsdsan/sanunits/_anaerobic_reactor.py index 346cff74..af857086 100644 --- a/qsdsan/sanunits/_anaerobic_reactor.py +++ b/qsdsan/sanunits/_anaerobic_reactor.py @@ -537,9 +537,11 @@ def _compile_ODE(self, algebraic_h2=True, pH_ctrl=None): _state = self._state _dstate = self._dstate _update_dstate = self._update_dstate + h = None if pH_ctrl: _params = self.model.rate_function.params - _f_rhos = lambda state_arr: self.model.flex_rhos(state_arr, _params, h=10**(-pH_ctrl)) + h = 10**(-pH_ctrl) + _f_rhos = lambda state_arr: self.model.flex_rhos(state_arr, _params, h=h) else: _f_rhos = self.model.rate_function _f_param = self.model.params_eval @@ -576,9 +578,9 @@ def h2_stoichio(state_arr): solve_pH = self.model.solve_pH dydt_Sh2_AD = self.model.dydt_Sh2_AD grad_dydt_Sh2_AD = self.model.grad_dydt_Sh2_AD - def solve_h2(QC, S_in, T): + def solve_h2(QC, S_in, T, h=h): Ka = params['Ka_base'] * T_correction_factor(params['T_base'], T, params['Ka_dH']) - h = solve_pH(QC, Ka, unit_conversion) + if h == None: h = solve_pH(QC, Ka, unit_conversion) # S_h2_0 = QC[h2_idx] S_h2_0 = 2.8309E-07 S_h2_in = S_in[h2_idx] From bc976c05c25af7b001c497ee689e50e77a2058c9 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Wed, 31 Jul 2024 10:13:09 -0700 Subject: [PATCH 393/424] more fail-proof mmp rate functions --- qsdsan/processes/_adm1_p_extension.py | 38 +++++++++++++++------------ 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/qsdsan/processes/_adm1_p_extension.py b/qsdsan/processes/_adm1_p_extension.py index b3dcf1d1..846aa70e 100644 --- a/qsdsan/processes/_adm1_p_extension.py +++ b/qsdsan/processes/_adm1_p_extension.py @@ -754,28 +754,32 @@ def _rhos_adm1p(state_arr, params, h=None): # if X_MgCO3 > 0: rhos_p[29] = (S_Mg * co3 - Ksp[4]) * f_dis[4] # else: rhos_p[29] = S_Mg * co3 - rhos_p[30] = X_AlOH * po4 * Monod(X_AlOH, K_AlOH) - rhos_p[31] = X_FeOH * po4 * Monod(X_FeOH, K_FeOH) + rhos_p[25:32] = 0 + if po4 > 0: + if X_AlOH > 0: + rhos_p[30] = X_AlOH * po4 * Monod(X_AlOH, K_AlOH) + if X_FeOH > 0: + rhos_p[31] = X_FeOH * po4 * Monod(X_FeOH, K_FeOH) - SI = (S_Ca * co3 / Ksp[0])**(1/2) - if SI > 1: rhos_p[25] = X_CaCO3 * (SI-1)**2 - else: rhos_p[25] = 0 + if S_Ca > 0 and co3 > 0: + SI = (S_Ca * co3 / Ksp[0])**(1/2) + if SI > 1: rhos_p[25] = X_CaCO3 * (SI-1)**2 - SI = (S_Mg * nh4 * po4 / Ksp[1])**(1/3) - if SI > 1: rhos_p[26] = X_struv * (SI-1)**3 - else: rhos_p[26] = 0 + if S_Mg > 0 and nh4 > 0 and po4 > 0: + SI = (S_Mg * nh4 * po4 / Ksp[1])**(1/3) + if SI > 1: rhos_p[26] = X_struv * (SI-1)**3 - SI = (S_Mg * hpo4 / Ksp[2])**(1/2) - if SI > 1: rhos_p[27] = X_newb * (SI-1)**2 - else: rhos_p[27] = 0 + if S_Mg > 0 and hpo4 > 0: + SI = (S_Mg * hpo4 / Ksp[2])**(1/2) + if SI > 1: rhos_p[27] = X_newb * (SI-1)**2 - SI = (S_Ca**3 * po4**2 / Ksp[3])**(1/5) - if SI > 1: rhos_p[28] = X_ACP * (SI-1)**5 - else: rhos_p[28] = 0 + if S_Ca > 0 and po4 > 0: + SI = (S_Ca**3 * po4**2 / Ksp[3])**(1/5) + if SI > 1: rhos_p[28] = X_ACP * (SI-1)**5 - SI = (S_Mg * co3 / Ksp[4])**(1/2) - if SI > 1: rhos_p[29] = X_MgCO3 * (SI-1)**2 - else: rhos_p[29] = 0 + if S_Mg > 0 and co3 > 0: + SI = (S_Mg * co3 / Ksp[4])**(1/2) + if SI > 1: rhos_p[29] = X_MgCO3 * (SI-1)**2 rhos_p[25:32] *= k_mmp From 87458013ab31a91d5e4894323f63b37b81ca0029 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Wed, 31 Jul 2024 10:14:12 -0700 Subject: [PATCH 394/424] added default initial headspace gas concentrations --- qsdsan/sanunits/_anaerobic_reactor.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/qsdsan/sanunits/_anaerobic_reactor.py b/qsdsan/sanunits/_anaerobic_reactor.py index af857086..2cacbe9f 100644 --- a/qsdsan/sanunits/_anaerobic_reactor.py +++ b/qsdsan/sanunits/_anaerobic_reactor.py @@ -444,19 +444,23 @@ def _init_state(self): #!!! how to make unit conversion generalizable to all models? if self._concs is not None: Cs = self._concs * 1e-3 # mg/L to kg/m3 else: Cs = mixed.conc * 1e-3 # mg/L to kg/m3 - self._state = np.append(Cs, [0]*self._n_gas + [Q]).astype('float64') + Gs = [0]*self._n_gas # initial gas phase concentrations [M] + Gs[0] = 0.041*0.01 + Gs[1] = 0.041*0.57 + Gs[2] = 0.041*0.4 + self._state = np.append(Cs, Gs + [Q]).astype('float64') self._dstate = self._state * 0. def _update_state(self): y = self._state y[-1] = sum(ws.state[-1] for ws in self.ins) - y[y<0] = 0. + # y[y<0] = 0. f_rtn = self._f_retain i_mass = self.components.i_mass chem_MW = self.components.chem_MW n_cmps = len(self.components) Cs = y[:n_cmps]*(1-f_rtn)*1e3 # kg/m3 to mg/L - pH = self._tempstate.pop('pH', 7) + pH = self.pH_ctrl or self._tempstate.pop('pH', 7) if self.split is None: gas, liquid = self._outs if liquid.state is None: @@ -595,7 +599,7 @@ def update_h2_dstate(dstate): def update_h2_dstate(dstate): pass def dy_dt(t, QC_ins, QC, dQC_ins): - QC[QC < 2.2e-16] = 0. + QC[QC < 0] = 0. Q_ins = QC_ins[:, -1] S_ins = QC_ins[:, :-1] * 1e-3 # mg/L to kg/m3 Q = sum(Q_ins) From 85f12006b0731d1cfe949f2d305d0ef255135f96 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Fri, 2 Aug 2024 13:08:29 -0700 Subject: [PATCH 395/424] minor update --- qsdsan/processes/_asm2d.py | 8 ++------ qsdsan/sanunits/_anaerobic_reactor.py | 2 +- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/qsdsan/processes/_asm2d.py b/qsdsan/processes/_asm2d.py index 751ef1b6..0bd73f91 100644 --- a/qsdsan/processes/_asm2d.py +++ b/qsdsan/processes/_asm2d.py @@ -556,7 +556,6 @@ def _rhos_masm2d(state_arr, params, acceptor_dependent_decay=True, h=None): rhos[14:17] *= (aero[3] +eta_decay[1:4]*(1-aero[3])*anox[3]) rhos[18] *= (aero[5] + eta_decay[4]*(1-aero[5])*anox[5]) - # breakpoint() ######### pH ############ mass2mol = params['mass2mol'] Ka = params['Ka'] @@ -583,25 +582,22 @@ def _rhos_masm2d(state_arr, params, acceptor_dependent_decay=True, h=None): # else: rhos[22] = S_Ca**3 * po4**2 # if X_MgCO3 > 0: rhos[23] = (S_Mg * co3 - Ksp[4]) * f_dis[4] # else: rhos[23] = S_Mg * co3 + + rhos[19:26] = 0. SI = (S_Ca * co3 / Ksp[0])**(1/2) if SI > 1: rhos[19] = X_CaCO3 * (SI-1)**2 - else: rhos[19] = 0 SI = (S_Mg * nh4 * po4 / Ksp[1])**(1/3) if SI > 1: rhos[20] = X_struv * (SI-1)**3 - else: rhos[20] = 0 SI = (S_Mg * hpo4 / Ksp[2])**(1/2) if SI > 1: rhos[21] = X_newb * (SI-1)**2 - else: rhos[21] = 0 SI = (S_Ca**3 * po4**2 / Ksp[3])**(1/5) if SI > 1: rhos[22] = X_ACP * (SI-1)**5 - else: rhos[22] = 0 SI = (S_Mg * co3 / Ksp[4])**(1/2) if SI > 1: rhos[23] = X_MgCO3 * (SI-1)**2 - else: rhos[23] = 0 rhos[24] = X_AlOH * po4 * Monod(X_AlOH, K_AlOH) rhos[25] = X_FeOH * po4 * Monod(X_FeOH, K_FeOH) diff --git a/qsdsan/sanunits/_anaerobic_reactor.py b/qsdsan/sanunits/_anaerobic_reactor.py index 2cacbe9f..be4eeb34 100644 --- a/qsdsan/sanunits/_anaerobic_reactor.py +++ b/qsdsan/sanunits/_anaerobic_reactor.py @@ -599,7 +599,7 @@ def update_h2_dstate(dstate): def update_h2_dstate(dstate): pass def dy_dt(t, QC_ins, QC, dQC_ins): - QC[QC < 0] = 0. + # QC[QC < 0] = 0. Q_ins = QC_ins[:, -1] S_ins = QC_ins[:, :-1] * 1e-3 # mg/L to kg/m3 Q = sum(Q_ins) From a66965866f89454f01f837fe7809c7aacd705bd9 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Tue, 13 Aug 2024 16:04:45 -0700 Subject: [PATCH 396/424] more fail-proof rate functions --- qsdsan/data/process_data/_asm1.tsv | 2 +- qsdsan/processes/_asm2d.py | 92 ++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 1 deletion(-) diff --git a/qsdsan/data/process_data/_asm1.tsv b/qsdsan/data/process_data/_asm1.tsv index b0d89d52..9297ecff 100644 --- a/qsdsan/data/process_data/_asm1.tsv +++ b/qsdsan/data/process_data/_asm1.tsv @@ -6,4 +6,4 @@ decay_hetero 1-f_P -1 f_P ? b_H*X_BH decay_auto 1-f_P -1 f_P ? b_A*X_BA ammonification 1 -1 ? k_a*S_ND*X_BH hydrolysis 1 -1 k_h*X_S/(K_X*X_BH+X_S)*(S_O/(K_O_H+S_O)+eta_h*K_O_H/(K_O_H+S_O)*S_NO/(K_NO+S_NO))*X_BH -hydrolysis_N 1 -1 k_h*X_S/(K_X*X_BH+X_S)*(S_O/(K_O_H+S_O)+eta_h*K_O_H/(K_O_H+S_O)*S_NO/(K_NO+S_NO))*X_BH*X_ND/X_S +hydrolysis_N 1 -1 k_h*X_ND/(K_X*X_BH+X_S)*(S_O/(K_O_H+S_O)+eta_h*K_O_H/(K_O_H+S_O)*S_NO/(K_NO+S_NO))*X_BH diff --git a/qsdsan/processes/_asm2d.py b/qsdsan/processes/_asm2d.py index 0bd73f91..3a8b8337 100644 --- a/qsdsan/processes/_asm2d.py +++ b/qsdsan/processes/_asm2d.py @@ -161,6 +161,95 @@ def create_masm2d_cmps(set_thermo=True): #%% +_rhos = np.zeros(21) +def rhos_asm2d(state_arr, params): + S_O2, S_N2, S_NH4, S_NO3, S_PO4, S_F, S_A, S_I, S_ALK, \ + X_I, X_S, X_H, X_PAO, X_PP, X_PHA, X_AUT, X_MeOH, X_MeP = state_arr[:18] + + _rhos[:19] = 0. + if X_H > 0: + K_h = params['K_h'] + K_O2 = params['K_O2'] + K_X = params['K_X'] + K_NO3 = params['K_NO3'] + eta_NO3 = params['eta_NO3'] + eta_fe = params['eta_fe'] + _rhos[:3] = K_h*(X_S/X_H)/(K_X+X_S/X_H)*X_H + _rhos[0] *= S_O2/(K_O2+S_O2) + _rhos[1] *= eta_NO3*K_O2/(K_O2+S_O2)*S_NO3/(K_NO3+S_NO3) + _rhos[2] *= eta_fe*K_O2/(K_O2+S_O2)*K_NO3/(K_NO3+S_NO3) + + mu_H = params['mu_H'] + K_O2_H = params['K_O2_H'] + K_F = params['K_F'] + K_A_H = params['K_A_H'] + K_NH4_H = params['K_NH4_H'] + K_P_H = params['K_P_H'] + K_ALK_H = params['K_ALK_H'] + K_NO3_H = params['K_NO3_H'] + eta_NO3_H = params['eta_NO3_H'] + _rhos[3:7] = mu_H*S_NH4/(K_NH4_H+S_NH4)*S_PO4/(K_P_H+S_PO4)*S_ALK/(K_ALK_H+S_ALK)*X_H + _rhos[[3,5]] *= S_F/(K_F+S_F)*S_F/(S_F+S_A) + _rhos[[4,6]] *= S_A/(K_A_H+S_A)*S_A/(S_F+S_A) + _rhos[3:5] *= S_O2/(K_O2_H+S_O2) + _rhos[6] *= eta_NO3_H*K_O2_H/(K_O2_H+S_O2)*S_NO3/(K_NO3_H+S_NO3) + + q_fe = params['q_fe'] + K_fe = params['K_fe'] + b_H = params['b_H'] + _rhos[7] = q_fe*K_O2_H/(K_O2_H+S_O2)*K_NO3_H/(K_NO3_H+S_NO3)*S_F/(K_fe+S_F)*S_ALK/(K_ALK_H+S_ALK)*X_H + _rhos[8] = b_H*X_H + + K_ALK_PAO = params['K_ALK_PAO'] + if X_PAO > 0: + q_PHA = params['q_PHA'] + K_A_PAO = params['K_A_PAO'] + K_PP = params['K_PP'] + _rhos[9] = q_PHA*S_A/(K_A_PAO+S_A)*S_ALK/(K_ALK_PAO+S_ALK)*(X_PP/X_PAO)/(K_PP+X_PP/X_PAO)*X_PAO + + q_PP = params['q_PP'] + K_O2_PAO = params['K_O2_PAO'] + K_PS = params['K_PS'] + K_PHA = params['K_PHA'] + K_MAX = params['K_MAX'] + K_IPP = params['K_IPP'] + eta_NO3_PAO = params['eta_NO3_PAO'] + K_NO3_PAO = params['K_NO3_PAO'] + mu_PAO = params['mu_PAO'] + K_NH4_PAO = params['K_NH4_PAO'] + K_P_PAO = params['K_P_PAO'] + _rhos[10:12] = q_PP*S_PO4/(K_PS+S_PO4)*S_ALK/(K_ALK_PAO+S_ALK)*(X_PHA/X_PAO)/(K_PHA+X_PHA/X_PAO)*(K_MAX-X_PP/X_PAO)/(K_IPP+K_MAX-X_PP/X_PAO)*X_PAO + _rhos[12:14] = mu_PAO*S_NH4/(K_NH4_PAO+S_NH4)*S_PO4/(K_P_PAO+S_PO4)*S_ALK/(K_ALK_PAO+S_ALK)*(X_PHA/X_PAO)/(K_PHA+X_PHA/X_PAO)*X_PAO + _rhos[[10,12]] *= S_O2/(K_O2_PAO+S_O2) + _rhos[[11,13]] *= eta_NO3_PAO*K_O2_PAO/(K_O2_PAO+S_O2)*S_NO3/(K_NO3_PAO+S_NO3) + + b_PAO = params['b_PAO'] + _rhos[14] = b_PAO*X_PAO + + b_PP = params['b_PP'] + b_PHA = params['b_PHA'] + _rhos[15] = b_PP*X_PP + _rhos[16] = b_PHA*X_PHA + _rhos[14:17] *= S_ALK/(K_ALK_PAO+S_ALK) + + if X_AUT > 0: + mu_AUT = params['mu_AUT'] + K_O2_AUT = params['K_O2_AUT'] + K_NH4_AUT = params['K_NH4_AUT'] + K_P_AUT = params['K_P_AUT'] + K_ALK_AUT = params['K_ALK_AUT'] + b_AUT = params['b_AUT'] + _rhos[17] = mu_AUT*S_O2/(K_O2_AUT+S_O2)*S_NH4/(K_NH4_AUT+S_NH4)*S_PO4/(K_P_AUT+S_PO4)*S_ALK/(K_ALK_AUT+S_ALK)*X_AUT + _rhos[18] = b_AUT*X_AUT + + k_PRE = params['k_PRE'] + k_RED = params['k_RED'] + K_ALK_PRE = params['K_ALK_PRE'] + _rhos[19] = k_PRE*S_PO4*X_MeOH + _rhos[20] = k_RED*X_MeP*S_ALK/(K_ALK_PRE+S_ALK) + + return _rhos + @chemicals_user class ASM2d(CompiledProcesses): ''' @@ -431,6 +520,9 @@ def __new__(cls, components=None, k_PRE=k_PRE, k_RED=k_RED, K_ALK_PRE=K_ALK_PRE*12, COD_deN=cmps.S_N2.i_COD-cmps.S_NO3.i_COD, **kwargs) + self.set_rate_function(rhos_asm2d) + self.rate_function._params = self.parameters + return self #%% From f0809e2428104aac21940a789317419a63323ca2 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Tue, 13 Aug 2024 16:05:12 -0700 Subject: [PATCH 397/424] minor bug fix --- qsdsan/sanunits/_junction.py | 4 ++-- qsdsan/sanunits/_suspended_growth_bioreactor.py | 5 +---- tests/test_exposan.py | 2 +- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/qsdsan/sanunits/_junction.py b/qsdsan/sanunits/_junction.py index 8151d17d..85fc14ac 100644 --- a/qsdsan/sanunits/_junction.py +++ b/qsdsan/sanunits/_junction.py @@ -893,7 +893,7 @@ def _compile_reactions(self): atol = self.atol cmps_asm = ins.components - S_NO_i_COD = cmps_asm.S_NO.i_COD + S_NO_i_COD = -40/14 X_BH_i_N = cmps_asm.X_BH.i_N X_BA_i_N = cmps_asm.X_BA.i_N asm_X_I_i_N = cmps_asm.X_I.i_N @@ -1054,7 +1054,7 @@ def asm2adm(asm_vals): S_IN = adm_vals[adm_ions_idx[0]] S_IC = (asm_charge_tot - S_IN*alpha_IN)/alpha_IC net_Scat = asm_charge_tot + proton_charge - if net_Scat > 0: + if net_Scat > 0: S_cat = net_Scat S_an = 0 else: diff --git a/qsdsan/sanunits/_suspended_growth_bioreactor.py b/qsdsan/sanunits/_suspended_growth_bioreactor.py index 0b46da8a..5c305a59 100644 --- a/qsdsan/sanunits/_suspended_growth_bioreactor.py +++ b/qsdsan/sanunits/_suspended_growth_bioreactor.py @@ -293,7 +293,7 @@ def _init_state(self): def _update_state(self): arr = self._state - arr[arr < 2.2e-16] = 0. + arr[arr < 1e-16] = 0. arr[-1] = sum(ws.state[-1] for ws in self.ins) if self.split is None: self._outs[0].state = arr else: @@ -364,7 +364,6 @@ def _compile_ODE(self): i = self.components.index(self._DO_ID) fixed_DO = self._aeration def dy_dt(t, QC_ins, QC, dQC_ins): - # QC[QC < 2.2e-16] = 0. QC[i] = fixed_DO dydt_cstr(QC_ins, QC, V, _dstate) if hasexo: QC = np.append(QC, f_exovars(t)) @@ -376,7 +375,6 @@ def dy_dt(t, QC_ins, QC, dQC_ins): aer_stoi = aer._stoichiometry aer_frho = aer.rate_function def dy_dt(t, QC_ins, QC, dQC_ins): - # QC[QC < 2.2e-16] = 0. dydt_cstr(QC_ins, QC, V, _dstate) if hasexo: QC = np.append(QC, f_exovars(t)) _dstate[:-1] += r(QC) + aer_stoi * aer_frho(QC) @@ -384,7 +382,6 @@ def dy_dt(t, QC_ins, QC, dQC_ins): _update_dstate() else: def dy_dt(t, QC_ins, QC, dQC_ins): - # QC[QC < 2.2e-16] = 0. dydt_cstr(QC_ins, QC, V, _dstate) if hasexo: QC = np.append(QC, f_exovars(t)) _dstate[:-1] += r(QC) diff --git a/tests/test_exposan.py b/tests/test_exposan.py index 5be8c89d..5f645ba3 100644 --- a/tests/test_exposan.py +++ b/tests/test_exposan.py @@ -35,7 +35,7 @@ def test_exposan(): from exposan.bsm1 import create_system as create_bsm1_system, biomass_IDs bsm1_sys = create_bsm1_system() bsm1_sys.simulate(t_span=(0,10), method='BDF') - print(get_SRT(bsm1_sys, biomass_IDs=biomass_IDs)) # to test the `get_SRT` function + print(get_SRT(bsm1_sys, biomass_IDs=biomass_IDs['asm1'])) # to test the `get_SRT` function #!!! Will use bsm2 to test the junction models # from exposan.interface import create_system as create_inter_system From dcef80a7dc1b747828e397450febf7f97f66ea07 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Tue, 13 Aug 2024 16:44:36 -0700 Subject: [PATCH 398/424] minor bug fix --- qsdsan/processes/_asm2d.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qsdsan/processes/_asm2d.py b/qsdsan/processes/_asm2d.py index 3a8b8337..9cbd6d94 100644 --- a/qsdsan/processes/_asm2d.py +++ b/qsdsan/processes/_asm2d.py @@ -192,7 +192,7 @@ def rhos_asm2d(state_arr, params): _rhos[[3,5]] *= S_F/(K_F+S_F)*S_F/(S_F+S_A) _rhos[[4,6]] *= S_A/(K_A_H+S_A)*S_A/(S_F+S_A) _rhos[3:5] *= S_O2/(K_O2_H+S_O2) - _rhos[6] *= eta_NO3_H*K_O2_H/(K_O2_H+S_O2)*S_NO3/(K_NO3_H+S_NO3) + _rhos[5:7] *= eta_NO3_H*K_O2_H/(K_O2_H+S_O2)*S_NO3/(K_NO3_H+S_NO3) q_fe = params['q_fe'] K_fe = params['K_fe'] From 16f9fe1d5e55298d49c55bda4583d947b12fbef4 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Tue, 13 Aug 2024 16:57:21 -0700 Subject: [PATCH 399/424] improve stability --- qsdsan/sanunits/_anaerobic_reactor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qsdsan/sanunits/_anaerobic_reactor.py b/qsdsan/sanunits/_anaerobic_reactor.py index be4eeb34..3505778b 100644 --- a/qsdsan/sanunits/_anaerobic_reactor.py +++ b/qsdsan/sanunits/_anaerobic_reactor.py @@ -454,7 +454,7 @@ def _init_state(self): def _update_state(self): y = self._state y[-1] = sum(ws.state[-1] for ws in self.ins) - # y[y<0] = 0. + y[y<1e-16] = 0. f_rtn = self._f_retain i_mass = self.components.i_mass chem_MW = self.components.chem_MW From 7a59ddbdd4db728b19fec895610fad074389f27a Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Tue, 13 Aug 2024 17:02:43 -0700 Subject: [PATCH 400/424] Create build-dev.yml --- .github/workflows/build-dev.yml | 37 +++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 .github/workflows/build-dev.yml diff --git a/.github/workflows/build-dev.yml b/.github/workflows/build-dev.yml new file mode 100644 index 00000000..086bb3ee --- /dev/null +++ b/.github/workflows/build-dev.yml @@ -0,0 +1,37 @@ +# This workflow will install Python dependencies, run tests with a variety of Python versions, and report coverage + +name: build-only + +on: + push: + branches: [dev] + pull_request: + branches: [dev] + +jobs: + build: + + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + # os: [ubuntu-latest, macos-latest, windows-latest] if want multiple os + python-version: ["3.9", "3.10"] + + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install pytest nbval + pip install --no-cache-dir git+https://github.com/BioSTEAMDevelopmentGroup/thermosteam.git@qsdsan + pip install --no-cache-dir git+https://github.com/BioSTEAMDevelopmentGroup/biosteam.git@qsdsan + pip install --no-cache-dir git+https://github.com/QSD-Group/EXPOsan.git@bsm2 + pip install --no-cache-dir -r requirements.txt + - name: Test with pytest + run: | + pytest \ No newline at end of file From 3da83c7bbe21a8ba162f1358419be114c96e117b Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Wed, 14 Aug 2024 08:50:19 -0700 Subject: [PATCH 401/424] name fix --- .github/workflows/build-dev.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-dev.yml b/.github/workflows/build-dev.yml index 086bb3ee..15f950fc 100644 --- a/.github/workflows/build-dev.yml +++ b/.github/workflows/build-dev.yml @@ -1,6 +1,6 @@ # This workflow will install Python dependencies, run tests with a variety of Python versions, and report coverage -name: build-only +name: build-dev on: push: From ff1271f934d7f1c7f689ff9b20ffb331f43d5cb4 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Wed, 14 Aug 2024 10:40:22 -0700 Subject: [PATCH 402/424] clean up ASM2d-ADM1 interfaces --- qsdsan/sanunits/_junction.py | 238 +++++++++++------------------------ 1 file changed, 74 insertions(+), 164 deletions(-) diff --git a/qsdsan/sanunits/_junction.py b/qsdsan/sanunits/_junction.py index 85fc14ac..83e6ab58 100644 --- a/qsdsan/sanunits/_junction.py +++ b/qsdsan/sanunits/_junction.py @@ -1113,7 +1113,6 @@ class ASM2dtoADM1(ADMjunction): xs_to_li = 0.7 bio_to_li = 0.4 frac_deg = 0.68 - asm_X_I_i_N = 0.06 def isbalanced(self, lhs, rhs_vals, rhs_i): rhs = sum(rhs_vals*rhs_i) @@ -1169,26 +1168,19 @@ def _compile_reactions(self): # Retrieve constants ins = self.ins[0] outs = self.outs[0] - rtol = self.rtol - atol = self.atol - cmps_asm = ins.components - - S_NO3_i_COD = cmps_asm.S_NO3.i_COD + cmps_asm = ins.components + S_NO3_i_COD = cmps_asm.S_NO3.i_COD - cmps_asm.S_N2.i_COD X_H_i_N = cmps_asm.X_H.i_N X_AUT_i_N = cmps_asm.X_AUT.i_N X_PAO_i_N = cmps_asm.X_PAO.i_N S_F_i_N = cmps_asm.S_F.i_N X_S_i_N = cmps_asm.X_S.i_N asm_S_I_i_N = cmps_asm.S_I.i_N - - if self.asm_X_I_i_N == None: - asm_X_I_i_N = cmps_asm.X_I.i_N - else: - asm_X_I_i_N = self.asm_X_I_i_N + asm_X_I_i_N = cmps_asm.X_I.i_N if cmps_asm.S_A.i_N > 0: - warn(f'S_A in ASM2d has positive nitrogen content: {cmps_asm.S_S.i_N} gN/gCOD. ' + warn(f'S_A in ASM2d has positive nitrogen content: {cmps_asm.S_A.i_N} gN/gCOD. ' 'These nitrogen will be ignored by the interface model ' 'and could lead to imbalance of TKN after conversion.') @@ -1196,15 +1188,11 @@ def _compile_reactions(self): S_aa_i_N = cmps_adm.S_aa.i_N X_pr_i_N = cmps_adm.X_pr.i_N adm_S_I_i_N = cmps_adm.S_I.i_N - adm_X_I_i_N = cmps_adm.X_I.i_N - + adm_X_I_i_N = cmps_adm.X_I.i_N adm_ions_idx = cmps_adm.indices(['S_IN', 'S_IC', 'S_cat', 'S_an']) frac_deg = self.frac_deg - alpha_IN = self.alpha_IN - alpha_IC = self.alpha_IC - proton_charge = 10**(-self.pKa[0]+self.pH) - 10**(-self.pH) # self.pKa[0] is pKw f_corr = self.balance_cod_tkn def asm2adm(asm_vals): @@ -1224,15 +1212,11 @@ def asm2adm(asm_vals): O2_coddm = S_O2 NO3_coddm = -S_NO3*S_NO3_i_COD - cod_spl = (S_A + S_F) + (X_S + X_PHA) + (X_H + X_AUT + X_PAO) - + cod_spl = (S_A + S_F) + (X_S + X_PHA) + (X_H + X_AUT + X_PAO) bioN = X_H*X_H_i_N + X_AUT*X_AUT_i_N + X_PAO*X_PAO_i_N - # To be used in Step 2 - S_F_N = S_F*S_F_i_N #S_ND (in asm1) equals the N content in S_F - # To be used in Step 3 - X_S_N = X_S*X_S_i_N #X_ND (in asm1) equals the N content in X_S - + snd = S_F*S_F_i_N + xnd = X_S*X_S_i_N if cod_spl <= O2_coddm: S_O2 = O2_coddm - cod_spl @@ -1264,161 +1248,92 @@ def asm2adm(asm_vals): S_O2 = S_NO3 = 0 - # Step 2: convert any readily biodegradable - # COD and TKN into amino acids and sugars - - # S_S (in asm1) equals to the sum of S_F and S_A (pg. 82 IWA ASM models handbook) - S_S_asm1 = S_F + S_A + # Step 2: convert readily biodegradable COD and TKN (S_F, S_A) + # into amino acids and sugars + scod = S_F + S_A + req_scod = snd / S_aa_i_N - # First we calculate the amount of amino acid required in ADM1 - # if all available soluble organic N can be mapped to amino acid - req_scod = S_F_N / S_aa_i_N - - # if available S_S is not enough to fulfill that amino acid requirement - if S_S_asm1 < req_scod: - # then all available S_S is mapped to amino acids - S_aa = S_S_asm1 - # and no S_S would be available for conversion to sugars + if scod < req_scod: + S_aa = scod S_su = 0 - # This needs to be followed by a corresponding loss in soluble organic N - S_F_N -= S_aa * S_aa_i_N - # if available S_S is more than enough to fulfill that amino acid requirement + snd -= S_aa * S_aa_i_N else: - # All soluble organic N will be mapped to amino acid S_aa = req_scod - # The line above implies that a certain portion of S_S would also be consumed to form amino acid - # The S_S which is left would form sugar - # In simpler terms; S_S = S_S - S_aa; S_su = S_S - S_su = S_S_asm1 - S_aa - # All soluble organic N would thus be consumed in amino acid formation - S_F_N = 0 - - - # Step 3: convert slowly biodegradable COD and TKN + S_su = scod - S_aa + snd = 0 + + # Step 3: convert slowly biodegradable COD and TKN (X_S, X_PHA) # into proteins, lipids, and carbohydrates - - # First we calculate the amount of protein required in ADM1 - # if all available particulate organic N can be mapped to protein - req_xcod = X_S_N / X_pr_i_N - - # if available X_S is not enough to fulfill that protein requirement - if X_S < req_xcod: - # then all available X_S is mapped to amino acids - X_pr = X_S - # and no X_S would be available for conversion to lipid or carbohydrates - - X_li = self.xs_to_li * X_PHA - X_ch = (1 - self.xs_to_li)*X_PHA - - # This needs to be followed by a corresponding loss in particulate organic N - X_S_N -= X_pr * X_pr_i_N - - # if available X_S is more than enough to fulfill that protein requirement + xcod = X_S + X_PHA + req_xcod = xnd / X_pr_i_N + if xcod < req_xcod: + X_pr = xcod + X_li = X_ch = 0 + xnd -= X_pr * X_pr_i_N else: - # All particulate organic N will be mapped to amino acid X_pr = req_xcod - # The line above implies that a certain portion of X_S would also be consumed to form protein - # The X_S which is left would form lipid and carbohydrates in a percentage define by the user - X_li = self.xs_to_li * (X_S + X_PHA - X_pr) - X_ch = (X_S + X_PHA - X_pr) - X_li - # All particulate organic N would thus be consumed in amino acid formation - X_S_N = 0 + X_li = self.xs_to_li * (xcod - X_pr) + X_ch = (xcod - X_pr) - X_li + xnd = 0 - # Step 4: convert active biomass into protein, lipids, - # carbohydrates and potentially particulate TKN - - # First the amount of biomass N available for protein, lipid etc is determined - # For this calculation, from total biomass N available the amount - # of particulate inert N expected in ADM1 is subtracted - - available_bioN = bioN - (X_H + X_AUT + X_PAO) * (1-frac_deg) * adm_X_I_i_N + # Step 4: convert active biomass (biodegradable portion) into + # protein, lipids, carbohydrates and potentially particulate TKN + biomass_cod = X_H + X_AUT + X_PAO + available_bioN = bioN - biomass_cod * (1-frac_deg) * adm_X_I_i_N if available_bioN < 0: raise RuntimeError('Not enough N in X_H, X_AUT and X_PAO to fully convert ' 'the non-biodegradable portion into X_I in ADM1.') - # Then the amount of biomass N required for biomass conversion to protein is determined - req_bioN = (X_H + X_AUT + X_PAO) * frac_deg * X_pr_i_N - # req_bioP = (X_H + X_AUT) * frac_deg * X_pr_i_P - - # If available biomass N and particulate organic N is greater than - # required biomass N for conversion to protein - if available_bioN + X_S_N >= req_bioN: - # then all biodegradable biomass N (corrsponding to protein demand) is converted to protein - X_pr += (X_H + X_AUT + X_PAO) * frac_deg - # the remaining biomass N is transfered as organic N - X_S_N += available_bioN - req_bioN + req_bioN = biomass_cod * frac_deg * X_pr_i_N + + if available_bioN + xnd >= req_bioN: + X_pr += biomass_cod * frac_deg + xnd += available_bioN - req_bioN else: - # all available N and particulate organic N is converted to protein - bio2pr = (available_bioN + X_S_N)/X_pr_i_N + bio2pr = (available_bioN + xnd)/X_pr_i_N X_pr += bio2pr - # Biodegradable biomass available after conversion to protein is calculated - bio_to_split = (X_H + X_AUT + X_PAO) * frac_deg - bio2pr - # Part of the remaining biomass is mapped to lipid based on user defined value - bio_split_to_li = bio_to_split * self.bio_to_li - X_li += bio_split_to_li - # The other portion of the remanining biomass is mapped to carbohydrates - X_ch += (bio_to_split - bio_split_to_li) - # Since all organic N has been mapped to protein, none is left - X_S_N = 0 + bio_to_split = biomass_cod * frac_deg - bio2pr + bio2li = bio_to_split * self.bio_to_li + X_li += bio2li + X_ch += (bio_to_split - bio2li) + xnd = 0 # Step 5: map particulate inerts - - # 5 (a) - # First determine the amount of particulate inert N available from ASM2d - xi_nsp_asm2d = X_I * asm_X_I_i_N - - # Then determine the amount of particulate inert N that could be produced - # in ADM1 given the ASM1 X_I + xi_nsp = X_I * asm_X_I_i_N xi_ndm = X_I * adm_X_I_i_N - # if particulate inert N available in ASM1 is greater than ADM1 demand - if xi_nsp_asm2d + X_S_N >= xi_ndm: - deficit = xi_ndm - xi_nsp_asm2d - # COD balance - X_I += (X_H + X_AUT + X_PAO) * (1-frac_deg) - # N balance - X_S_N -= deficit - elif isclose(xi_nsp_asm2d+X_S_N, xi_ndm, rel_tol=rtol, abs_tol=atol): - # COD balance - X_I += (X_H + X_AUT + X_PAO) * (1-frac_deg) - # N balance - X_S_N = 0 + if xi_nsp + xnd >= xi_ndm: + X_I += biomass_cod * (1-frac_deg) + xnd -= xi_ndm - xi_nsp else: raise RuntimeError('Not enough N in X_I, X_S to fully ' 'convert X_I in ASM2d into X_I in ADM1.') - # 5(b) - - # Then determine the amount of soluble inert N that could be produced - # in ADM1 given the ASM1 X_I - req_sn = S_I * adm_S_I_i_N - supply_inert_n_asm2d = S_I * asm_S_I_i_N - - # N balance - if req_sn <= S_F_N + supply_inert_n_asm2d: - S_F_N -= (req_sn - supply_inert_n_asm2d) - supply_inert_n_asm2d = 0 - # N balance - elif req_sn <= S_F_N + X_S_N + supply_inert_n_asm2d: - X_S_N -= (req_sn - S_F_N - supply_inert_n_asm2d) - S_F_N = supply_inert_n_asm2d = 0 - # N balance - elif req_sn <= S_F_N + X_S_N + S_NH4 + supply_inert_n_asm2d: - S_NH4 -= (req_sn - S_F_N - X_S_N - supply_inert_n_asm2d) - S_F_N = X_S_N = supply_inert_n_asm2d = 0 - else: - warn('Additional soluble inert COD is mapped to S_su.') - SI_cod = (S_F_N + X_S_N + S_NH4 + supply_inert_n_asm2d)/adm_S_I_i_N - S_su += S_I - SI_cod - S_I = SI_cod - S_F_N = X_S_N = S_NH4 = supply_inert_n_asm2d = 0 + si_ndm = S_I * adm_S_I_i_N + si_nsp = S_I * asm_S_I_i_N + + si_nsp -= si_ndm + if si_nsp < 0: + snd += si_nsp + si_nsp = 0 + if snd < 0: + xnd += snd + snd = 0 + if xnd < 0: + S_NH4 += xnd + xnd = 0 + if S_NH4 < 0: + warn('Additional soluble inert COD is mapped to S_su.') + icod_surplus = - S_NH4 / adm_S_I_i_N # negative + S_I -= icod_surplus + S_su += icod_surplus + S_NH4 = 0 # Step 6: Step map any remaining TKN/P - S_IN = S_F_N + X_S_N + S_NH4 + supply_inert_n_asm2d + S_IN = S_NH4 + xnd + snd + si_nsp # Step 8: check COD and TKN balance - # has TKN: S_aa, S_IN, S_I, X_pr, X_I S_IC = S_cat = S_an = 0 adm_vals = np.array([ @@ -1434,6 +1349,9 @@ def asm2adm(asm_vals): adm_vals = f_corr(asm_vals, adm_vals) # Step 7: charge balance + alpha_IN = self.alpha_IN + alpha_IC = self.alpha_IC + proton_charge = 10**(-self.pKa[0]+self.pH) - 10**(-self.pH) # self.pKa[0] is pKw asm_charge_tot = - _sa/64 + _snh4/14 - _sno3/14 - 1.5*_spo4/31 - _salk - _xpp/31 #Based on page 84 of IWA ASM handbook S_IN = adm_vals[adm_ions_idx[0]] S_IC = (asm_charge_tot -S_IN*alpha_IN)/alpha_IC @@ -1552,8 +1470,6 @@ def _compile_reactions(self): # Retrieve constants ins = self.ins[0] outs = self.outs[0] - rtol = self.rtol - atol = self.atol cmps_adm = ins.components X_c_i_N = cmps_adm.X_c.i_N @@ -1573,7 +1489,7 @@ def _compile_reactions(self): asm_X_I_i_N = cmps_asm.X_I.i_N asm_S_I_i_N = cmps_asm.S_I.i_N - asm_ions_idx = cmps_asm.indices(('S_A', 'S_NH4', 'S_NO3', 'S_PO4','S_ALK', 'X_PP')) + asm_ions_idx = cmps_asm.indices(('S_A', 'S_NH4', 'S_NO3', 'S_PO4', 'X_PP', 'S_ALK')) alpha_IN = self.alpha_IN alpha_IC = self.alpha_IC @@ -1715,19 +1631,13 @@ def adm2asm(adm_vals): asm_vals = f_corr(adm_vals, asm_vals) # Step 5: charge balance for alkalinity - - # asm_ions_idx = cmps_asm.indices(('S_A', 'S_NH4', 'S_NO3', 'S_PO4','S_ALK', 'X_PP')) - - _sa = asm_vals[asm_ions_idx[0]] - _snh4 = asm_vals[asm_ions_idx[1]] - _sno3 = asm_vals[asm_ions_idx[2]] - _spo4 = asm_vals[asm_ions_idx[3]] - _xpp = asm_vals[asm_ions_idx[5]] + + _sa, _snh4, _sno3, _spo4, _xpp, _salk = asm_vals[asm_ions_idx] S_ALK = (sum(_ions * np.append([alpha_IN, alpha_IC], alpha_vfa)) - \ (- _sa/64 + _snh4/14 - _sno3/14 - 1.5*_spo4/31 - _xpp/31))*(-12) - asm_vals[asm_ions_idx[4]] = S_ALK + asm_vals[asm_ions_idx[5]] = S_ALK return asm_vals From 0b1eeda624a7b90070b8854ff1a3ac88e91ae2a7 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Wed, 14 Aug 2024 12:20:43 -0700 Subject: [PATCH 403/424] clean up ASM2d-mADM1 interfaces --- qsdsan/sanunits/_junction.py | 730 +++-------------------------------- 1 file changed, 58 insertions(+), 672 deletions(-) diff --git a/qsdsan/sanunits/_junction.py b/qsdsan/sanunits/_junction.py index 83e6ab58..b86f8561 100644 --- a/qsdsan/sanunits/_junction.py +++ b/qsdsan/sanunits/_junction.py @@ -388,19 +388,6 @@ def __init__(self, ID='', upstream=None, downstream=(), thermo=None, thermo=thermo, init_with=init_with, F_BM_default=F_BM_default, isdynamic=isdynamic, adm1_model=adm1_model) - - # @property - # def T(self): - # '''[float] Temperature of the upstream/downstream [K].''' - # return self.ins[0].T - # @T.setter - # def T(self, T): - # self.ins[0].T = self.outs[0].T = T - - # @property - # def pH(self): - # '''[float] pH of the upstream/downstream.''' - # return self.ins[0].pH @property def asm2d_model(self): @@ -423,22 +410,6 @@ def adm1_model(self, model): raise ValueError('`adm1_model` must be an `ADM1_p_extension` object, ' #!!! update error message f'the given object is {type(model).__name__}.') self._adm1_model = model - - # @property - # def T_base(self): - # '''[float] Base temperature in the ADM1 model.''' - # return self.adm1_model.rate_function.params['T_base'] - - # @property - # def pKa_base(self): - # '''[float] pKa of the acid-base pairs at the base temperature in the ADM1 model.''' - # Ka_base = self.adm1_model.rate_function.params['Ka_base'] - # return -np.log10(Ka_base) - - # @property - # def Ka_dH(self): - # '''[float] Heat of reaction for Ka.''' - # return self.adm1_model.rate_function.params['Ka_dH'] @property def pKa(self): @@ -1688,11 +1659,6 @@ class mADM1toASM2d(mADMjunction): # User defined values bio_to_xs = 0.9 - # Since we are matching PAOs directly from ASM2d to mADM1, it is important - # for PAOs to have identical N/P content across models - # adm_X_PAO_i_N = 0.07 - # adm_X_PAO_i_P = 0.02 - @property def T(self): '''[float] Temperature of the upstream/downstream [K].''' @@ -1805,11 +1771,7 @@ def balance_cod_tkn(self, adm_vals, asm_vals): f'effluent (ASM) TKN is {asm_tkn}. ') return asm_vals - def _compile_reactions(self): - # Retrieve constants - # rtol = self.rtol - # atol = self.atol - + def _compile_reactions(self): cmps_adm = self.ins[0].components cmps_asm = self.outs[0].components self.check_component_properties(cmps_asm, cmps_adm) @@ -1817,76 +1779,29 @@ def _compile_reactions(self): # N balance X_pr_i_N = cmps_adm.X_pr.i_N S_aa_i_N = cmps_adm.S_aa.i_N - # adm_X_I_i_N = cmps_adm.X_I.i_N - # adm_S_I_i_N = cmps_adm.S_I.i_N adm_i_N = cmps_adm.i_N adm_bio_indices = cmps_adm.indices(('X_su', 'X_aa', 'X_fa', 'X_c4', 'X_pro', 'X_ac', 'X_h2')) # P balance X_pr_i_P = cmps_adm.X_pr.i_P - # adm_X_I_i_P = cmps_adm.X_I.i_P - # adm_S_I_i_P = cmps_adm.S_I.i_P S_aa_i_P = cmps_adm.S_aa.i_P adm_i_P = cmps_adm.i_P # N balance X_S_i_N = cmps_asm.X_S.i_N S_F_i_N = cmps_asm.S_F.i_N - # S_A_i_N = cmps_asm.S_A.i_N asm_X_I_i_N = cmps_asm.X_I.i_N - # asm_S_I_i_N = cmps_asm.S_I.i_N asm_ions_idx = cmps_asm.indices(('S_NH4', 'S_A', 'S_NO3', 'S_PO4', 'S_ALK')) # P balance X_S_i_P = cmps_asm.X_S.i_P S_F_i_P = cmps_asm.S_F.i_P - # S_A_i_P = cmps_asm.S_A.i_P asm_X_I_i_P = cmps_asm.X_I.i_P - # asm_S_I_i_P = cmps_asm.S_I.i_P - - #!!! All checks are now done in `check_component_property` - # Checks for direct mapping of X_PAO, X_PP, X_PHA - - # # Check for X_PAO (measured as COD so i_COD = 1 in both ASM2d and ADM1) - - # asm_X_PAO_i_N = cmps_asm.X_PAO.i_N - # adm_X_PAO_i_N = cmps_adm.X_PAO.i_N - - # if self.adm_X_PAO_i_N == None: - # adm_X_PAO_i_N = cmps_adm.X_PAO.i_N - # else: - # adm_X_PAO_i_N = self.adm_X_PAO_i_N - - # if asm_X_PAO_i_N != adm_X_PAO_i_N: - # raise RuntimeError('X_PAO cannot be directly mapped as N content' - # f'in asm2d_X_PAO_i_N = {asm_X_PAO_i_N} is not equal to' - # f'adm_X_PAO_i_N = {adm_X_PAO_i_N}') - - # asm_X_PAO_i_P = cmps_asm.X_PAO.i_P - # adm_X_PAO_i_P = cmps_adm.X_PAO.i_P - - # if self.adm_X_PAO_i_P == None: - # adm_X_PAO_i_P = cmps_adm.X_PAO.i_P - # else: - # adm_X_PAO_i_P = self.adm_X_PAO_i_P - # if asm_X_PAO_i_P != adm_X_PAO_i_P: - # raise RuntimeError('X_PAO cannot be directly mapped as P content' - # f'in asm2d_X_PAO_i_P = {asm_X_PAO_i_P} is not equal to' - # f'adm_X_PAO_i_P = {adm_X_PAO_i_P}') - - # # Checks not required for X_PP as measured as P in both, with i_COD = i_N = 0 - # # Checks not required for X_PHA as measured as COD in both, with i_N = i_P = 0 - - #!!! alpha must be called within the function. - # alpha_IN = self.alpha_IN - # alpha_IC = self.alpha_IC - # alpha_IP = self.alpha_IP - # alpha_vfa = self.alpha_vfa f_corr = self.balance_cod_tkn - + bio_to_xs = self.bio_to_xs # To convert components from mADM1 to ASM2d (madm1-2-asm2d) def madm12asm2d(adm_vals): @@ -1895,44 +1810,23 @@ def madm12asm2d(adm_vals): X_PHA, X_PP, X_PAO, S_K, S_Mg, X_MeOH, X_MeP, S_cat, S_an, H2O = adm_vals # Step 0: snapshot of charged components - # Not sure about charge on X_PP, S_Mg, S_K (PHA and PAO would have zero charge) # X_PP in ADM1 is charge neutral _ions = np.array([S_IN, S_IC, S_IP, S_Mg, S_K, S_ac, S_pro, S_bu, S_va]) # Step 1a: convert biomass and inert particulates into X_S and X_I - - # What is available bio_cod = X_su + X_aa + X_fa + X_c4 + X_pro + X_ac + X_h2 bio_n = sum((adm_vals*adm_i_N)[adm_bio_indices]) bio_p = sum((adm_vals*adm_i_P)[adm_bio_indices]) - - - #!!! In default ASM2d stoichiometry, biomass decay (cell lysis) - #!!! yields 90% particulate substrate + 10% X_I - #!!! so: convert both biomass and X_I in adm to X_S and X_I in asm - - # ----------------------------Rai version--------------------------------------------- - # What is available - # xi_n = X_I*adm_X_I_i_N - # xi_p = X_I*adm_X_I_i_P - # ----------------------------Rai version--------------------------------------------- - - # What would be formed by X_S - xs_cod = bio_cod * self.bio_to_xs + + xs_cod = bio_cod * bio_to_xs xs_ndm = xs_cod * X_S_i_N xs_pdm = xs_cod * X_S_i_P - # What would be formed by X_I (ASM2d) - # xi_cod = bio_cod * (1 - self.bio_to_xs) + X_I - X_I += bio_cod * (1 - self.bio_to_xs) - - # ----------------------------Rai version--------------------------------------------- - # xi_ndm = xi_cod * asm_X_I_i_N - # xi_pdm = xi_cod * asm_X_I_i_P - # ----------------------------Rai version--------------------------------------------- - - # MAPPING OF X_S - + bio2xi = bio_cod * (1 - bio_to_xs) + X_I += bio2xi + deficit_N = bio2xi*asm_X_I_i_N # additional N needed for the mapping + deficit_P = bio2xi*asm_X_I_i_P + # MAPPING OF X_S # Case I: Both bio_N and bio_P are sufficient if xs_ndm <= bio_n and xs_pdm <= bio_p: X_S = xs_cod @@ -1961,77 +1855,7 @@ def madm12asm2d(adm_vals): if bio_n < 0: S_IN += bio_n bio_n = 0 - - # Step 2: MAPPING OF X_I - - # Flores Alsina - - #!!! equality is enforced in `check_component_properties` - # if asm_X_I_i_N == adm_X_I_i_N and asm_X_I_i_P == adm_X_I_i_P: - # X_I = xi_cod - excess_N = (bio_cod * (1 - self.bio_to_xs))*asm_X_I_i_N - excess_P = (bio_cod * (1 - self.bio_to_xs))*asm_X_I_i_P - # else: - # raise RuntimeError('N and P content in X_I should be same') - - # ----------------------------Rai version--------------------------------------------- - # if xi_ndm < bio_n + xi_n + S_IN and xi_pdm < bio_p + xi_p + S_IP: - - # X_I = xi_cod - # xi_cod = 0 - - # xi_n -= xi_ndm - # if xi_n < 0: - # bio_n += xi_n - # xi_n = 0 - # if bio_n < 0: - # S_IN += bio_n - # bio_n = 0 - - # xi_p -= xi_pdm - # if xi_p < 0: - # bio_p += xi_p - # xi_p = 0 - # if bio_p < 0: - # S_IP += bio_p - # bio_p = 0 - # else: - # if (bio_p + xi_p + S_IP) / asm_X_I_i_P > (bio_n + xi_n + S_IN) / asm_X_I_i_N: - - # warn('Not enough N in biomass and X_I to map the specified proportion of' - # 'biomass COD into X_I. Rest of the biomass COD goes to S_A') - # X_I = (bio_n + xi_n + S_IN) / asm_X_I_i_N - # xi_cod -= X_I - - # bio_n = xi_n = S_IN = 0 - - # xi_p -= X_I*asm_X_I_i_P - # if xi_p < 0: - # bio_p += xi_p - # xi_p = 0 - # if bio_p < 0: - # S_IP += bio_p - # bio_p = 0 - - # else: - - # warn('Not enough P in biomass and X_I to map the specified proportion of' - # 'biomass COD into X_I. Rest of the biomass COD goes to S_A') - # X_I = (bio_p + xi_p + S_IP) / asm_X_I_i_P - # xi_cod -= X_I - - # bio_p = xi_p = S_IP = 0 - - # xi_n -= X_I*asm_X_I_i_N - # if xi_n < 0: - # bio_n += xi_n - # xi_n = 0 - # if bio_n < 0: - # S_IN += bio_n - # bio_n = 0 - #------------------------------Rai version--------------------------------------------- - # Step 1(b) xsub_cod = X_ch + X_pr + X_li @@ -2070,8 +1894,7 @@ def madm12asm2d(adm_vals): bio_n += xsub_n xsub_n = 0 - # Step 3(A) - + # Step 3(A) # P balance not required as S_su, S_aa, S_fa do not have P ssub_cod = S_su + S_aa + S_fa ssub_n = S_aa * S_aa_i_N @@ -2132,76 +1955,10 @@ def madm12asm2d(adm_vals): bio_n += xsub_n xsub_n = 0 - # N and P balance not required as S_ac, S_pro, S_bu, and S_va do not have N and P S_A = S_ac + S_pro + S_bu + S_va - - # Step 4 - #!!! equality if enforced in `check_component_properties` - # if asm_S_I_i_N == adm_S_I_i_N and asm_S_I_i_P == adm_S_I_i_P: - # S_I = S_I - # else: - # raise RuntimeError('N and P content in S_I should be same') - - #------------------------------Rai version--------------------------------------------- - # si_n = S_I * adm_S_I_i_N - # si_p = S_I * adm_S_I_i_P - - # si_ndm = si_cod * asm_S_I_i_N - # si_pdm = si_cod * asm_S_I_i_P - - # if si_ndm <= si_n + xi_n + S_IN and si_pdm <= si_p + xi_p + S_IP: - # S_I = si_cod - # si_cod = 0 - # si_n -= si_ndm - # if si_n < 0: - # xi_n += si_n - # si_n = 0 - # if xi_n < 0: - # S_IN += xi_n - # xi_n = 0 - # si_p -= si_pdm - # if si_p < 0: - # xi_p += si_p - # si_p = 0 - # if xi_p < 0: - # S_IP += xi_p - # xi_p = 0 - # else: - # if (si_n + xi_n + S_IN) / asm_S_I_i_N < (si_p + xi_p + S_IP) / asm_S_I_i_P: - # S_I = (si_n + xi_n + S_IN) / asm_S_I_i_N - # si_cod -= S_I - # si_n = xi_n = S_IN = 0 - # si_p -= S_I * asm_S_I_i_P - # if si_p < 0: - # xi_p += si_p - # si_p = 0 - # if xi_p < 0: - # S_IP += xi_p - # xi_p = 0 - # else: - # S_I = (si_p + xi_p + S_IP) / asm_S_I_i_P - # si_cod -= S_I - # si_p = xi_p = S_IP = 0 - # si_n -= S_I * asm_S_I_i_N - # if si_n < 0: - # xi_n += si_n - # si_n = 0 - # if xi_n < 0: - # S_IN += xi_n - # xi_n = 0 - #------------------------------Rai version--------------------------------------------- - - #------------------------------Rai version--------------------------------------------- - # S_NH4 = S_IN + si_n + ssub_n + xsub_n + xi_n + bio_n - # S_PO4 = S_IP + si_p + ssub_p + xsub_p + xi_p + bio_p - #------------------------------Rai version--------------------------------------------- - - S_NH4 = S_IN + ssub_n + xsub_n + bio_n - excess_N - S_PO4 = S_IP + ssub_p + xsub_p + bio_p - excess_P - - #------------------------------Rai version--------------------------------------------- - # S_A += si_cod + ssub_cod + xsub_cod + xi_cod + xs_cod - #------------------------------Rai version--------------------------------------------- + + S_NH4 = S_IN + ssub_n + xsub_n + bio_n - deficit_N + S_PO4 = S_IP + ssub_p + xsub_p + bio_p - deficit_P S_A += ssub_cod + xsub_cod + xs_cod @@ -2223,22 +1980,15 @@ def madm12asm2d(adm_vals): asm_vals = f_corr(adm_vals, asm_vals) - # Step 5: charge balance for alkalinity - - # asm_ions_idx = cmps_asm.indices(('S_NH4', 'S_A', 'S_NO3', 'S_PO4', 'S_ALK')) - + # Step 5: charge balance for alkalinity S_NH4, S_A, S_NO3, S_PO4 = asm_vals[asm_ions_idx[:4]] - # Need to include S_K, S_Mg in the charge balance - # _ions = np.array([S_IN, S_IC, S_IP, S_Mg, S_K, S_ac, S_pro, S_bu, S_va]) - adm_alphas = np.array([self.alpha_IN, self.alpha_IC, self.alpha_IP, 2/24, 1/39, *self.alpha_vfa]) #!!! should be in unit of charge per g adm_charge = np.dot(_ions, adm_alphas) #!!! X_PP in ASM2d has negative charge, to compensate for the absent variables S_K & S_Mg S_ALK = (adm_charge - (S_NH4/14 - S_A/64 - S_NO3/14 - 1.5*S_PO4/31 - X_PP/31))*(-12) - asm_vals[asm_ions_idx[-1]] = S_ALK return asm_vals @@ -2327,12 +2077,6 @@ def pH(self): def pH(self, ph): self._pH = self.outs[0].pH = ph - # def isbalanced(self, lhs, rhs_vals, rhs_i): - # rhs = sum(rhs_vals*rhs_i) - # error = rhs - lhs - # tol = max(self.rtol*lhs, self.rtol*rhs, self.atol) - # return abs(error) <= tol, error, tol, rhs - def balance_cod_tkn_tp(self, asm_vals, adm_vals): cmps_asm = self.ins[0].components cmps_adm = self.outs[0].components @@ -2425,146 +2169,41 @@ def balance_cod_tkn_tp(self, asm_vals, adm_vals): return adm_vals def _compile_reactions(self): - # Retrieve constants - # rtol = self.rtol - # atol = self.atol cmps_asm = self.ins[0].components cmps_adm = self.outs[0].components self.check_component_properties(cmps_asm, cmps_adm) # For COD balance - S_NO3_i_COD = cmps_asm.S_NO3.i_COD + S_NO3_i_COD = cmps_asm.S_NO3.i_COD - cmps_asm.S_N2.i_COD # For N balance X_H_i_N = cmps_asm.X_H.i_N X_AUT_i_N = cmps_asm.X_AUT.i_N S_F_i_N = cmps_asm.S_F.i_N X_S_i_N = cmps_asm.X_S.i_N - # S_A_i_N = cmps_asm.S_A.i_N - - # Due to issue with mapping of X_I across ASM2d and ADM1, making this user dependent is important - - # # PUT A CHECK ON USER DEFINED N AND P CONTENT IN SI and XI here. - # if self.asm_X_I_i_N == None: - # asm_X_I_i_N = cmps_asm.X_I.i_N - # else: - # asm_X_I_i_N = self.asm_X_I_i_N - - # if self.asm_X_I_i_P == None: - # asm_X_I_i_P = cmps_asm.X_I.i_P - # else: - # asm_X_I_i_P = self.asm_X_I_i_P - - # if self.asm_S_I_i_N == None: - # asm_S_I_i_N = cmps_asm.S_I.i_N - # else: - # asm_S_I_i_N = self.asm_X_I_i_N - - # if self.asm_S_I_i_P == None: - # asm_S_I_i_P = cmps_asm.S_I.i_P - # else: - # asm_S_I_i_P = self.asm_S_I_i_P # For P balance X_H_i_P = cmps_asm.X_H.i_P X_AUT_i_P = cmps_asm.X_AUT.i_P S_F_i_P = cmps_asm.S_F.i_P X_S_i_P = cmps_asm.X_S.i_P - # S_A_i_P = cmps_asm.S_A.i_P - - # if cmps_asm.S_A.i_N > 0: - # warn(f'S_A in ASM has positive nitrogen content: {cmps_asm.S_S.i_N} gN/gCOD. ' - # 'These nitrogen will be ignored by the interface model ' - # 'and could lead to imbalance of TKN after conversion.') - - # if cmps_asm.S_A.i_P > 0: - # warn(f'S_A in ASM has positive phosphorous content: {cmps_asm.S_S.i_P} gN/gCOD. ' - # 'These phosphorous will be ignored by the interface model ' - # 'and could lead to imbalance of TP after conversion.') - - # ------------------------------Rai Version--------------------------------------------- - # if cmps_asm.S_I.i_P > 0: - # warn(f'S_I in ASM has positive phosphorous content: {cmps_asm.S_I.i_P} gN/gCOD. ' - # 'These phosphorous will be ignored by the interface model ' - # 'and could lead to imbalance of TP after conversion.') - # ------------------------------Rai Version--------------------------------------------- - - # We do not need to check if X_S.i_N != 0 since we take care of it using X_ND_asm1 - # We do not need to check if S_F.i_N != 0 since we take care of it using S_ND_asm1 - - # For nitrogen balance - # S_ac_i_N = cmps_adm.S_ac.i_N + S_aa_i_N = cmps_adm.S_aa.i_N X_pr_i_N = cmps_adm.X_pr.i_N - # adm_S_I_i_N = cmps_adm.S_I.i_N - adm_X_I_i_N = cmps_adm.X_I.i_N + X_I_i_N = cmps_adm.X_I.i_N - # For phosphorous balance - # S_ac_i_P = cmps_adm.S_ac.i_P X_pr_i_P = cmps_adm.X_pr.i_P - # adm_S_I_i_P = cmps_adm.S_I.i_P - adm_X_I_i_P = cmps_adm.X_I.i_P - - # Checks for direct mapping of X_PAO, X_PP, X_PHA - - # Check for X_PAO (measured as COD so i_COD = 1 in both ASM2d and ADM1) - # asm_X_PAO_i_N = cmps_asm.X_PAO.i_N - - # if self.adm_X_PAO_i_N == None: - # adm_X_PAO_i_N = cmps_adm.X_PAO.i_N - # else: - # adm_X_PAO_i_N = self.adm_X_PAO_i_N - - # if asm_X_PAO_i_N != adm_X_PAO_i_N: - # raise RuntimeError('X_PAO cannot be directly mapped as N content' - # f'in asm2d_X_PAO_i_N = {asm_X_PAO_i_N} is not equal to' - # f'adm_X_PAO_i_N = {adm_X_PAO_i_N}') - - # asm_X_PAO_i_P = cmps_asm.X_PAO.i_P - - # if self.adm_X_PAO_i_P == None: - # adm_X_PAO_i_P = cmps_adm.X_PAO.i_P - # else: - # adm_X_PAO_i_P = self.adm_X_PAO_i_P - - # if asm_X_PAO_i_P != adm_X_PAO_i_P: - # raise RuntimeError('X_PAO cannot be directly mapped as P content' - # f'in asm2d_X_PAO_i_P = {asm_X_PAO_i_P} is not equal to' - # f'adm_X_PAO_i_P = {adm_X_PAO_i_P}') - - # # Checks not required for X_PP as measured as P in both, with i_COD = i_N = 0 - # # Checks not required for X_PHA as measured as COD in both, with i_N = i_P = 0 - - # if asm_X_I_i_N != adm_X_I_i_N: - # raise RuntimeError('X_I cannot be directly mapped as N content' - # f'in asm2d_X_I_i_N = {asm_X_I_i_N} is not equal to' - # f'adm_X_I_i_N = {adm_X_I_i_N}') - - # if asm_X_I_i_P != adm_X_I_i_P: - # raise RuntimeError('X_I cannot be directly mapped as P content' - # f'in asm2d_X_I_i_P = {asm_X_I_i_P} is not equal to' - # f'adm_X_I_i_P = {adm_X_I_i_P}') - - # if asm_S_I_i_N != adm_X_I_i_N: - # raise RuntimeError('S_I cannot be directly mapped as N content' - # f'in asm2d_S_I_i_N = {asm_S_I_i_N} is not equal to' - # f'adm_S_I_i_N = {adm_S_I_i_N}') - - # if asm_S_I_i_P != adm_S_I_i_P: - # raise RuntimeError('S_I cannot be directly mapped as P content' - # f'in asm2d_S_I_i_P = {asm_S_I_i_P} is not equal to \n' - # f'adm_S_I_i_P = {adm_S_I_i_P}') - + X_I_i_P = cmps_adm.X_I.i_P + adm_ions_idx = cmps_adm.indices(['S_IN', 'S_IP', 'S_IC', 'S_cat', 'S_an']) - + xs_to_li = self.xs_to_li + bio_to_li = self.bio_to_li frac_deg = self.frac_deg f_corr = self.balance_cod_tkn_tp # To convert components from ASM2d to mADM1 (asm2d-2-madm1) - def asm2d2madm1(asm_vals): - # S_I, S_S, X_I, X_S, X_BH, X_BA, X_P, S_O, S_NO, S_NH, S_ND, X_ND, S_ALK, S_N2, H2O = asm_vals - + def asm2d2madm1(asm_vals): S_O2, S_N2, S_NH4, S_NO3, S_PO4, S_F, S_A, S_I, S_ALK, X_I, X_S, X_H, \ X_PAO, X_PP, X_PHA, X_AUT, X_MeOH, X_MeP, H2O = asm_vals @@ -2580,24 +2219,19 @@ def asm2d2madm1(asm_vals): O2_coddm = S_O2 NO3_coddm = -S_NO3*S_NO3_i_COD - # cod_spl = S_S + X_S + X_BH + X_BA - # Replacing S_S with S_F + S_A (IWA ASM textbook) - cod_spl = (S_A + S_F) + X_S + (X_H + X_AUT) - # bioN = X_BH*X_BH_i_N + X_BA*X_BA_i_N - bioN = X_H*X_H_i_N + X_AUT*X_AUT_i_N bioP = X_H*X_H_i_P + X_AUT*X_AUT_i_P # To be used in Step 2 - S_ND_asm1 = S_F*S_F_i_N #S_ND (in asm1) equals the N content in S_F + snd = S_F*S_F_i_N #S_ND (in asm1) equals the N content in S_F # To be used in Step 3 - X_ND_asm1 = X_S*X_S_i_N #X_ND (in asm1) equals the N content in X_S + xnd = X_S*X_S_i_N #X_ND (in asm1) equals the N content in X_S # To be used in Step 5 (a) - X_S_P = X_S*X_S_i_P + xpd = X_S*X_S_i_P # To be used in Step 5 (b) - S_F_P = S_F*S_F_i_P + spd = S_F*S_F_i_P if cod_spl <= O2_coddm: S_O2 = O2_coddm - cod_spl @@ -2625,85 +2259,43 @@ def asm2d2madm1(asm_vals): # Step 2: convert any readily biodegradable # COD and TKN into amino acids and sugars - # Directly map acetate S_ac = S_A - # if S_ac_i_N == S_A_i_N and S_ac_i_P == S_A_i_P: - # S_ac = S_A - # else: - # raise RuntimeError('N and P content should be the same in S_A and S_ac for direct translation') - - # First we calculate the amount of amino acid required in ADM1 - # if all available soluble organic N can be mapped to amino acid - req_scod = S_ND_asm1 / S_aa_i_N - - # if available S_F is not enough to fulfill that amino acid requirement + req_scod = snd / S_aa_i_N if S_F < req_scod: - # then all available S_F is mapped to amino acids S_aa = S_F - # and no S_S would be available for conversion to sugars S_su = 0 - # This needs to be followed by a corresponding loss in soluble organic N - S_ND_asm1 -= S_aa * S_aa_i_N - # if available S_F is more than enough to fulfill that amino acid requirement + snd -= S_aa * S_aa_i_N else: - # All soluble organic N will be mapped to amino acid S_aa = req_scod - # The line above implies that a certain portion of S_S would also be consumed to form amino acid - # The S_S which is left would form sugar - # In simpler terms; S_F = S_F - S_aa; S_su = S_F S_su = S_F - S_aa - # All soluble organic N would thus be consumed in amino acid formation - S_ND_asm1 = 0 + snd = 0 # Step 3: convert slowly biodegradable COD and TKN # into proteins, lipids, and carbohydrates - - # First we calculate the amount of protein required in ADM1 - # if all available particulate organic N can be mapped to amino acid - req_xcod = X_ND_asm1 / X_pr_i_N + req_xcod = xnd / X_pr_i_N # Since X_pr_i_N >> X_pr_i_P there's no need to check req_xcod for N and P separately (CONFIRM LATER 05/16) - # if available X_S is not enough to fulfill that protein requirement if X_S < req_xcod: - # then all available X_S is mapped to amino acids X_pr = X_S - # and no X_S would be available for conversion to lipid or carbohydrates X_li = X_ch = 0 - # This needs to be followed by a corresponding loss in particulate organic N - X_ND_asm1 -= X_pr * X_pr_i_N - - # For P balance - # This needs to be followed by a corresponding loss in particulate organic P - X_S_P -= X_pr * X_pr_i_P - - # if available X_S is more than enough to fulfill that protein requirement + xnd -= X_pr * X_pr_i_N + xpd -= X_pr * X_pr_i_P else: - # All particulate organic N will be mapped to amino acid X_pr = req_xcod - # The line above implies that a certain portion of X_S would also be consumed to form protein - # The X_S which is left would form lipid and carbohydrates in a percentage define by the user - X_li = self.xs_to_li * (X_S - X_pr) + X_li = xs_to_li * (X_S - X_pr) X_ch = (X_S - X_pr) - X_li - # All particulate organic N would thus be consumed in amino acid formation - X_ND_asm1 = 0 - - # For P balance - # This needs to be followed by a corresponding loss in particulate organic P - X_S_P -= X_pr * X_pr_i_P + xnd = 0 + xpd -= X_pr * X_pr_i_P # Step 4: convert active biomass into protein, lipids, # carbohydrates and potentially particulate TKN - - # First the amount of biomass N/P available for protein, lipid etc is determined - # For this calculation, from total biomass N available the amount - # of particulate inert N/P expected in ADM1 is subtracted - - available_bioN = bioN - (X_H + X_AUT) * (1-frac_deg) * adm_X_I_i_N + bio2xi = (X_H + X_AUT) * (1-frac_deg) + available_bioN = bioN - bio2xi * X_I_i_N if available_bioN < 0: raise RuntimeError('Not enough N in X_H and X_AUT to fully convert ' 'the non-biodegradable portion into X_I in ADM1.') - available_bioP = bioP - (X_H + X_AUT) * (1-frac_deg) * adm_X_I_i_P + available_bioP = bioP - (X_H + X_AUT) * (1-frac_deg) * X_I_i_P if available_bioP < 0: raise RuntimeError('Not enough P in X_H and X_AUT to fully convert ' 'the non-biodegradable portion into X_I in ADM1.') @@ -2714,12 +2306,10 @@ def asm2d2madm1(asm_vals): # Case I: if both available biomass N/P and particulate organic N/P is greater than # required biomass N/P for conversion to protein - if available_bioN + X_ND_asm1 >= req_bioN and available_bioP + X_S_P >= req_bioP: - # then all biodegradable biomass N/P (corrsponding to protein demand) is converted to protein + if available_bioN + xnd >= req_bioN and available_bioP + xpd >= req_bioP: X_pr += (X_H + X_AUT) * frac_deg - # the remaining biomass N/P is transfered as organic N/P - X_ND_asm1 += available_bioN - req_bioN - X_S_P += available_bioP - req_bioP + xnd += available_bioN - req_bioN + xpd += available_bioP - req_bioP # Case II: if available biomass N and particulate organic N is less than # required biomass N for conversion to protein, but available biomass P and @@ -2731,235 +2321,31 @@ def asm2d2madm1(asm_vals): # Case IV: if both available biomass N/P and particulate organic N/P is less than # required biomass N/P for conversion to protein - else: - - if (available_bioP + X_S_P)/X_pr_i_P < (available_bioN + X_ND_asm1)/X_pr_i_N: - # all available P and particulate organic P is converted to protein - bio2pr = (available_bioP + X_S_P)/X_pr_i_P - X_pr += bio2pr - # Biodegradable biomass available after conversion to protein is calculated - bio_to_split = (X_H + X_AUT) * frac_deg - bio2pr - # Part of the remaining biomass is mapped to lipid based on user defined value - bio_split_to_li = bio_to_split * self.bio_to_li - X_li += bio_split_to_li - # The other portion of the remanining biomass is mapped to carbohydrates - X_ch += (bio_to_split - bio_split_to_li) - # Since all organic P has been mapped to protein, none is left - X_S_P = 0 - - # the remaining biomass N is transfered as organic N - X_ND_asm1 += available_bioN - (bio2pr*X_pr_i_N) - + else: + if (available_bioP + xpd)/X_pr_i_P < (available_bioN + xnd)/X_pr_i_N: + bio2pr = (available_bioP + xpd)/X_pr_i_P + xpd = 0 + xnd += available_bioN - (bio2pr*X_pr_i_N) else: - # all available N and particulate organic N is converted to protein - bio2pr = (available_bioN + X_ND_asm1)/X_pr_i_N - X_pr += bio2pr - # Biodegradable biomass available after conversion to protein is calculated - bio_to_split = (X_H + X_AUT) * frac_deg - bio2pr - # Part of the remaining biomass is mapped to lipid based on user defined value - bio_split_to_li = bio_to_split * self.bio_to_li - X_li += bio_split_to_li - # The other portion of the remanining biomass is mapped to carbohydrates - X_ch += (bio_to_split - bio_split_to_li) - # Since all organic N has been mapped to protein, none is left - X_ND_asm1 = 0 - - # the remaining biomass P is transfered as organic P - X_S_P += available_bioP - (bio2pr*X_pr_i_P) - + bio2pr = (available_bioN + xnd)/X_pr_i_N + xnd = 0 + xpd += available_bioP - (bio2pr*X_pr_i_P) + X_pr += bio2pr + bio_to_split = (X_H + X_AUT) * frac_deg - bio2pr + bio2li = bio_to_split * bio_to_li + X_li += bio2li + X_ch += (bio_to_split - bio2li) # Step 5: map particulate inerts - - # 5 (a) - - # --------------------------Rai version-------------------------------------- - # # First determine the amount of particulate inert N/P available from ASM2d - # xi_nsp_asm2d = X_I * asm_X_I_i_N - # xi_psp_asm2d = X_I * asm_X_I_i_P - - # # Then determine the amount of particulate inert N/P that could be produced - # # in ADM1 given the ASM1 X_I - # xi_ndm = X_I * adm_X_I_i_N - # xi_pdm = X_I * adm_X_I_i_P - - # # if particulate inert N available in ASM1 is greater than ADM1 demand - # if xi_nsp_asm2d + X_ND_asm1 >= xi_ndm: - # deficit = xi_ndm - xi_nsp_asm2d - # # COD balance - # X_I += (X_H+X_AUT) * (1-frac_deg) - # # N balance - # X_ND_asm1 -= deficit - # # P balance - # if xi_psp_asm2d + X_S_P >= xi_pdm: - # deficit = xi_pdm - xi_psp_asm2d - # X_S_P -= deficit - # elif isclose(xi_psp_asm2d+X_S_P, xi_pdm, rel_tol=rtol, abs_tol=atol): - # X_S_P = 0 - # else: - # raise RuntimeError('Not enough P in X_I, X_S to fully ' - # 'convert X_I in ASM2d into X_I in ADM1.') - # elif isclose(xi_nsp_asm2d+X_ND_asm1, xi_ndm, rel_tol=rtol, abs_tol=atol): - # # COD balance - # X_I += (X_H+X_AUT) * (1-frac_deg) - # # N balance - # X_ND_asm1 = 0 - # # P balance - # if xi_psp_asm2d + X_S_P >= xi_pdm: - # deficit = xi_pdm - xi_psp_asm2d - # X_S_P -= deficit - # elif isclose(xi_psp_asm2d+X_S_P, xi_pdm, rel_tol=rtol, abs_tol=atol): - # X_S_P = 0 - # else: - # raise RuntimeError('Not enough P in X_I, X_S to fully ' - # 'convert X_I in ASM2d into X_I in ADM1.') - # else: - # # Since the N balance cannot hold, the P balance is not futher checked - # raise RuntimeError('Not enough N in X_I, X_S to fully ' - # 'convert X_I in ASM2d into X_I in ADM1.') - # --------------------------Rai version-------------------------------------- - - # --------------------------Flores Alsina et al. 2016 version-------------------------------------- - # X_I = X_I - # COD balance - X_I += (X_H+X_AUT) * (1-frac_deg) - # --------------------------Flores Alsina et al. 2016 version-------------------------------------- - - # # 5(b) - - # --------------------------Rai version-------------------------------------- - # # Then determine the amount of soluble inert N/P that could be produced - # # in ADM1 given the ASM1 X_I - # req_sn = S_I * adm_S_I_i_N - # req_sp = S_I * adm_S_I_i_P - - # supply_inert_n_asm2d = S_I * asm_S_I_i_N - - # # N balance - # if req_sn <= S_ND_asm1 + supply_inert_n_asm2d: - # S_ND_asm1 -= (req_sn - supply_inert_n_asm2d) - # supply_inert_n_asm2d = 0 - # # P balance - # if req_sp <= S_F_P: - # S_F_P -= req_sp - # elif req_sp <= S_F_P + X_S_P: - # X_S_P -= (req_sp - S_F_P) - # S_F_P = 0 - # elif req_sp <= S_F_P + X_S_P + S_PO4: - # S_PO4 -= (req_sp - S_F_P - X_S_P) - # S_F_P = X_S_P = 0 - # else: - # S_PO4 -= (req_sp - S_F_P - X_S_P) - # S_F_P = X_S_P = 0 - # # N balance - # elif req_sn <= S_ND_asm1 + X_ND_asm1 + supply_inert_n_asm2d: - # X_ND_asm1 -= (req_sn - S_ND_asm1 - supply_inert_n_asm2d) - # S_ND_asm1 = supply_inert_n_asm2d = 0 - # # P balance - # if req_sp <= S_F_P: - # S_F_P -= req_sp - # elif req_sp <= S_F_P + X_S_P: - # X_S_P -= (req_sp - S_F_P) - # S_F_P = 0 - # elif req_sp <= S_F_P + X_S_P + S_PO4: - # S_PO4 -= (req_sp - S_F_P - X_S_P) - # S_F_P = X_S_P = 0 - # else: - # S_PO4 -= (req_sp - S_F_P - X_S_P) - # S_F_P = X_S_P = 0 - # # N balance - # elif req_sn <= S_ND_asm1 + X_ND_asm1 + S_NH4 + supply_inert_n_asm2d: - # S_NH4 -= (req_sn - S_ND_asm1 - X_ND_asm1 - supply_inert_n_asm2d) - # S_ND_asm1 = X_ND_asm1 = supply_inert_n_asm2d = 0 - # # P balance - # if req_sp <= S_F_P: - # S_F_P -= req_sp - # elif req_sp <= S_F_P + X_S_P: - # X_S_P -= (req_sp - S_F_P) - # S_F_P = 0 - # elif req_sp <= S_F_P + X_S_P + S_PO4: - # S_PO4 -= (req_sp - S_F_P - X_S_P) - # S_F_P = X_S_P = 0 - # else: - # S_PO4 -= (req_sp - S_F_P - X_S_P) - # S_F_P = X_S_P = 0 - # elif req_sp <= S_F_P or req_sp <= S_F_P + X_S_P or req_sp <= S_F_P + X_S_P + S_PO4: - - # S_NH4 -= (req_sn - S_ND_asm1 - X_ND_asm1 - supply_inert_n_asm2d) - # S_ND_asm1 = X_ND_asm1 = supply_inert_n_asm2d = 0 - - # if req_sp <= S_F_P: - # S_F_P -= req_sp - # elif req_sp <= S_F_P + X_S_P: - # X_S_P -= (req_sp - S_F_P) - # S_F_P = 0 - # elif req_sp <= S_F_P + X_S_P + S_PO4: - # S_PO4 -= (req_sp - S_F_P - X_S_P) - # S_F_P = X_S_P = 0 - # else: - # if (S_ND_asm1 + X_ND_asm1 + S_NH4 + supply_inert_n_asm2d)/adm_S_I_i_N < (S_F_P + X_S_P + S_PO4)/adm_S_I_i_P: - # warn('Additional soluble inert COD is mapped to S_su.') - # SI_cod = (S_ND_asm1 + X_ND_asm1 + S_NH4 + supply_inert_n_asm2d)/adm_S_I_i_N - # S_su += S_I - SI_cod - # S_I = SI_cod - # S_ND_asm1 = X_ND_asm1 = S_NH4 = supply_inert_n_asm2d = 0 - - # req_sp = S_I * adm_S_I_i_P - # S_PO4 -= (req_sp - S_F_P - X_S_P) - # S_F_P = X_S_P = 0 - # else: - # warn('Additional soluble inert COD is mapped to S_su.') - # SI_cod = (S_F_P + X_S_P + S_PO4)/adm_S_I_i_P - # S_su += S_I - SI_cod - # S_I = SI_cod - # S_F_P = X_S_P = S_PO4 = 0 - - # req_sn = S_I * adm_S_I_i_N - # S_NH4 -= (req_sn - S_ND_asm1 - X_ND_asm1 - supply_inert_n_asm2d) - # S_ND_asm1 = X_ND_asm1 = supply_inert_n_asm2d = 0 - - # if S_PO4 < 0: - # raise RuntimeError(f'S_PO4 = {S_PO4}\n''Not enough P in S_F_P, X_S_P and S_PO4 to fully ' - # 'convert S_I in ASM2d into S_I in ADM1. Consider ' - # 'increasing the value of P content in S_I (ASM2d)') - - # if S_NH4 < 0: - # raise RuntimeError(f'S_NH4 = {S_NH4}\n''Not enough N in S_I, S_ND_asm1, X_ND_asm1, and S_NH4 to fully ' - # 'convert S_I in ASM2d into S_I in ADM1. Consider ' - # 'increasing the value of N content in S_I (ASM2d)') - # --------------------------Rai version-------------------------------------- - - # --------------------------Flores Alsina et al. 2016 version-------------------------------------- - # S_I = S_I - # --------------------------Flores Alsina et al. 2016 version-------------------------------------- - - # Step 6: Step map any remaining TKN/P - - # --------------------------Rai version-------------------------------------- - # S_IN = S_ND_asm1 + X_ND_asm1 + S_NH4 + supply_inert_n_asm2d - # --------------------------Rai version-------------------------------------- - - # --------------------------Flores Alsina et al. 2016 version-------------------------------------- - S_IN = S_ND_asm1 + X_ND_asm1 + S_NH4 - # --------------------------Flores Alsina et al. 2016 version-------------------------------------- - - S_IP = S_F_P + X_S_P + S_PO4 + X_I += bio2xi + + S_IN = snd + xnd + S_NH4 + S_IP = spd + xpd + S_PO4 # Step 8: check COD and TKN balance # has TKN: S_aa, S_IN, S_I, X_pr, X_I S_IC = S_cat = S_an = 0 - # When mapping components directly in Step 9 ensure the values of - # cmps.i_N, cmps.i_P, and cmps.i_COD are same in both ASM2d and ADM1 - - # Step 9: Mapping common state variables directly - # The next three commented lines are executed when outputting - # array of ADM1 components - # X_PAO (ADM1) = X_PAO (ASM2d) - # X_PP (ADM1) = X_PP (ASM2d) - # X_PHA (ADM1) = X_PHA (ASM2d) - # X_MeOH (ADM1) = X_MeOH (ASM2d) - # X_MeP (ADM1) = X_MeP (ASM2d) - adm_vals = np.array([ S_su, S_aa, 0, 0, 0, 0, S_ac, # S_fa, S_va, S_bu, S_pro, From a92e72f71fa67816bf52701b62b0a02595efdf2f Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Fri, 16 Aug 2024 17:55:19 -0700 Subject: [PATCH 404/424] debug ideal clarifier --- qsdsan/sanunits/_clarifier.py | 1117 +++++++++++++------------- qsdsan/sanunits/_sludge_treatment.py | 443 +++++----- 2 files changed, 771 insertions(+), 789 deletions(-) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index 61150653..2e1b93e5 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -136,12 +136,12 @@ class FlatBottomCircularClarifier(SanUnit): _N_ins = 1 _N_outs = 3 - # Costs - wall_concrete_unit_cost = 1081.73 # $/m3 (Hydromantis. CapdetWorks 4.0. https://www.hydromantis.com/CapdetWorks.html) - slab_concrete_unit_cost = 582.48 # $/m3 (Hydromantis. CapdetWorks 4.0. https://www.hydromantis.com/CapdetWorks.html) - stainless_steel_unit_cost=1.8 # Alibaba. Brushed Stainless Steel Plate 304. https://www.alibaba.com/product-detail/brushed-stainless-steel-plate-304l-stainless_1600391656401.html?spm=a2700.details.0.0.230e67e6IKwwFd + # # Costs + # wall_concrete_unit_cost = 1081.73 # $/m3 (Hydromantis. CapdetWorks 4.0. https://www.hydromantis.com/CapdetWorks.html) + # slab_concrete_unit_cost = 582.48 # $/m3 (Hydromantis. CapdetWorks 4.0. https://www.hydromantis.com/CapdetWorks.html) + # stainless_steel_unit_cost=1.8 # Alibaba. Brushed Stainless Steel Plate 304. https://www.alibaba.com/product-detail/brushed-stainless-steel-plate-304l-stainless_1600391656401.html?spm=a2700.details.0.0.230e67e6IKwwFd - pumps = ('ras', 'was',) + # pumps = ('ras', 'was',) def __init__(self, ID='', ins=None, outs=(), thermo=None, init_with='WasteStream', underflow=2000, wastage=385, @@ -552,261 +552,273 @@ def dy_dt(t, QC_ins, QC, dQC_ins): self._ODE = dy_dt - _units = { - 'Number of clarifiers': 'ea', - 'Volumetric flow': 'm3/day', - 'Clarifier depth': 'm', - 'Surface area': 'm2', - 'Clarifier diameter': 'm', - 'Clarifier volume': 'm3', - 'Design solids loading rate': 'kg/m2/hr', - 'Surface overflow rate': 'm3/day/m2', - 'Hydraulic Retention Time': 'hr', - 'Center feed depth': 'm', - 'Downward flow velocity': 'm/hr', - 'Center feed diameter': 'm', - 'Volume of concrete wall': 'm3', - 'Stainless steel': 'kg', - 'Pump pipe stainless steel' : 'kg', - 'Pump stainless steel': 'kg', - 'Number of pumps': 'ea' - } + # _units = { + # 'Number of clarifiers': 'ea', + # 'Volumetric flow': 'm3/day', + # 'Clarifier depth': 'm', + # 'Surface area': 'm2', + # 'Clarifier diameter': 'm', + # 'Clarifier volume': 'm3', + # 'Design solids loading rate': 'kg/m2/hr', + # 'Surface overflow rate': 'm3/day/m2', + # 'Hydraulic Retention Time': 'hr', + # 'Center feed depth': 'm', + # 'Downward flow velocity': 'm/hr', + # 'Center feed diameter': 'm', + # 'Volume of concrete wall': 'm3', + # 'Stainless steel': 'kg', + # 'Pump pipe stainless steel' : 'kg', + # 'Pump stainless steel': 'kg', + # 'Number of pumps': 'ea' + # } - def _design_pump(self): - ID, pumps = self.ID, self.pumps + # def _design_pump(self): + # ID, pumps = self.ID, self.pumps - self._ras.copy_like(self.outs[1]) - self._was.copy_like(self.outs[2]) + # self._ras.copy_like(self.outs[1]) + # self._was.copy_like(self.outs[2]) - ins_dct = { - 'ras': self._ras, - 'was': self._was, - } + # ins_dct = { + # 'ras': self._ras, + # 'was': self._was, + # } - D = self.design_results + # D = self.design_results - ras_flow = self._ras.get_total_flow('m3/hr') - was_flow = self._was.get_total_flow('m3/hr') + # ras_flow = self._ras.get_total_flow('m3/hr') + # was_flow = self._was.get_total_flow('m3/hr') - ras_flow_u = ras_flow/D['Number of clarifiers']*0.00634 - was_flow_u = was_flow/D['Number of clarifiers']*0.00634 + # ras_flow_u = ras_flow/D['Number of clarifiers']*0.00634 + # was_flow_u = was_flow/D['Number of clarifiers']*0.00634 - Q_mgd = { - 'ras': ras_flow_u, - 'was': was_flow_u, - } + # Q_mgd = { + # 'ras': ras_flow_u, + # 'was': was_flow_u, + # } - type_dct = dict.fromkeys(pumps, 'sludge') - inputs_dct = dict.fromkeys(pumps, (1,)) + # type_dct = dict.fromkeys(pumps, 'sludge') + # inputs_dct = dict.fromkeys(pumps, (1,)) - for i in pumps: - if hasattr(self, f'{i}_pump'): - p = getattr(self, f'{i}_pump') - setattr(p, 'add_inputs', inputs_dct[i]) - else: - ID = f'{ID}_{i}' - capacity_factor=1 - pump = WWTpump( - ID=ID, ins=ins_dct[i], thermo = self.thermo, pump_type=type_dct[i], - Q_mgd=Q_mgd[i], add_inputs=inputs_dct[i], - capacity_factor=capacity_factor, - include_pump_cost=True, - include_building_cost=False, - include_OM_cost=True, - ) - setattr(self, f'{i}_pump', pump) - - pipe_ss, pump_ss = 0., 0. - for i in pumps: - p = getattr(self, f'{i}_pump') - p.simulate() - p_design = p.design_results - pipe_ss += p_design['Pump pipe stainless steel'] - pump_ss += p_design['Pump stainless steel'] - return pipe_ss, pump_ss + # for i in pumps: + # if hasattr(self, f'{i}_pump'): + # p = getattr(self, f'{i}_pump') + # setattr(p, 'add_inputs', inputs_dct[i]) + # else: + # ID = f'{ID}_{i}' + # capacity_factor=1 + # pump = WWTpump( + # ID=ID, ins=ins_dct[i], thermo = self.thermo, pump_type=type_dct[i], + # Q_mgd=Q_mgd[i], add_inputs=inputs_dct[i], + # capacity_factor=capacity_factor, + # include_pump_cost=True, + # include_building_cost=False, + # include_OM_cost=True, + # ) + # setattr(self, f'{i}_pump', pump) + + # pipe_ss, pump_ss = 0., 0. + # for i in pumps: + # p = getattr(self, f'{i}_pump') + # p.simulate() + # p_design = p.design_results + # pipe_ss += p_design['Pump pipe stainless steel'] + # pump_ss += p_design['Pump stainless steel'] + # return pipe_ss, pump_ss - def _design(self): - - self._mixed.mix_from(self.ins) - mixed = self._mixed - D = self.design_results - - # Number of clarifiers based on tentative suggestions by Jeremy - # (would be verified through collaboration with industry) - total_flow = (mixed.get_total_flow('m3/hr')*24)/3785 # in MGD - if total_flow <= 3: - D['Number of clarifiers'] = 2 - elif total_flow > 3 and total_flow <= 8: - D['Number of clarifiers'] = 3 - elif total_flow > 8 and total_flow <=20: - D['Number of clarifiers'] = 4 - else: - D['Number of clarifiers'] = 4 - total_flow -= 20 - D['Number of clarifiers'] += np.ceil(total_flow/20) + # def _design(self): + + # self._mixed.mix_from(self.ins) + # mixed = self._mixed + # D = self.design_results + + # # Number of clarifiers based on tentative suggestions by Jeremy + # # (would be verified through collaboration with industry) + # total_flow = (mixed.get_total_flow('m3/hr')*24)/3785 # in MGD + # if total_flow <= 3: + # D['Number of clarifiers'] = 2 + # elif total_flow > 3 and total_flow <= 8: + # D['Number of clarifiers'] = 3 + # elif total_flow > 8 and total_flow <=20: + # D['Number of clarifiers'] = 4 + # else: + # D['Number of clarifiers'] = 4 + # total_flow -= 20 + # D['Number of clarifiers'] += np.ceil(total_flow/20) - D['Volumetric flow'] = (mixed.get_total_flow('m3/hr')*24)/D['Number of clarifiers'] #m3/day - - # Sidewater depth of a cylindrical clarifier lies between 4-5 m (MOP 8) - D['Clarifier depth'] = self._h # in m - - # Area of clarifier - # D['Surface area'] = solids_clarifier/D['Solids loading rate'] #m2 - D['Surface area'] = self._A/D['Number of clarifiers'] - D['Clarifier diameter'] = np.sqrt(4*D['Surface area']/np.pi) # in m - D['Clarifier volume'] = D['Surface area']*D['Clarifier depth'] # in m3 - - # Checks on SLR,, SOR, and HRT - - D['Design solids loading rate'] = self._slr # kg/(m2*hr) - - total_solids = mixed.get_TSS()*mixed.get_total_flow('m3/hr')/1000 # in kg/hr (mg/l * m3/hr) - solids_clarifier = total_solids/D['Number of clarifiers'] # in kg/hr - simulated_slr = solids_clarifier/D['Surface area'] # in kg/(m2*hr) - - # Consult Joy on the margin or error - if simulated_slr < 0.8*D['Design solids loading rate'] or simulated_slr > 1.2*D['Design solids loading rate']: - design_slr = D['Design solids loading rate'] - warn(f'Solids loading rate = {simulated_slr} is not within 20% of the recommended design level of {design_slr} kg/hr/m2') - - # Check on SLR [3, 4, 5] - if simulated_slr > 14: - warn(f'Solids loading rate = {simulated_slr} is above recommended level of 14 kg/hr/m2') - - # Check on SOR [3, 4, 5] - D['Surface overflow rate'] = D['Volumetric flow']/D['Surface area'] # in m3/m2/hr - if D['Surface overflow rate'] > 49: - sor = D['Surface overflow rate'] - warn(f'Surface overflow rate = {sor} is above recommended level of 49 m3/day/m2') - - # HRT - D['Hydraulic Retention Time'] = D['Clarifier volume']*24/D['Volumetric flow'] # in hr - - # Clarifiers can be center feed or peripheral feed. The design here is for the more commonly deployed center feed. - # Depth of the center feed lies between 30-75% of sidewater depth. [2] - D['Center feed depth'] = 0.5*D['Clarifier depth'] - # Criteria for downward velocity of flow determine - D['Downward flow velocity'] = self._downward_flow_velocity # in m/hr - Center_feed_area = (D['Volumetric flow']/24)/D['Downward flow velocity'] # in m2 - D['Center feed diameter'] = np.sqrt(4*Center_feed_area/np.pi) - - #Sanity check: Diameter of the center feed lies between 20-25% of tank diameter [2] - if D['Center feed diameter'] < 0.20*D['Clarifier diameter'] or D['Center feed diameter'] > 0.25*D['Clarifier diameter']: - cf_dia = D['Center feed diameter'] - tank_dia = D['Clarifier diameter'] - warn(f'Diameter of the center feed does not lie between 20-25% of tank diameter. It is {cf_dia*100/tank_dia} % of tank diameter') + # D['Volumetric flow'] = (mixed.get_total_flow('m3/hr')*24)/D['Number of clarifiers'] #m3/day + + # # Sidewater depth of a cylindrical clarifier lies between 4-5 m (MOP 8) + # D['Clarifier depth'] = self._h # in m + + # # Area of clarifier + # # D['Surface area'] = solids_clarifier/D['Solids loading rate'] #m2 + # D['Surface area'] = self._A/D['Number of clarifiers'] + # D['Clarifier diameter'] = np.sqrt(4*D['Surface area']/np.pi) # in m + # D['Clarifier volume'] = D['Surface area']*D['Clarifier depth'] # in m3 + + # # Checks on SLR,, SOR, and HRT + + # D['Design solids loading rate'] = self._slr # kg/(m2*hr) + + # total_solids = mixed.get_TSS()*mixed.get_total_flow('m3/hr')/1000 # in kg/hr (mg/l * m3/hr) + # solids_clarifier = total_solids/D['Number of clarifiers'] # in kg/hr + # simulated_slr = solids_clarifier/D['Surface area'] # in kg/(m2*hr) + + # # Consult Joy on the margin or error + # if simulated_slr < 0.8*D['Design solids loading rate'] or simulated_slr > 1.2*D['Design solids loading rate']: + # design_slr = D['Design solids loading rate'] + # warn(f'Solids loading rate = {simulated_slr} is not within 20% of the recommended design level of {design_slr} kg/hr/m2') + + # # Check on SLR [3, 4, 5] + # if simulated_slr > 14: + # warn(f'Solids loading rate = {simulated_slr} is above recommended level of 14 kg/hr/m2') + + # # Check on SOR [3, 4, 5] + # D['Surface overflow rate'] = D['Volumetric flow']/D['Surface area'] # in m3/m2/hr + # if D['Surface overflow rate'] > 49: + # sor = D['Surface overflow rate'] + # warn(f'Surface overflow rate = {sor} is above recommended level of 49 m3/day/m2') + + # # HRT + # D['Hydraulic Retention Time'] = D['Clarifier volume']*24/D['Volumetric flow'] # in hr + + # # Clarifiers can be center feed or peripheral feed. The design here is for the more commonly deployed center feed. + # # Depth of the center feed lies between 30-75% of sidewater depth. [2] + # D['Center feed depth'] = 0.5*D['Clarifier depth'] + # # Criteria for downward velocity of flow determine + # D['Downward flow velocity'] = self._downward_flow_velocity # in m/hr + # Center_feed_area = (D['Volumetric flow']/24)/D['Downward flow velocity'] # in m2 + # D['Center feed diameter'] = np.sqrt(4*Center_feed_area/np.pi) + + # #Sanity check: Diameter of the center feed lies between 20-25% of tank diameter [2] + # if D['Center feed diameter'] < 0.20*D['Clarifier diameter'] or D['Center feed diameter'] > 0.25*D['Clarifier diameter']: + # cf_dia = D['Center feed diameter'] + # tank_dia = D['Clarifier diameter'] + # warn(f'Diameter of the center feed does not lie between 20-25% of tank diameter. It is {cf_dia*100/tank_dia} % of tank diameter') - # Amount of concrete required - D_tank = D['Clarifier depth']*39.37 # m to inches - # Thickness of the wall concrete, [m]. Default to be minimum of 1 ft with 1 in added for every ft of depth over 12 ft. (Brian's code) - thickness_concrete_wall = (1 + max(D_tank-12, 0)/12)*0.3048 # from feet to m - inner_diameter = D['Clarifier diameter'] - outer_diameter = inner_diameter + 2*thickness_concrete_wall - D['Volume of concrete wall'] = (np.pi*D['Clarifier depth']/4)*(outer_diameter**2 - inner_diameter**2) - - # Concrete slab thickness, [ft], default to be 2 in thicker than the wall thickness. (Brian's code) - thickness_concrete_slab = thickness_concrete_wall + (2/12)*0.3048 # from inch to m - # From Brian's code - D['Volume of concrete slab'] = (thickness_concrete_slab + thickness_concrete_wall)*D['Surface area'] - - # Amount of metal required for center feed - thickness_metal_wall = 0.3048 # equal to 1 feet, in m (!! NEED A RELIABLE SOURCE !!) - inner_diameter_center_feed = D['Center feed diameter'] - outer_diameter_center_feed = inner_diameter_center_feed + 2*thickness_metal_wall - volume_center_feed = (np.pi*D['Center feed depth']/4)*(outer_diameter_center_feed**2 - inner_diameter_center_feed **2) - density_ss = 7930 # kg/m3, 18/8 Chromium - D['Stainless steel'] = volume_center_feed*density_ss # in kg + # # Amount of concrete required + # D_tank = D['Clarifier depth']*39.37 # m to inches + # # Thickness of the wall concrete, [m]. Default to be minimum of 1 ft with 1 in added for every ft of depth over 12 ft. (Brian's code) + # thickness_concrete_wall = (1 + max(D_tank-12, 0)/12)*0.3048 # from feet to m + # inner_diameter = D['Clarifier diameter'] + # outer_diameter = inner_diameter + 2*thickness_concrete_wall + # D['Volume of concrete wall'] = (np.pi*D['Clarifier depth']/4)*(outer_diameter**2 - inner_diameter**2) + + # # Concrete slab thickness, [ft], default to be 2 in thicker than the wall thickness. (Brian's code) + # thickness_concrete_slab = thickness_concrete_wall + (2/12)*0.3048 # from inch to m + # # From Brian's code + # D['Volume of concrete slab'] = (thickness_concrete_slab + thickness_concrete_wall)*D['Surface area'] + + # # Amount of metal required for center feed + # thickness_metal_wall = 0.3048 # equal to 1 feet, in m (!! NEED A RELIABLE SOURCE !!) + # inner_diameter_center_feed = D['Center feed diameter'] + # outer_diameter_center_feed = inner_diameter_center_feed + 2*thickness_metal_wall + # volume_center_feed = (np.pi*D['Center feed depth']/4)*(outer_diameter_center_feed**2 - inner_diameter_center_feed **2) + # density_ss = 7930 # kg/m3, 18/8 Chromium + # D['Stainless steel'] = volume_center_feed*density_ss # in kg - # Pumps - pipe, pumps = self._design_pump() - D['Pump pipe stainless steel'] = pipe - D['Pump stainless steel'] = pumps + # # Pumps + # pipe, pumps = self._design_pump() + # D['Pump pipe stainless steel'] = pipe + # D['Pump stainless steel'] = pumps - # For secondary clarifier - D['Number of pumps'] = 2*D['Number of clarifiers'] + # # For secondary clarifier + # D['Number of pumps'] = 2*D['Number of clarifiers'] - def _cost(self): + # def _cost(self): - D = self.design_results - C = self.baseline_purchase_costs + # D = self.design_results + # C = self.baseline_purchase_costs - # Construction of concrete and stainless steel walls - C['Wall concrete'] = D['Number of clarifiers']*D['Volume of concrete wall']*self.wall_concrete_unit_cost + # # Construction of concrete and stainless steel walls + # C['Wall concrete'] = D['Number of clarifiers']*D['Volume of concrete wall']*self.wall_concrete_unit_cost - C['Slab concrete'] = D['Number of clarifiers']*D['Volume of concrete slab']*self.slab_concrete_unit_cost + # C['Slab concrete'] = D['Number of clarifiers']*D['Volume of concrete slab']*self.slab_concrete_unit_cost - C['Wall stainless steel'] = D['Number of clarifiers']*D['Stainless steel']*self.stainless_steel_unit_cost + # C['Wall stainless steel'] = D['Number of clarifiers']*D['Stainless steel']*self.stainless_steel_unit_cost - # Cost of equipment + # # Cost of equipment - # Source of scaling exponents: Process Design and Economics for Biochemical Conversion of Lignocellulosic Biomass to Ethanol by NREL. + # # Source of scaling exponents: Process Design and Economics for Biochemical Conversion of Lignocellulosic Biomass to Ethanol by NREL. - # Scraper - # Source: https://www.alibaba.com/product-detail/Peripheral-driving-clarifier-mud-scraper-waste_1600891102019.html?spm=a2700.details.0.0.47ab45a4TP0DLb - # base_cost_scraper = 2500 - # base_flow_scraper = 1 # in m3/hr (!!! Need to know whether this is for solids or influent !!!) - clarifier_flow = D['Volumetric flow']/24 # in m3/hr - # C['Scraper'] = D['Number of clarifiers']*base_cost_scraper*(clarifier_flow/base_flow_scraper)**0.6 - # base_power_scraper = 2.75 # in kW - # THE EQUATION BELOW IS NOT CORRECT TO SCALE SCRAPER POWER REQUIREMENTS - # scraper_power = D['Number of clarifiers']*base_power_scraper*(clarifier_flow/base_flow_scraper)**0.6 + # # Scraper + # # Source: https://www.alibaba.com/product-detail/Peripheral-driving-clarifier-mud-scraper-waste_1600891102019.html?spm=a2700.details.0.0.47ab45a4TP0DLb + # # base_cost_scraper = 2500 + # # base_flow_scraper = 1 # in m3/hr (!!! Need to know whether this is for solids or influent !!!) + # clarifier_flow = D['Volumetric flow']/24 # in m3/hr + # # C['Scraper'] = D['Number of clarifiers']*base_cost_scraper*(clarifier_flow/base_flow_scraper)**0.6 + # # base_power_scraper = 2.75 # in kW + # # THE EQUATION BELOW IS NOT CORRECT TO SCALE SCRAPER POWER REQUIREMENTS + # # scraper_power = D['Number of clarifiers']*base_power_scraper*(clarifier_flow/base_flow_scraper)**0.6 - # v notch weir - # Source: https://www.alibaba.com/product-detail/50mm-Tube-Settler-Media-Modules-Inclined_1600835845218.html?spm=a2700.galleryofferlist.normal_offer.d_title.69135ff6o4kFPb - base_cost_v_notch_weir = 6888 - base_flow_v_notch_weir = 10 # in m3/hr - C['v notch weir'] = D['Number of clarifiers']*base_cost_v_notch_weir*(clarifier_flow/base_flow_v_notch_weir)**0.6 + # # v notch weir + # # Source: https://www.alibaba.com/product-detail/50mm-Tube-Settler-Media-Modules-Inclined_1600835845218.html?spm=a2700.galleryofferlist.normal_offer.d_title.69135ff6o4kFPb + # base_cost_v_notch_weir = 6888 + # base_flow_v_notch_weir = 10 # in m3/hr + # C['v notch weir'] = D['Number of clarifiers']*base_cost_v_notch_weir*(clarifier_flow/base_flow_v_notch_weir)**0.6 - # Pump (construction and maintainance) - pumps = self.pumps - add_OPEX = self.add_OPEX - pump_cost = 0. - building_cost = 0. - opex_o = 0. - opex_m = 0. + # # Pump (construction and maintainance) + # pumps = self.pumps + # add_OPEX = self.add_OPEX + # pump_cost = 0. + # building_cost = 0. + # opex_o = 0. + # opex_m = 0. - # i would be 0 and 1 for RAS and WAS respectively - for i in pumps: - p = getattr(self, f'{i}_pump') - p_cost = p.baseline_purchase_costs - p_add_opex = p.add_OPEX - pump_cost += p_cost['Pump'] - building_cost += p_cost['Pump building'] - opex_o += p_add_opex['Pump operating'] - opex_m += p_add_opex['Pump maintenance'] + # # i would be 0 and 1 for RAS and WAS respectively + # for i in pumps: + # p = getattr(self, f'{i}_pump') + # p_cost = p.baseline_purchase_costs + # p_add_opex = p.add_OPEX + # pump_cost += p_cost['Pump'] + # building_cost += p_cost['Pump building'] + # opex_o += p_add_opex['Pump operating'] + # opex_m += p_add_opex['Pump maintenance'] - # All costs associated with pumping need to be multiplied by number of clarifiers - C['Pumps'] = pump_cost*D['Number of clarifiers'] - C['Pump building'] = building_cost*D['Number of clarifiers'] - add_OPEX['Pump operating'] = opex_o*D['Number of clarifiers'] - add_OPEX['Pump maintenance'] = opex_m*D['Number of clarifiers'] + # # All costs associated with pumping need to be multiplied by number of clarifiers + # C['Pumps'] = pump_cost*D['Number of clarifiers'] + # C['Pump building'] = building_cost*D['Number of clarifiers'] + # add_OPEX['Pump operating'] = opex_o*D['Number of clarifiers'] + # add_OPEX['Pump maintenance'] = opex_m*D['Number of clarifiers'] - # Power - pumping = 0. - for ID in self.pumps: - p = getattr(self, f'{ID}_pump') - if p is None: - continue - pumping += p.power_utility.rate + # # Power + # pumping = 0. + # for ID in self.pumps: + # p = getattr(self, f'{ID}_pump') + # if p is None: + # continue + # pumping += p.power_utility.rate - pumping = pumping*D['Number of clarifiers'] + # pumping = pumping*D['Number of clarifiers'] - self.power_utility.rate += pumping - # self.power_utility.consumption += scraper_power + # self.power_utility.rate += pumping + # # self.power_utility.consumption += scraper_power # %% class IdealClarifier(SanUnit): """ Ideal clarifier that settles suspended solids by specified efficiency. Has - no design or costing algorithm. + no design or costing algorithm. Governing equations are + + .. math:: + Q_i X_i = Q_e X_e + Q_s X_s + + Q_i = Q_e + Q_s + + X_e = X_i * (1-e_rmv) + + where subscripts 'i', 'e', 's' represent influent, overflow effluent, and + underflow sludge, respectively. 'Q' indicates volumetric flowrate and 'X' + indicates suspended solids concentration. Parameters ---------- sludge_flow_rate : float, optional Underflow sludge flowrate [m3/d]. The default is 2000. solids_removal_efficiency : float, optional - Removal efficiency of suspended solids, unitless. The default is 0.995. + Removal efficiency (concentration basis) of suspended solids, unitless. + The default is 0.995. sludge_MLSS : float, optional Underflow MLSS [mg/L]. Used only when either `solids_removal_efficiency` or `sludge_flow_rate` is unspecified. The default is None. @@ -862,100 +874,108 @@ def sludge_MLSS(self, MLSS): f'one of them is unspecified.') self._MLSS = MLSS - - def _calc_Qs(self, TSS_in=None, Q_in=None): - if Q_in is None: Q_in = self._mixed.get_total_flow('m3/d') - if TSS_in is None: TSS_in = self._mixed.get_TSS() - return Q_in*TSS_in*self._e_rmv/(self._MLSS-TSS_in) - - def _calc_ermv(self, TSS_in=None, Q_in=None): - if Q_in is None: Q_in = self._mixed.get_total_flow('m3/d') - if TSS_in is None: TSS_in = self._mixed.get_TSS() - return self._Qs*(self._MLSS-TSS_in)/TSS_in/(Q_in-self._Qs) - - def _calc_SS(self, SS_in=None, Q_in=None): - if Q_in is None: Q_in = self._mixed.get_total_flow('m3/d') - if SS_in is None: SS_in = self._mixed.get_TSS() - SS_e = (1-self._e_rmv)*SS_in - Qs = self._Qs - Qe = Q_in - Qs - return SS_e, (Q_in*SS_in - Qe*SS_e)/Qs + # def _calc_Qs(self, TSS_in=None, Q_in=None): + # if Q_in is None: Q_in = self._mixed.get_total_flow('m3/d') + # if TSS_in is None: TSS_in = self._mixed.get_TSS() + # # return Q_in*TSS_in*self._e_rmv/(self._MLSS-TSS_in) + # er = self._e_rmv + # return er * TSS_in/(self._MLSS - (1-er)*TSS_in) * Q_in + + # def _calc_ermv(self, TSS_in=None, Q_in=None): + # if Q_in is None: Q_in = self._mixed.get_total_flow('m3/d') + # if TSS_in is None: TSS_in = self._mixed.get_TSS() + # # return self._Qs*(self._MLSS-TSS_in)/TSS_in/(Q_in-self._Qs) + # Q_e = Q_in - self._Qs + # TSS_e = (Q_in * TSS_in - self._Qs * self._MLSS) / Q_e + # return 1 - TSS_e / TSS_in + + # def _calc_SS(self, SS_in=None, Q_in=None): + # if Q_in is None: Q_in = self._mixed.get_total_flow('m3/d') + # if SS_in is None: SS_in = self._mixed.get_TSS() + # SS_e = (1-self._e_rmv)*SS_in + # Qs = self._Qs + # Qe = Q_in - Qs + # return SS_e, (Q_in*SS_in - Qe*SS_e)/Qs def _run(self): inf = self._mixed inf.mix_from(self.ins) of, uf = self.outs - Qs, e_rmv = self._Qs, self._e_rmv TSS_in = inf.get_TSS() - Q_in = inf.F_vol * 24 # m3/d - if Qs is None: Qs = self._calc_Qs(TSS_in, Q_in) - if e_rmv is None: e_rmv = self._calc_ermv(TSS_in, Q_in) - f_Qu = Qs/Q_in - x = inf.components.x - split_to_uf = (1-x)*f_Qu + x*e_rmv - if any(split_to_uf > 1): split_to_uf = 1 - inf.split_to(uf, of, split_to_uf) + if TSS_in <= 0: + uf.empty() + of.copy_like(inf) + else: + Q_in = inf.F_vol * 24 # m3/d + x = inf.components.x + Qs, e_rmv, mlss = self._Qs, self._e_rmv, self._MLSS + if Qs and e_rmv: + f_Qu = Qs/Q_in + f_Xu = e_rmv + (1-e_rmv) * f_Qu + elif Qs and mlss: + f_Qu = Qs/Q_in + f_Xu = f_Qu*mlss/TSS_in + elif e_rmv and mlss: + f_Qu = e_rmv / (mlss/TSS_in - (1-e_rmv)) + f_Xu = e_rmv + (1-e_rmv) * f_Qu + split_to_uf = (1-x)*f_Qu + x*f_Xu + if any(split_to_uf > 1): split_to_uf = 1 + inf.split_to(uf, of, split_to_uf) def _init_state(self): inf = self._mixed C_in = inf.conc - TSS_in = inf.get_TSS() Q_in = inf.F_vol * 24 self._state = np.append(C_in, Q_in) self._dstate = self._state * 0. - if not self._Qs: self._Qs = self._calc_Qs(TSS_in, Q_in) - if not self._e_rmv: self._e_rmv = self._calc_ermv(TSS_in, Q_in) def _update_state(self): arr = self._state Cs = arr[:-1] Qi = arr[-1] - Qs = self._Qs - e_rmv = self._e_rmv + Qs, e_rmv, mlss = self._Qs, self._e_rmv, self._MLSS x = self.components.x + i_tss = x * self.components.i_mass of, uf = self.outs if uf.state is None: uf.state = np.zeros(len(x)+1) if of.state is None: of.state = np.zeros(len(x)+1) + if Qs: + Qe = Qi - Qs + if e_rmv: + fuf = e_rmv * Qi/Qs + (1-e_rmv) + fof = 1-e_rmv + elif mlss: + tss_in = sum(Cs * i_tss) + tss_e = (Qi * tss_in - Qs * mlss)/Qe + fuf = mlss/tss_in + fof = tss_e/tss_in + elif e_rmv and mlss: + tss_in = sum(Cs * i_tss) + Qs = Qi * e_rmv / (mlss/tss_in - (1-e_rmv)) + Qe = Qi - Qs + fuf = mlss/tss_in + fof = 1-e_rmv + else: + raise RuntimeError('missing parameter') + if Qs >= Qi: uf.state[:] = arr of.state[:] = 0. - elif Qs <= 0: - uf.state[:] = 0. - of.state[:] = arr else: - self._f_uf = fuf = e_rmv*Qi/Qs - self._f_of = fof = (1-e_rmv)/(1-Qs/Qi) + self._f_uf = fuf + self._f_of = fof uf.state[:-1] = Cs * ((1-x) + x*fuf) uf.state[-1] = Qs of.state[:-1] = Cs * ((1-x) + x*fof) - of.state[-1] = Qi - Qs + of.state[-1] = Qe def _update_dstate(self): - arr = self._dstate - dCs = arr[:-1] - dQi = arr[-1] - Cs = self._state[:-1] - Qi = self._state[-1] - Qs = self._Qs - e_rmv = self._e_rmv - x = self.components.x - of, uf = self.outs + x = self.components.x if uf.dstate is None: uf.dstate = np.zeros(len(x)+1) if of.dstate is None: of.dstate = np.zeros(len(x)+1) - if Qs >= Qi: - uf.dstate[:] = arr - of.dstate[:] = 0. - elif Qs <= 0: - uf.dstate[:] = 0. - of.dstate[:] = arr - else: - uf.dstate[:-1] = dCs * ((1-x) + x*self._f_uf) + Cs*x*e_rmv*dQi/Qs - uf.dstate[-1] = 0. - of.dstate[:-1] = dCs * ((1-x) + x*self._f_of) - Cs*x*(1-e_rmv)*Qs/(Qi-Qs)**2 * dQi - of.dstate[-1] = dQi @property def AE(self): @@ -965,24 +985,24 @@ def AE(self): def _compile_AE(self): _state = self._state - _dstate = self._dstate + # _dstate = self._dstate _update_state = self._update_state - _update_dstate = self._update_dstate + # _update_dstate = self._update_dstate def yt(t, QC_ins, dQC_ins): Q_ins = QC_ins[:, -1] C_ins = QC_ins[:, :-1] - dQ_ins = dQC_ins[:, -1] - dC_ins = dQC_ins[:, :-1] + # dQ_ins = dQC_ins[:, -1] + # dC_ins = dQC_ins[:, :-1] Q = Q_ins.sum() C = Q_ins @ C_ins / Q _state[-1] = Q _state[:-1] = C - Q_dot = dQ_ins.sum() - C_dot = (dQ_ins @ C_ins + Q_ins @ dC_ins - Q_dot * C)/Q - _dstate[-1] = Q_dot - _dstate[:-1] = C_dot + # Q_dot = dQ_ins.sum() + # C_dot = (dQ_ins @ C_ins + Q_ins @ dC_ins - Q_dot * C)/Q + # _dstate[-1] = Q_dot + # _dstate[:-1] = C_dot _update_state() - _update_dstate() + # _update_dstate() self._AE = yt @@ -1000,7 +1020,7 @@ def calc_f_i(fx, f_corr, HRT): class PrimaryClarifierBSM2(SanUnit): """ - A Primary clarifier based on BSM2 Layout. [1] + A Primary clarifier based on the Otterpohl model [1] in BSM2 [2]. Parameters ---------- @@ -1014,19 +1034,12 @@ class PrimaryClarifierBSM2(SanUnit): Clarifier volume, in m^3. The default is 900. ratio_uf : float The volumetric ratio of sludge to primary influent. The default is 0.007, - based on IWA report.[1] + based on IWA report.[2] mean_f_x : float, optional The average fraction of particulate COD out of total COD in primary influent. The default is 0.85. f_corr : float - Dimensionless correction factor for removal efficiency in the primary clarifier.[1] - cylindrical_depth : float, optional - The depth of the cylindrical portion of clarifier [in m]. - upflow_velocity : float, optional - Speed with which influent enters the center feed of the clarifier [m/hr]. - The default is 43.2. - F_BM : dict - Equipment bare modules. + Dimensionless correction factor for removal efficiency in the primary clarifier.[2] Examples -------- @@ -1098,33 +1111,24 @@ class PrimaryClarifierBSM2(SanUnit): References ---------- - [1] Gernaey, Krist V., Ulf Jeppsson, Peter A. Vanrolleghem, and John B. Copp. - Benchmarking of control strategies for wastewater treatment plants. IWA publishing, 2014. - [2] Metcalf, Leonard, Harrison P. Eddy, and Georg Tchobanoglous. Wastewater - engineering: treatment, disposal, and reuse. Vol. 4. New York: McGraw-Hill, 1991. - [3] Otterpohl R. and Freund M. (1992). Dynamic Models for clarifiers of activated sludge + [1] Otterpohl R. and Freund M. (1992). Dynamic Models for clarifiers of activated sludge plants with dry and wet weather flows. Water Sci. Technol., 26(5-6), 1391-1400. + [2] Gernaey, Krist V., Ulf Jeppsson, Peter A. Vanrolleghem, and John B. Copp. + Benchmarking of control strategies for wastewater treatment plants. IWA publishing, 2014. """ _N_ins = 3 _N_outs = 2 # [0] effluent; [1] underflow _ins_size_is_fixed = False - # Costs - wall_concrete_unit_cost = 650 / 0.765 # $/m3, 0.765 is to convert from $/yd3 - stainless_steel_unit_cost=1.8 # $/kg (Taken from Joy's METAB code) https://www.alibaba.com/product-detail/brushed-stainless-steel-plate-304l-stainless_1600391656401.html?spm=a2700.details.0.0.230e67e6IKwwFd - - pumps = ('sludge',) t_m = 0.125 # Smoothing time constant for qm calculation def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=True, init_with='WasteStream', - volume=900, ratio_uf=0.007, mean_f_x=0.85, - f_corr=0.65, cylindrical_depth = 5, upflow_velocity=43.2, + volume=900, ratio_uf=0.007, mean_f_x=0.85, f_corr=0.65, F_BM=default_F_BM, **kwargs): SanUnit.__init__(self, ID, ins, outs, thermo, isdynamic=isdynamic, init_with=init_with) - # self.HRT = HRT # in days self._mixed = self.ins[0].copy(f'{ID}_mixed') self._sludge = np.ones(len(self.components)+1) self._effluent = np.ones(len(self.components)+1) @@ -1132,8 +1136,6 @@ def __init__(self, ID='', ins=None, outs=(), thermo=None, self.ratio_uf = ratio_uf self.f_x = mean_f_x self.f_corr = f_corr - self.cylindrical_depth = cylindrical_depth # in m - self.upflow_velocity = upflow_velocity # in m/hr (converted from 12 mm/sec) self.F_BM.update(default_F_BM) self._concs = None @@ -1266,17 +1268,17 @@ class PrimaryClarifier(IdealClarifier): >>> cmps_test = cmps.subgroup(['S_F', 'S_NH4', 'X_OHO', 'H2O']) >>> set_thermo(cmps_test) >>> ws = WasteStream('ws', S_F = 10, S_NH4 = 20, X_OHO = 15, H2O=1000) - >>> from qsdsan.sanunits import PrimaryClarifier, CSTR - >>> M1 = CSTR('mixer', ins=ws, outs='out', aeration=None) - >>> PC = PrimaryClarifier(ID='IC', ins=M1-0, outs=('effluent', 'sludge'), + >>> from qsdsan.sanunits import PrimaryClarifier + >>> PC = PrimaryClarifier(ID='PC', ins=ws, outs=('effluent', 'sludge'), ... solids_removal_efficiency=0.6, - ... sludge_flow_rate=ws.F_vol*24*0.3) - >>> sys = System('sys', path=(M1, PC)) + ... sludge_flow_rate=ws.F_vol*24*0.3, + ... isdynamic=True) + >>> sys = System('sys', path=(PC,)) >>> sys.simulate(t_span=(0,10), method='BDF') # doctest: +ELLIPSIS >>> PC.show() # doctest: +ELLIPSIS - PrimaryClarifier: IC + PrimaryClarifier: PC ins... - [0] out from CSTR-mixer + [0] ws phase: 'l', T: 298.15 K, P: 101325 Pa flow (g/hr): S_F 1e+04 S_NH4 2e+04 @@ -1298,34 +1300,34 @@ class PrimaryClarifier(IdealClarifier): phase: 'l', T: 298.15 K, P: 101325 Pa flow (g/hr): S_F 7e+03 S_NH4 1.4e+04 - X_OHO 6e+03 - H2O 7.03e+05 + X_OHO 4.2e+03 + H2O 7.04e+05 WasteStream-specific properties: pH : 7.0 - COD : 17734.2 mg/L - BOD : 11484.7 mg/L - TC : 6051.5 mg/L - TOC : 6051.5 mg/L - TN : 19943.9 mg/L - TP : 251.0 mg/L - TK : 39.0 mg/L - TSS : 6356.8 mg/L + COD : 15278.7 mg/L + BOD : 10093.4 mg/L + TC : 5152.8 mg/L + TOC : 5152.8 mg/L + TN : 19776.2 mg/L + TP : 204.4 mg/L + TK : 27.3 mg/L + TSS : 4449.8 mg/L [1] sludge phase: 'l', T: 298.15 K, P: 101325 Pa flow (g/hr): S_F 3e+03 S_NH4 6e+03 - X_OHO 9e+03 - H2O 2.97e+05 + X_OHO 1.08e+04 + H2O 2.96e+05 WasteStream-specific properties: pH : 7.0 - COD : 38196.8 mg/L - BOD : 23079.7 mg/L - TC : 13540.8 mg/L - TOC : 13540.8 mg/L - TN : 21341.5 mg/L - TP : 639.8 mg/L - TK : 136.6 mg/L - TSS : 22248.9 mg/L + COD : 43926.4 mg/L + BOD : 26326.2 mg/L + TC : 15637.8 mg/L + TOC : 15637.8 mg/L + TN : 21732.9 mg/L + TP : 748.7 mg/L + TK : 163.9 mg/L + TSS : 26698.6 mg/L References ---------- @@ -1341,12 +1343,12 @@ class PrimaryClarifier(IdealClarifier): _ins_size_is_fixed = False _outs_size_is_fixed = True - # Costs - wall_concrete_unit_cost = 1081.73 # $/m3 (Hydromantis. CapdetWorks 4.0. https://www.hydromantis.com/CapdetWorks.html) - slab_concrete_unit_cost = 582.48 # $/m3 (Hydromantis. CapdetWorks 4.0. https://www.hydromantis.com/CapdetWorks.html) - stainless_steel_unit_cost=1.8 # Alibaba. Brushed Stainless Steel Plate 304. https://www.alibaba.com/product-detail/brushed-stainless-steel-plate-304l-stainless_1600391656401.html?spm=a2700.details.0.0.230e67e6IKwwFd + # # Costs + # wall_concrete_unit_cost = 1081.73 # $/m3 (Hydromantis. CapdetWorks 4.0. https://www.hydromantis.com/CapdetWorks.html) + # slab_concrete_unit_cost = 582.48 # $/m3 (Hydromantis. CapdetWorks 4.0. https://www.hydromantis.com/CapdetWorks.html) + # stainless_steel_unit_cost=1.8 # Alibaba. Brushed Stainless Steel Plate 304. https://www.alibaba.com/product-detail/brushed-stainless-steel-plate-304l-stainless_1600391656401.html?spm=a2700.details.0.0.230e67e6IKwwFd - pumps = ('sludge',) + # pumps = ('sludge',) def __init__(self, ID='', ins=None, outs=(), sludge_flow_rate=2000, solids_removal_efficiency=0.6, @@ -1356,7 +1358,6 @@ def __init__(self, ID='', ins=None, outs=(), super().__init__(ID, ins, outs, thermo, sludge_flow_rate=sludge_flow_rate, solids_removal_efficiency=solids_removal_efficiency, - # thermo=thermo, isdynamic=isdynamic, init_with=init_with) @@ -1378,241 +1379,241 @@ def __init__(self, ID='', ins=None, outs=(), # else: # raise ValueError('solids_loading_rate of the clarifier expected from user') - def _design_pump(self): - ID, pumps = self.ID, self.pumps - sludge = self._sludge - sludge.copy_like(self.outs[1]) + # def _design_pump(self): + # ID, pumps = self.ID, self.pumps + # sludge = self._sludge + # sludge.copy_like(self.outs[1]) - ins_dct = { - 'sludge': sludge, - } + # ins_dct = { + # 'sludge': sludge, + # } - type_dct = dict.fromkeys(pumps, 'sludge') - inputs_dct = dict.fromkeys(pumps, (1,)) + # type_dct = dict.fromkeys(pumps, 'sludge') + # inputs_dct = dict.fromkeys(pumps, (1,)) - D = self.design_results - influent_Q = sludge.get_total_flow('m3/hr')/D['Number of clarifiers'] - influent_Q_mgd = influent_Q*0.00634 # m3/hr to MGD + # D = self.design_results + # influent_Q = sludge.get_total_flow('m3/hr')/D['Number of clarifiers'] + # influent_Q_mgd = influent_Q*0.00634 # m3/hr to MGD - for i in pumps: - if hasattr(self, f'{i}_pump'): - p = getattr(self, f'{i}_pump') - setattr(p, 'add_inputs', inputs_dct[i]) - else: - ID = f'{ID}_{i}' - capacity_factor=1 - pump = WWTpump( - ID=ID, ins= ins_dct[i], thermo = self.thermo, pump_type=type_dct[i], - Q_mgd=influent_Q_mgd, add_inputs=inputs_dct[i], - capacity_factor=capacity_factor, - include_pump_cost=True, - include_building_cost=False, - include_OM_cost=True, - ) + # for i in pumps: + # if hasattr(self, f'{i}_pump'): + # p = getattr(self, f'{i}_pump') + # setattr(p, 'add_inputs', inputs_dct[i]) + # else: + # ID = f'{ID}_{i}' + # capacity_factor=1 + # pump = WWTpump( + # ID=ID, ins= ins_dct[i], thermo = self.thermo, pump_type=type_dct[i], + # Q_mgd=influent_Q_mgd, add_inputs=inputs_dct[i], + # capacity_factor=capacity_factor, + # include_pump_cost=True, + # include_building_cost=False, + # include_OM_cost=True, + # ) - setattr(self, f'{i}_pump', pump) - - pipe_ss, pump_ss = 0., 0. - for i in pumps: - p = getattr(self, f'{i}_pump') - p.simulate() - p_design = p.design_results - pipe_ss += p_design['Pump pipe stainless steel'] - pump_ss += p_design['Pump stainless steel'] - return pipe_ss, pump_ss + # setattr(self, f'{i}_pump', pump) + + # pipe_ss, pump_ss = 0., 0. + # for i in pumps: + # p = getattr(self, f'{i}_pump') + # p.simulate() + # p_design = p.design_results + # pipe_ss += p_design['Pump pipe stainless steel'] + # pump_ss += p_design['Pump stainless steel'] + # return pipe_ss, pump_ss - _units = { - 'Number of clarifiers': 'ea', - 'SOR': 'm3/day/m2', - 'Volumetric flow': 'm3/day', - 'Surface area': 'm2', - 'Cylindrical diameter': 'm', - 'Conical radius': 'm', - 'Conical depth': 'm', - 'Clarifier depth': 'm', - 'Cylindrical depth': 'm', - 'Cylindrical volume': 'm3', - 'Conical volume': 'm3', - 'Volume': 'm3', - 'Hydraulic Retention Time': 'hr', - 'Center feed depth': 'm', - 'Downward flow velocity': 'm/hr', - 'Center feed diameter': 'm', - 'Volume of concrete wall': 'm3', - 'Volume of concrete slab': 'm3', - 'Stainless steel': 'kg', - 'Pump pipe stainless steel' : 'kg', - 'Pump stainless steel': 'kg', - 'Number of pumps': 'ea' - } + # _units = { + # 'Number of clarifiers': 'ea', + # 'SOR': 'm3/day/m2', + # 'Volumetric flow': 'm3/day', + # 'Surface area': 'm2', + # 'Cylindrical diameter': 'm', + # 'Conical radius': 'm', + # 'Conical depth': 'm', + # 'Clarifier depth': 'm', + # 'Cylindrical depth': 'm', + # 'Cylindrical volume': 'm3', + # 'Conical volume': 'm3', + # 'Volume': 'm3', + # 'Hydraulic Retention Time': 'hr', + # 'Center feed depth': 'm', + # 'Downward flow velocity': 'm/hr', + # 'Center feed diameter': 'm', + # 'Volume of concrete wall': 'm3', + # 'Volume of concrete slab': 'm3', + # 'Stainless steel': 'kg', + # 'Pump pipe stainless steel' : 'kg', + # 'Pump stainless steel': 'kg', + # 'Number of pumps': 'ea' + # } - def _design(self): - - mixed = self._mixed - mixed.mix_from(self.ins) - D = self.design_results - - # Number of clarifiers based on tentative suggestions by Jeremy - # (would be verified through collaboration with industry) - total_flow = (mixed.get_total_flow('m3/hr')*24)/3785 # in MGD - if total_flow <= 3: - D['Number of clarifiers'] = 2 - elif total_flow > 3 and total_flow <= 8: - D['Number of clarifiers'] = 3 - elif total_flow > 8 and total_flow <=20: - D['Number of clarifiers'] = 4 - else: - D['Number of clarifiers'] = 4 - total_flow -= 20 - D['Number of clarifiers'] += np.ceil(total_flow/20) + # def _design(self): + + # mixed = self._mixed + # mixed.mix_from(self.ins) + # D = self.design_results + + # # Number of clarifiers based on tentative suggestions by Jeremy + # # (would be verified through collaboration with industry) + # total_flow = (mixed.get_total_flow('m3/hr')*24)/3785 # in MGD + # if total_flow <= 3: + # D['Number of clarifiers'] = 2 + # elif total_flow > 3 and total_flow <= 8: + # D['Number of clarifiers'] = 3 + # elif total_flow > 8 and total_flow <=20: + # D['Number of clarifiers'] = 4 + # else: + # D['Number of clarifiers'] = 4 + # total_flow -= 20 + # D['Number of clarifiers'] += np.ceil(total_flow/20) - D['SOR'] = self.surface_overflow_rate # in (m3/day)/m2 - D['Volumetric flow'] = (mixed.get_total_flow('m3/hr')*24)/D['Number of clarifiers'] # m3/day - D['Surface area'] = D['Volumetric flow']/D['SOR'] # in m2 - D['Cylindrical diameter'] = np.sqrt(4*D['Surface area']/np.pi) #in m - - #Check on cylindrical diameter [2, 3] - if D['Cylindrical diameter'] < 3 or D['Cylindrical diameter'] > 60: - Cylindrical_dia = D['Cylindrical diameter'] - warn(f'Cylindrical diameter = {Cylindrical_dia} is not between 3 m and 60 m') - - D['Conical radius'] = D['Cylindrical diameter']/2 - # The slope of the bottom conical floor lies between 1:10 to 1:12 [3, 4] - D['Conical depth'] = D['Conical radius']/12 - D['Clarifier depth'] = self.depth_clarifier #in m - D['Cylindrical depth'] = D['Clarifier depth'] - D['Conical depth'] - - # Check on cylindrical and conical depths - if D['Cylindrical depth'] < D['Conical depth']: - Cylindrical_depth = D['Cylindrical depth'] - Conical_depth = D['Conical depth'] - warn(f'Cylindrical depth = {Cylindrical_depth} is lower than Conical depth = {Conical_depth}') - - D['Cylindrical volume'] = np.pi*np.square(D['Cylindrical diameter']/2)*D['Cylindrical depth'] #in m3 - D['Conical volume'] = (3.14/3)*(D['Conical radius']**2)*D['Conical depth'] #in m3 - D['Volume'] = D['Cylindrical volume'] + D['Conical volume'] #in m3 - - D['Hydraulic Retention Time'] = D['Volume']/(D['Volumetric flow']/24) #in hrs - - # Check on cylinderical HRT [3] - if D['Hydraulic Retention Time'] < 1.5 or D['Hydraulic Retention Time'] > 2.5: - HRT = D['Hydraulic Retention Time'] - warn(f'HRT = {HRT} is not between 1.5 and 2.5 hrs') - - # The design here is for center feed of clarifier. - - # Depth of the center feed lies between 30-75% of sidewater depth. [3, 4] - D['Center feed depth'] = 0.5*D['Cylindrical depth'] - # Typical conventional feed wells are designed for an average downflow velocity - # of 10-13 mm/s and maximum velocity of 25-30 mm/s. [4] - peak_flow_safety_factor = 2.5 # assumed based on average and maximum velocities - D['Downward flow velocity'] = self.downward_flow_velocity*peak_flow_safety_factor # in m/hr - - Center_feed_area = (D['Volumetric flow']/24)/D['Downward flow velocity'] # in m2 - - D['Center feed diameter'] = np.sqrt(4*Center_feed_area/np.pi) - - #Sanity check: Diameter of the center feed lies between 15-25% of tank diameter [4] - #The lower limit of this check has been modified to 10% based on advised range of down flow velocity in [4]. - if D['Center feed diameter'] < 0.10*D['Cylindrical diameter'] or D['Center feed diameter'] > 0.25*D['Cylindrical diameter']: - cf_dia = D['Center feed diameter'] - tank_dia = D['Cylindrical diameter'] - warn(f'Diameter of the center feed does not lie between 15-25% of tank diameter. It is {cf_dia*100/tank_dia}% of tank diameter') - - # Amount of concrete required - D_tank = D['Cylindrical depth']*39.37 # m to inches - # Thickness of the wall concrete [m]. Default to be minimum of 1 feet with 1 inch added for every feet of depth over 12 feet. - thickness_concrete_wall = (1 + max(D_tank-12, 0)/12)*0.3048 # from feet to m - inner_diameter = D['Cylindrical diameter'] - outer_diameter = inner_diameter + 2*thickness_concrete_wall - volume_cylindercal_wall = (np.pi*D['Cylindrical depth']/4)*(outer_diameter**2 - inner_diameter**2) - D['Volume of concrete wall'] = volume_cylindercal_wall # in m3 - - # Concrete slab thickness, [ft], default to be 2 in thicker than the wall thickness. (Brian's code) - thickness_concrete_slab = thickness_concrete_wall + (2/12)*0.3048 # from inch to m - outer_diameter_cone = inner_diameter + 2*(thickness_concrete_wall + thickness_concrete_slab) - volume_conical_wall = (np.pi/(3*4))*(((D['Conical depth'] + thickness_concrete_wall + thickness_concrete_slab)*(outer_diameter_cone**2)) - (D['Conical depth']*(inner_diameter)**2)) - D['Volume of concrete slab'] = volume_conical_wall - - # Amount of metal required for center feed - thickness_metal_wall = 0.3048 # equal to 1 feet, in m (!! NEED A RELIABLE SOURCE !!) - inner_diameter_center_feed = D['Center feed diameter'] - outer_diameter_center_feed = inner_diameter_center_feed + 2*thickness_metal_wall - volume_center_feed = (3.14*D['Center feed depth']/4)*(outer_diameter_center_feed**2 - inner_diameter_center_feed**2) - density_ss = 7930 # kg/m3, 18/8 Chromium - D['Stainless steel'] = volume_center_feed*density_ss # in kg + # D['SOR'] = self.surface_overflow_rate # in (m3/day)/m2 + # D['Volumetric flow'] = (mixed.get_total_flow('m3/hr')*24)/D['Number of clarifiers'] # m3/day + # D['Surface area'] = D['Volumetric flow']/D['SOR'] # in m2 + # D['Cylindrical diameter'] = np.sqrt(4*D['Surface area']/np.pi) #in m + + # #Check on cylindrical diameter [2, 3] + # if D['Cylindrical diameter'] < 3 or D['Cylindrical diameter'] > 60: + # Cylindrical_dia = D['Cylindrical diameter'] + # warn(f'Cylindrical diameter = {Cylindrical_dia} is not between 3 m and 60 m') + + # D['Conical radius'] = D['Cylindrical diameter']/2 + # # The slope of the bottom conical floor lies between 1:10 to 1:12 [3, 4] + # D['Conical depth'] = D['Conical radius']/12 + # D['Clarifier depth'] = self.depth_clarifier #in m + # D['Cylindrical depth'] = D['Clarifier depth'] - D['Conical depth'] + + # # Check on cylindrical and conical depths + # if D['Cylindrical depth'] < D['Conical depth']: + # Cylindrical_depth = D['Cylindrical depth'] + # Conical_depth = D['Conical depth'] + # warn(f'Cylindrical depth = {Cylindrical_depth} is lower than Conical depth = {Conical_depth}') + + # D['Cylindrical volume'] = np.pi*np.square(D['Cylindrical diameter']/2)*D['Cylindrical depth'] #in m3 + # D['Conical volume'] = (3.14/3)*(D['Conical radius']**2)*D['Conical depth'] #in m3 + # D['Volume'] = D['Cylindrical volume'] + D['Conical volume'] #in m3 + + # D['Hydraulic Retention Time'] = D['Volume']/(D['Volumetric flow']/24) #in hrs + + # # Check on cylinderical HRT [3] + # if D['Hydraulic Retention Time'] < 1.5 or D['Hydraulic Retention Time'] > 2.5: + # HRT = D['Hydraulic Retention Time'] + # warn(f'HRT = {HRT} is not between 1.5 and 2.5 hrs') + + # # The design here is for center feed of clarifier. + + # # Depth of the center feed lies between 30-75% of sidewater depth. [3, 4] + # D['Center feed depth'] = 0.5*D['Cylindrical depth'] + # # Typical conventional feed wells are designed for an average downflow velocity + # # of 10-13 mm/s and maximum velocity of 25-30 mm/s. [4] + # peak_flow_safety_factor = 2.5 # assumed based on average and maximum velocities + # D['Downward flow velocity'] = self.downward_flow_velocity*peak_flow_safety_factor # in m/hr + + # Center_feed_area = (D['Volumetric flow']/24)/D['Downward flow velocity'] # in m2 + + # D['Center feed diameter'] = np.sqrt(4*Center_feed_area/np.pi) + + # #Sanity check: Diameter of the center feed lies between 15-25% of tank diameter [4] + # #The lower limit of this check has been modified to 10% based on advised range of down flow velocity in [4]. + # if D['Center feed diameter'] < 0.10*D['Cylindrical diameter'] or D['Center feed diameter'] > 0.25*D['Cylindrical diameter']: + # cf_dia = D['Center feed diameter'] + # tank_dia = D['Cylindrical diameter'] + # warn(f'Diameter of the center feed does not lie between 15-25% of tank diameter. It is {cf_dia*100/tank_dia}% of tank diameter') + + # # Amount of concrete required + # D_tank = D['Cylindrical depth']*39.37 # m to inches + # # Thickness of the wall concrete [m]. Default to be minimum of 1 feet with 1 inch added for every feet of depth over 12 feet. + # thickness_concrete_wall = (1 + max(D_tank-12, 0)/12)*0.3048 # from feet to m + # inner_diameter = D['Cylindrical diameter'] + # outer_diameter = inner_diameter + 2*thickness_concrete_wall + # volume_cylindercal_wall = (np.pi*D['Cylindrical depth']/4)*(outer_diameter**2 - inner_diameter**2) + # D['Volume of concrete wall'] = volume_cylindercal_wall # in m3 + + # # Concrete slab thickness, [ft], default to be 2 in thicker than the wall thickness. (Brian's code) + # thickness_concrete_slab = thickness_concrete_wall + (2/12)*0.3048 # from inch to m + # outer_diameter_cone = inner_diameter + 2*(thickness_concrete_wall + thickness_concrete_slab) + # volume_conical_wall = (np.pi/(3*4))*(((D['Conical depth'] + thickness_concrete_wall + thickness_concrete_slab)*(outer_diameter_cone**2)) - (D['Conical depth']*(inner_diameter)**2)) + # D['Volume of concrete slab'] = volume_conical_wall + + # # Amount of metal required for center feed + # thickness_metal_wall = 0.3048 # equal to 1 feet, in m (!! NEED A RELIABLE SOURCE !!) + # inner_diameter_center_feed = D['Center feed diameter'] + # outer_diameter_center_feed = inner_diameter_center_feed + 2*thickness_metal_wall + # volume_center_feed = (3.14*D['Center feed depth']/4)*(outer_diameter_center_feed**2 - inner_diameter_center_feed**2) + # density_ss = 7930 # kg/m3, 18/8 Chromium + # D['Stainless steel'] = volume_center_feed*density_ss # in kg - # Pumps - pipe, pumps = self._design_pump() - D['Pump pipe stainless steel'] = pipe - D['Pump stainless steel'] = pumps + # # Pumps + # pipe, pumps = self._design_pump() + # D['Pump pipe stainless steel'] = pipe + # D['Pump stainless steel'] = pumps - #For primary clarifier - D['Number of pumps'] = D['Number of clarifiers'] + # #For primary clarifier + # D['Number of pumps'] = D['Number of clarifiers'] - def _cost(self): - D = self.design_results - C = self.baseline_purchase_costs + # def _cost(self): + # D = self.design_results + # C = self.baseline_purchase_costs - # Construction of concrete and stainless steel walls - C['Wall concrete'] = D['Number of clarifiers']*D['Volume of concrete wall']*self.wall_concrete_unit_cost + # # Construction of concrete and stainless steel walls + # C['Wall concrete'] = D['Number of clarifiers']*D['Volume of concrete wall']*self.wall_concrete_unit_cost - C['Slab concrete'] = D['Number of clarifiers']*D['Volume of concrete slab']*self.slab_concrete_unit_cost + # C['Slab concrete'] = D['Number of clarifiers']*D['Volume of concrete slab']*self.slab_concrete_unit_cost - C['Wall stainless steel'] = D['Number of clarifiers']*D['Stainless steel']*self.stainless_steel_unit_cost + # C['Wall stainless steel'] = D['Number of clarifiers']*D['Stainless steel']*self.stainless_steel_unit_cost - # Cost of equipment + # # Cost of equipment - # Source of scaling exponents: Process Design and Economics for Biochemical Conversion of Lignocellulosic Biomass to Ethanol by NREL. + # # Source of scaling exponents: Process Design and Economics for Biochemical Conversion of Lignocellulosic Biomass to Ethanol by NREL. - # Scraper - # Source: https://www.alibaba.com/product-detail/Peripheral-driving-clarifier-mud-scraper-waste_1600891102019.html?spm=a2700.details.0.0.47ab45a4TP0DLb - # base_cost_scraper = 2500 - # base_flow_scraper = 1 # in m3/hr (!!! Need to know whether this is for solids or influent !!!) - clarifier_flow = D['Volumetric flow']/24 + # # Scraper + # # Source: https://www.alibaba.com/product-detail/Peripheral-driving-clarifier-mud-scraper-waste_1600891102019.html?spm=a2700.details.0.0.47ab45a4TP0DLb + # # base_cost_scraper = 2500 + # # base_flow_scraper = 1 # in m3/hr (!!! Need to know whether this is for solids or influent !!!) + # clarifier_flow = D['Volumetric flow']/24 - # C['Scraper'] = D['Number of clarifiers']*base_cost_scraper*(clarifier_flow/base_flow_scraper)**0.6 + # # C['Scraper'] = D['Number of clarifiers']*base_cost_scraper*(clarifier_flow/base_flow_scraper)**0.6 - # base_power_scraper = 2.75 # in kW - # THE EQUATION BELOW IS NOT CORRECT TO SCALE SCRAPER POWER REQUIREMENTS - # scraper_power = D['Number of clarifiers']*base_power_scraper*(clarifier_flow/base_flow_scraper)**0.6 + # # base_power_scraper = 2.75 # in kW + # # THE EQUATION BELOW IS NOT CORRECT TO SCALE SCRAPER POWER REQUIREMENTS + # # scraper_power = D['Number of clarifiers']*base_power_scraper*(clarifier_flow/base_flow_scraper)**0.6 - # v notch weir - # Source: https://www.alibaba.com/product-detail/50mm-Tube-Settler-Media-Modules-Inclined_1600835845218.html?spm=a2700.galleryofferlist.normal_offer.d_title.69135ff6o4kFPb - base_cost_v_notch_weir = 6888 - base_flow_v_notch_weir = 10 # in m3/hr - C['v notch weir'] = D['Number of clarifiers']*base_cost_v_notch_weir*(clarifier_flow/base_flow_v_notch_weir)**0.6 + # # v notch weir + # # Source: https://www.alibaba.com/product-detail/50mm-Tube-Settler-Media-Modules-Inclined_1600835845218.html?spm=a2700.galleryofferlist.normal_offer.d_title.69135ff6o4kFPb + # base_cost_v_notch_weir = 6888 + # base_flow_v_notch_weir = 10 # in m3/hr + # C['v notch weir'] = D['Number of clarifiers']*base_cost_v_notch_weir*(clarifier_flow/base_flow_v_notch_weir)**0.6 - # Pump (construction and maintainance) - pumps = self.pumps - add_OPEX = self.add_OPEX - pump_cost = 0. - building_cost = 0. - opex_o = 0. - opex_m = 0. + # # Pump (construction and maintainance) + # pumps = self.pumps + # add_OPEX = self.add_OPEX + # pump_cost = 0. + # building_cost = 0. + # opex_o = 0. + # opex_m = 0. - for i in pumps: - p = getattr(self, f'{i}_pump') - p_cost = p.baseline_purchase_costs - p_add_opex = p.add_OPEX - pump_cost += p_cost['Pump'] - building_cost += p_cost['Pump building'] - opex_o += p_add_opex['Pump operating'] - opex_m += p_add_opex['Pump maintenance'] - - N = D['Number of clarifiers'] - C['Pumps'] = pump_cost*N - C['Pump building'] = building_cost*N - add_OPEX['Pump operating'] = opex_o*N - add_OPEX['Pump maintenance'] = opex_m*N + # for i in pumps: + # p = getattr(self, f'{i}_pump') + # p_cost = p.baseline_purchase_costs + # p_add_opex = p.add_OPEX + # pump_cost += p_cost['Pump'] + # building_cost += p_cost['Pump building'] + # opex_o += p_add_opex['Pump operating'] + # opex_m += p_add_opex['Pump maintenance'] + + # N = D['Number of clarifiers'] + # C['Pumps'] = pump_cost*N + # C['Pump building'] = building_cost*N + # add_OPEX['Pump operating'] = opex_o*N + # add_OPEX['Pump maintenance'] = opex_m*N - # Power - pumping = 0. - for ID in self.pumps: - p = getattr(self, f'{ID}_pump') - if p is None: - continue - pumping += p.power_utility.rate + # # Power + # pumping = 0. + # for ID in self.pumps: + # p = getattr(self, f'{ID}_pump') + # if p is None: + # continue + # pumping += p.power_utility.rate - self.power_utility.rate += pumping*N - # self.power_utility.rate += scraper_power + # self.power_utility.rate += pumping*N + # # self.power_utility.rate += scraper_power diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py index a349adbd..c2ffe82a 100644 --- a/qsdsan/sanunits/_sludge_treatment.py +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -171,12 +171,12 @@ class Thickener(SanUnit): _ins_size_is_fixed = False _outs_size_is_fixed = False - # Costs - wall_concrete_unit_cost = 1081.73 # $/m3 (Hydromantis. CapdetWorks 4.0. https://www.hydromantis.com/CapdetWorks.html) - slab_concrete_unit_cost = 582.48 # $/m3 (Hydromantis. CapdetWorks 4.0. https://www.hydromantis.com/CapdetWorks.html) - stainless_steel_unit_cost=1.8 # Alibaba. Brushed Stainless Steel Plate 304. https://www.alibaba.com/product-detail/brushed-stainless-steel-plate-304l-stainless_1600391656401.html?spm=a2700.details.0.0.230e67e6IKwwFd + # # Costs + # wall_concrete_unit_cost = 1081.73 # $/m3 (Hydromantis. CapdetWorks 4.0. https://www.hydromantis.com/CapdetWorks.html) + # slab_concrete_unit_cost = 582.48 # $/m3 (Hydromantis. CapdetWorks 4.0. https://www.hydromantis.com/CapdetWorks.html) + # stainless_steel_unit_cost=1.8 # Alibaba. Brushed Stainless Steel Plate 304. https://www.alibaba.com/product-detail/brushed-stainless-steel-plate-304l-stainless_1600391656401.html?spm=a2700.details.0.0.230e67e6IKwwFd - pumps = ('sludge',) + # pumps = ('sludge',) def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, init_with='WasteStream', F_BM_default=default_F_BM, thickener_perc=7, @@ -258,8 +258,6 @@ def Qu_factor(self): return f_Qu def _update_parameters(self): - # Thickener_factor, Thinning_factor, and Qu_factor need to be - # updated again and again. while dynamic simulations cmps = self.components TSS_in = np.sum(self._state[:-1]*cmps.i_mass*cmps.x) self._f_thick = f_thick = calc_f_thick(self._tp, TSS_in) @@ -281,18 +279,10 @@ def _run(self): mixed.split_to(uf, of, split_to_uf) def _init_state(self): - - # This function is run only once during dynamic simulations - - # Since there could be multiple influents, the state of the unit is - # obtained assuming perfect mixing Qs = self._ins_QC[:,-1] Cs = self._ins_QC[:,:-1] self._state = np.append(Qs @ Cs / Qs.sum(), Qs.sum()) self._dstate = self._state * 0. - - # To initialize the updated_thickener_factor, updated_thinning_factor - # and updated_Qu_factor for dynamic simulation self._update_parameters() def _update_state(self): @@ -315,8 +305,7 @@ def _update_state(self): # For sludge, the particulate concentrations (x) are multipled by thickener factor, and # flowrate is multiplied by Qu_factor. The soluble concentrations (1-x) remains same. uf.state[:-1] = arr[:-1] * ((1-x) + x*thickener_factor) - uf.state[-1] = arr[-1] * Qu_factor - + uf.state[-1] = arr[-1] * Qu_factor # For effluent, the particulate concentrations (x) are multipled by thinning factor, and # flowrate is multiplied by Qu_factor. The soluble concentrations (1-x) remains same. of.state[:-1] = arr[:-1] * ((1-x) + x*thinning_factor) @@ -380,247 +369,238 @@ def yt(t, QC_ins, dQC_ins): _update_dstate() self._AE = yt - def _design_pump(self): - ID, pumps = self.ID, self.pumps - self._sludge.copy_like(self.outs[0]) - sludge = self._sludge + # def _design_pump(self): + # ID, pumps = self.ID, self.pumps + # self._sludge.copy_like(self.outs[0]) + # sludge = self._sludge - ins_dct = { - 'sludge': sludge, - } + # ins_dct = { + # 'sludge': sludge, + # } - type_dct = dict.fromkeys(pumps, 'sludge') - inputs_dct = dict.fromkeys(pumps, (1,)) + # type_dct = dict.fromkeys(pumps, 'sludge') + # inputs_dct = dict.fromkeys(pumps, (1,)) - D = self.design_results - influent_Q = sludge.get_total_flow('m3/hr')/D['Number of thickeners'] - influent_Q_mgd = influent_Q*0.00634 # m3/hr to MGD + # D = self.design_results + # influent_Q = sludge.get_total_flow('m3/hr')/D['Number of thickeners'] + # influent_Q_mgd = influent_Q*0.00634 # m3/hr to MGD - for i in pumps: - if hasattr(self, f'{i}_pump'): - p = getattr(self, f'{i}_pump') - setattr(p, 'add_inputs', inputs_dct[i]) - else: - ID = f'{ID}_{i}' - capacity_factor=1 - # No. of pumps = No. of influents - pump = WWTpump( - ID=ID, ins= ins_dct[i], thermo = self.thermo, pump_type=type_dct[i], - Q_mgd=influent_Q_mgd, add_inputs=inputs_dct[i], - capacity_factor=capacity_factor, - include_pump_cost=True, - include_building_cost=False, - include_OM_cost=True, - ) - setattr(self, f'{i}_pump', pump) + # for i in pumps: + # if hasattr(self, f'{i}_pump'): + # p = getattr(self, f'{i}_pump') + # setattr(p, 'add_inputs', inputs_dct[i]) + # else: + # ID = f'{ID}_{i}' + # capacity_factor=1 + # # No. of pumps = No. of influents + # pump = WWTpump( + # ID=ID, ins= ins_dct[i], thermo = self.thermo, pump_type=type_dct[i], + # Q_mgd=influent_Q_mgd, add_inputs=inputs_dct[i], + # capacity_factor=capacity_factor, + # include_pump_cost=True, + # include_building_cost=False, + # include_OM_cost=True, + # ) + # setattr(self, f'{i}_pump', pump) - pipe_ss, pump_ss = 0., 0. - for i in pumps: - p = getattr(self, f'{i}_pump') - p.simulate() - p_design = p.design_results - pipe_ss += p_design['Pump pipe stainless steel'] - pump_ss += p_design['Pump stainless steel'] - return pipe_ss, pump_ss + # pipe_ss, pump_ss = 0., 0. + # for i in pumps: + # p = getattr(self, f'{i}_pump') + # p.simulate() + # p_design = p.design_results + # pipe_ss += p_design['Pump pipe stainless steel'] + # pump_ss += p_design['Pump stainless steel'] + # return pipe_ss, pump_ss - _units = { - 'Design solids loading rate': 'kg/m2/hr', - 'Total mass of solids handled': 'kg', - 'Surface area': 'm2', - 'Thickener diameter': 'm', - 'Number of thickeners' : 'Unitless', - 'Thickener depth': 'm', - 'Conical depth': 'm', - 'Cylindrical depth': 'm', - 'Cylindrical volume': 'm3', - 'Conical volume': 'm3', - 'Thickener volume': 'm3', - - 'Center feed depth': 'm', - 'Downward flow velocity': 'm/hr', - 'Volumetric flow': 'm3/hr', - 'Center feed diameter': 'm', - 'Thickener depth': 'm', - 'Volume of concrete wall': 'm3', - 'Volume of concrete slab': 'm3', - 'Stainless steel': 'kg', - 'Pump pipe stainless steel' : 'kg', - 'Pump stainless steel': 'kg', - 'Number of pumps': 'Unitless' - } + # _units = { + # 'Design solids loading rate': 'kg/m2/hr', + # 'Total mass of solids handled': 'kg', + # 'Surface area': 'm2', + # 'Thickener diameter': 'm', + # 'Number of thickeners' : 'Unitless', + # 'Thickener depth': 'm', + # 'Conical depth': 'm', + # 'Cylindrical depth': 'm', + # 'Cylindrical volume': 'm3', + # 'Conical volume': 'm3', + # 'Thickener volume': 'm3', + + # 'Center feed depth': 'm', + # 'Downward flow velocity': 'm/hr', + # 'Volumetric flow': 'm3/hr', + # 'Center feed diameter': 'm', + # 'Thickener depth': 'm', + # 'Volume of concrete wall': 'm3', + # 'Volume of concrete slab': 'm3', + # 'Stainless steel': 'kg', + # 'Pump pipe stainless steel' : 'kg', + # 'Pump stainless steel': 'kg', + # 'Number of pumps': 'Unitless' + # } - def _design(self): - - self._mixed.mix_from(self.ins) - mixed = self._mixed - D = self.design_results - - # D['Number of thickeners'] = np.ceil(self._mixed.get_total_flow('m3/hr')/self.design_flow) - D['Design solids loading rate'] = self.solids_loading_rate # in (kg/hr)/m2 - D['Total mass of solids handled'] = (mixed.get_TSS()/1000)*mixed.get_total_flow('m3/hr') # (mg/L)*[1/1000(kg*L)/(mg*m3)](m3/hr) = (kg/hr) - - # Common gravity thickener configurations have tanks with diameter between 21-24m (MOP 8) - diameter_thickener = 24 - number_of_thickeners = 0 - while diameter_thickener >= 22: - number_of_thickeners += 1 - total_surface_area = D['Total mass of solids handled']/D['Design solids loading rate'] #m2 - surface_area_thickener = total_surface_area/number_of_thickeners - diameter_thickener = np.sqrt(4*surface_area_thickener/np.pi) + # def _design(self): + + # self._mixed.mix_from(self.ins) + # mixed = self._mixed + # D = self.design_results + + # # D['Number of thickeners'] = np.ceil(self._mixed.get_total_flow('m3/hr')/self.design_flow) + # D['Design solids loading rate'] = self.solids_loading_rate # in (kg/hr)/m2 + # D['Total mass of solids handled'] = (mixed.get_TSS()/1000)*mixed.get_total_flow('m3/hr') # (mg/L)*[1/1000(kg*L)/(mg*m3)](m3/hr) = (kg/hr) + + # # Common gravity thickener configurations have tanks with diameter between 21-24m (MOP 8) + # diameter_thickener = 24 + # number_of_thickeners = 0 + # while diameter_thickener >= 22: + # number_of_thickeners += 1 + # total_surface_area = D['Total mass of solids handled']/D['Design solids loading rate'] #m2 + # surface_area_thickener = total_surface_area/number_of_thickeners + # diameter_thickener = np.sqrt(4*surface_area_thickener/np.pi) - D['Surface area'] = surface_area_thickener #in m2 - D['Thickener diameter'] = diameter_thickener #in m - D['Number of thickeners'] = number_of_thickeners + # D['Surface area'] = surface_area_thickener #in m2 + # D['Thickener diameter'] = diameter_thickener #in m + # D['Number of thickeners'] = number_of_thickeners - # Common gravity thickener configurations have sidewater depth between 3-4m (MOP 8) - D['Thickener depth'] = self.h_thickener #in m - # The thickener tank floor generally has slope between 2:12 and 3:12 (MOP 8) - D['Conical depth'] = (2/12)*(D['Thickener diameter']/2) - D['Cylindrical depth'] = D['Thickener depth'] - D['Conical depth'] - - # Checks on depth - if D['Cylindrical depth'] < 0: - cyl_depth = D['Cylindrical depth'] - RuntimeError(f'Cylindrical depth (= {cyl_depth} ) is negative') + # # Common gravity thickener configurations have sidewater depth between 3-4m (MOP 8) + # D['Thickener depth'] = self.h_thickener #in m + # # The thickener tank floor generally has slope between 2:12 and 3:12 (MOP 8) + # D['Conical depth'] = (2/12)*(D['Thickener diameter']/2) + # D['Cylindrical depth'] = D['Thickener depth'] - D['Conical depth'] + + # # Checks on depth + # if D['Cylindrical depth'] < 0: + # cyl_depth = D['Cylindrical depth'] + # RuntimeError(f'Cylindrical depth (= {cyl_depth} ) is negative') - if D['Cylindrical depth'] < D['Conical depth']: - cyl_depth = D['Cylindrical depth'] - con_depth = D['Conical depth'] - RuntimeError(f'Cylindrical depth (= {cyl_depth} ) is lower than Conical depth (= {con_depth})') + # if D['Cylindrical depth'] < D['Conical depth']: + # cyl_depth = D['Cylindrical depth'] + # con_depth = D['Conical depth'] + # RuntimeError(f'Cylindrical depth (= {cyl_depth} ) is lower than Conical depth (= {con_depth})') - D['Cylindrical volume'] = np.pi*np.square(D['Thickener diameter']/2)*D['Cylindrical depth'] #in m3 - D['Conical volume'] = (np.pi/3)*(D['Thickener diameter']/2)**2*D['Conical depth'] - D['Thickener volume'] = D['Cylindrical volume'] + D['Conical volume'] - - #Check on SOR is pending - - # The design here is for center feed of thickener. - # Depth of the center feed lies between 30-75% of sidewater depth. [2] - D['Center feed depth'] = 0.5*D['Cylindrical depth'] - # Typical conventional feed wells are designed for an average downflow velocity - # of 10-13 mm/s and maximum velocity of 25-30 mm/s. [4] - peak_flow_safety_factor = 2.5 # assumed based on average and maximum velocities - D['Downward flow velocity'] = self.downward_flow_velocity*peak_flow_safety_factor # in m/hr - - D['Volumetric flow'] = mixed.get_total_flow('m3/hr')/D['Number of thickeners'] # m3/hr - Center_feed_area = D['Volumetric flow']/D['Downward flow velocity'] # in m2 - D['Center feed diameter'] = np.sqrt(4*Center_feed_area/np.pi) # in m + # D['Cylindrical volume'] = np.pi*np.square(D['Thickener diameter']/2)*D['Cylindrical depth'] #in m3 + # D['Conical volume'] = (np.pi/3)*(D['Thickener diameter']/2)**2*D['Conical depth'] + # D['Thickener volume'] = D['Cylindrical volume'] + D['Conical volume'] + + # #Check on SOR is pending + + # # The design here is for center feed of thickener. + # # Depth of the center feed lies between 30-75% of sidewater depth. [2] + # D['Center feed depth'] = 0.5*D['Cylindrical depth'] + # # Typical conventional feed wells are designed for an average downflow velocity + # # of 10-13 mm/s and maximum velocity of 25-30 mm/s. [4] + # peak_flow_safety_factor = 2.5 # assumed based on average and maximum velocities + # D['Downward flow velocity'] = self.downward_flow_velocity*peak_flow_safety_factor # in m/hr + + # D['Volumetric flow'] = mixed.get_total_flow('m3/hr')/D['Number of thickeners'] # m3/hr + # Center_feed_area = D['Volumetric flow']/D['Downward flow velocity'] # in m2 + # D['Center feed diameter'] = np.sqrt(4*Center_feed_area/np.pi) # in m - #Diameter of the center feed does not exceed 40% of tank diameter [2] - if D['Center feed diameter'] > 0.40*D['Thickener diameter']: - cf_dia = D['Center feed diameter'] - tank_dia = D['Thickener diameter'] - warn(f'Diameter of the center feed exceeds 40% of tank diameter. It is {cf_dia*100/tank_dia}% of tank diameter') + # #Diameter of the center feed does not exceed 40% of tank diameter [2] + # if D['Center feed diameter'] > 0.40*D['Thickener diameter']: + # cf_dia = D['Center feed diameter'] + # tank_dia = D['Thickener diameter'] + # warn(f'Diameter of the center feed exceeds 40% of tank diameter. It is {cf_dia*100/tank_dia}% of tank diameter') - # Amount of concrete required - D_tank = D['Thickener depth']*39.37 # m to inches - # Thickness of the wall concrete [m]. Default to be minimum of 1 feet with 1 inch added for every feet of depth over 12 feet. - thickness_concrete_wall = (1 + max(D_tank-12, 0)/12)*0.3048 # from feet to m - inner_diameter = D['Thickener diameter'] - outer_diameter = inner_diameter + 2*thickness_concrete_wall - volume_cylindercal_wall = (np.pi*D['Cylindrical depth']/4)*(outer_diameter**2 - inner_diameter**2) - D['Volume of concrete wall'] = volume_cylindercal_wall # in m3 - - # Concrete slab thickness, [ft], default to be 2 in thicker than the wall thickness. (Brian's code) - thickness_concrete_slab = thickness_concrete_wall + (2/12)*0.3048 # from inch to m - outer_diameter_cone = inner_diameter + 2*(thickness_concrete_wall + thickness_concrete_slab) - volume_conical_wall = (np.pi/(3*4))*(((D['Conical depth'] + thickness_concrete_wall + thickness_concrete_slab)*(outer_diameter_cone**2)) - (D['Conical depth']*(inner_diameter)**2)) - D['Volume of concrete slab'] = volume_conical_wall - - # Amount of metal required for center feed - thickness_metal_wall = 0.3048 # equal to 1 feet, in m (!! NEED A RELIABLE SOURCE !!) - inner_diameter_center_feed = D['Center feed diameter'] - outer_diameter_center_feed = inner_diameter_center_feed + 2*thickness_metal_wall - volume_center_feed = (3.14*D['Center feed depth']/4)*(outer_diameter_center_feed**2 - inner_diameter_center_feed**2) - density_ss = 7930 # kg/m3, 18/8 Chromium - D['Stainless steel'] = volume_center_feed*density_ss # in kg + # # Amount of concrete required + # D_tank = D['Thickener depth']*39.37 # m to inches + # # Thickness of the wall concrete [m]. Default to be minimum of 1 feet with 1 inch added for every feet of depth over 12 feet. + # thickness_concrete_wall = (1 + max(D_tank-12, 0)/12)*0.3048 # from feet to m + # inner_diameter = D['Thickener diameter'] + # outer_diameter = inner_diameter + 2*thickness_concrete_wall + # volume_cylindercal_wall = (np.pi*D['Cylindrical depth']/4)*(outer_diameter**2 - inner_diameter**2) + # D['Volume of concrete wall'] = volume_cylindercal_wall # in m3 + + # # Concrete slab thickness, [ft], default to be 2 in thicker than the wall thickness. (Brian's code) + # thickness_concrete_slab = thickness_concrete_wall + (2/12)*0.3048 # from inch to m + # outer_diameter_cone = inner_diameter + 2*(thickness_concrete_wall + thickness_concrete_slab) + # volume_conical_wall = (np.pi/(3*4))*(((D['Conical depth'] + thickness_concrete_wall + thickness_concrete_slab)*(outer_diameter_cone**2)) - (D['Conical depth']*(inner_diameter)**2)) + # D['Volume of concrete slab'] = volume_conical_wall + + # # Amount of metal required for center feed + # thickness_metal_wall = 0.3048 # equal to 1 feet, in m (!! NEED A RELIABLE SOURCE !!) + # inner_diameter_center_feed = D['Center feed diameter'] + # outer_diameter_center_feed = inner_diameter_center_feed + 2*thickness_metal_wall + # volume_center_feed = (3.14*D['Center feed depth']/4)*(outer_diameter_center_feed**2 - inner_diameter_center_feed**2) + # density_ss = 7930 # kg/m3, 18/8 Chromium + # D['Stainless steel'] = volume_center_feed*density_ss # in kg - # Pumps - pipe, pumps = self._design_pump() - D['Pump pipe stainless steel'] = pipe - D['Pump stainless steel'] = pumps + # # Pumps + # pipe, pumps = self._design_pump() + # D['Pump pipe stainless steel'] = pipe + # D['Pump stainless steel'] = pumps - #For thickener - D['Number of pumps'] = D['Number of thickeners'] + # #For thickener + # D['Number of pumps'] = D['Number of thickeners'] - def _cost(self): + # def _cost(self): - self._mixed.mix_from(self.ins) - mixed = self._mixed + # self._mixed.mix_from(self.ins) + # mixed = self._mixed - D = self.design_results - C = self.baseline_purchase_costs + # D = self.design_results + # C = self.baseline_purchase_costs - # Construction of concrete and stainless steel walls - C['Wall concrete'] = D['Number of thickeners']*D['Volume of concrete wall']*self.wall_concrete_unit_cost - C['Slab concrete'] = D['Number of thickeners']*D['Volume of concrete slab']*self.slab_concrete_unit_cost - C['Wall stainless steel'] = D['Number of thickeners']*D['Stainless steel']*self.stainless_steel_unit_cost + # # Construction of concrete and stainless steel walls + # C['Wall concrete'] = D['Number of thickeners']*D['Volume of concrete wall']*self.wall_concrete_unit_cost + # C['Slab concrete'] = D['Number of thickeners']*D['Volume of concrete slab']*self.slab_concrete_unit_cost + # C['Wall stainless steel'] = D['Number of thickeners']*D['Stainless steel']*self.stainless_steel_unit_cost - # Cost of equipment - # Source of scaling exponents: Process Design and Economics for Biochemical Conversion of Lignocellulosic Biomass to Ethanol by NREL. - - # Scraper - # Source: https://www.alibaba.com/product-detail/Peripheral-driving-clarifier-mud-scraper-waste_1600891102019.html?spm=a2700.details.0.0.47ab45a4TP0DLb - # base_cost_scraper = 2500 - # base_flow_scraper = 1 # in m3/hr (!!! Need to know whether this is for solids or influent !!!) - thickener_flow = mixed.get_total_flow('m3/hr')/D['Number of thickeners'] - # C['Scraper'] = D['Number of thickeners']*base_cost_scraper*(thickener_flow/base_flow_scraper)**0.6 - # base_power_scraper = 2.75 # in kW - # THE EQUATION BELOW IS NOT CORRECT TO SCALE SCRAPER POWER REQUIREMENTS - # scraper_power = D['Number of thickeners']*base_power_scraper*(thickener_flow/base_flow_scraper)**0.6 - - # v notch weir - # Source: https://www.alibaba.com/product-detail/50mm-Tube-Settler-Media-Modules-Inclined_1600835845218.html?spm=a2700.galleryofferlist.normal_offer.d_title.69135ff6o4kFPb - base_cost_v_notch_weir = 6888 - base_flow_v_notch_weir = 10 # in m3/hr - C['v notch weir'] = D['Number of thickeners']*base_cost_v_notch_weir*(thickener_flow/base_flow_v_notch_weir)**0.6 + # # Cost of equipment + # # Source of scaling exponents: Process Design and Economics for Biochemical Conversion of Lignocellulosic Biomass to Ethanol by NREL. + + # # Scraper + # # Source: https://www.alibaba.com/product-detail/Peripheral-driving-clarifier-mud-scraper-waste_1600891102019.html?spm=a2700.details.0.0.47ab45a4TP0DLb + # # base_cost_scraper = 2500 + # # base_flow_scraper = 1 # in m3/hr (!!! Need to know whether this is for solids or influent !!!) + # thickener_flow = mixed.get_total_flow('m3/hr')/D['Number of thickeners'] + # # C['Scraper'] = D['Number of thickeners']*base_cost_scraper*(thickener_flow/base_flow_scraper)**0.6 + # # base_power_scraper = 2.75 # in kW + # # THE EQUATION BELOW IS NOT CORRECT TO SCALE SCRAPER POWER REQUIREMENTS + # # scraper_power = D['Number of thickeners']*base_power_scraper*(thickener_flow/base_flow_scraper)**0.6 + + # # v notch weir + # # Source: https://www.alibaba.com/product-detail/50mm-Tube-Settler-Media-Modules-Inclined_1600835845218.html?spm=a2700.galleryofferlist.normal_offer.d_title.69135ff6o4kFPb + # base_cost_v_notch_weir = 6888 + # base_flow_v_notch_weir = 10 # in m3/hr + # C['v notch weir'] = D['Number of thickeners']*base_cost_v_notch_weir*(thickener_flow/base_flow_v_notch_weir)**0.6 - # Pump (construction and maintainance) - pumps = self.pumps - add_OPEX = self.add_OPEX - pump_cost = 0. - building_cost = 0. - opex_o = 0. - opex_m = 0. + # # Pump (construction and maintainance) + # pumps = self.pumps + # add_OPEX = self.add_OPEX + # pump_cost = 0. + # building_cost = 0. + # opex_o = 0. + # opex_m = 0. - for i in pumps: - p = getattr(self, f'{i}_pump') - p_cost = p.baseline_purchase_costs - p_add_opex = p.add_OPEX - pump_cost += p_cost['Pump'] - building_cost += p_cost['Pump building'] - opex_o += p_add_opex['Pump operating'] - opex_m += p_add_opex['Pump maintenance'] + # for i in pumps: + # p = getattr(self, f'{i}_pump') + # p_cost = p.baseline_purchase_costs + # p_add_opex = p.add_OPEX + # pump_cost += p_cost['Pump'] + # building_cost += p_cost['Pump building'] + # opex_o += p_add_opex['Pump operating'] + # opex_m += p_add_opex['Pump maintenance'] - C['Pumps'] = pump_cost*D['Number of thickeners'] - C['Pump building'] = building_cost*D['Number of thickeners'] - add_OPEX['Pump operating'] = opex_o*D['Number of thickeners'] - add_OPEX['Pump maintenance'] = opex_m*D['Number of thickeners'] + # C['Pumps'] = pump_cost*D['Number of thickeners'] + # C['Pump building'] = building_cost*D['Number of thickeners'] + # add_OPEX['Pump operating'] = opex_o*D['Number of thickeners'] + # add_OPEX['Pump maintenance'] = opex_m*D['Number of thickeners'] - # Power - pumping = 0. - for ID in self.pumps: - p = getattr(self, f'{ID}_pump') - if p is None: - continue - pumping += p.power_utility.rate - - pumping = pumping*D['Number of thickeners'] - - self.power_utility.rate += pumping - # self.power_utility.rate += scraper_power + # # Power + # pumping = 0. + # for ID in self.pumps: + # p = getattr(self, f'{ID}_pump') + # if p is None: + # continue + # pumping += p.power_utility.rate + + # pumping = pumping*D['Number of thickeners'] + + # self.power_utility.rate += pumping + # # self.power_utility.rate += scraper_power #%% Centrifuge -# Asign a bare module of 1 to all - -# default_F_BM = { -# 'Bowl stainless steel': 1, -# 'Conveyor': 1, -# 'Pumps': 1, -# } -# default_F_BM.update(default_WWTpump_F_BM) - class Centrifuge(Thickener): """ @@ -867,6 +847,7 @@ def _cost(self): pumping = pumping*D['Number of pumps'] self.power_utility.rate += pumping self.power_utility.rate += total_motor_power + #%% Incinerator class Incinerator(SanUnit): From 72d98d957363a3048d4b08bef6b57351a78be274 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Mon, 26 Aug 2024 10:50:33 -0700 Subject: [PATCH 405/424] consistency in `ADM1p` --- qsdsan/processes/_adm1_p_extension.py | 2 +- qsdsan/sanunits/_anaerobic_reactor.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/qsdsan/processes/_adm1_p_extension.py b/qsdsan/processes/_adm1_p_extension.py index 846aa70e..2171641d 100644 --- a/qsdsan/processes/_adm1_p_extension.py +++ b/qsdsan/processes/_adm1_p_extension.py @@ -898,7 +898,7 @@ class ADM1p(ADM1): _precipitates = ('X_CaCO3', 'X_struv', 'X_newb', 'X_ACP', 'X_MgCO3', 'X_AlPO4', 'X_FePO4') - _biogas_IDs = ('S_h2', 'S_ch4', 'S_IC') + _biomass_IDs = (*ADM1._biomass_IDs, 'X_PAO') def __new__(cls, components=None, path=None, f_sI_xb=0, f_ch_xb=0.275, f_pr_xb=0.275, f_li_xb=0.350, diff --git a/qsdsan/sanunits/_anaerobic_reactor.py b/qsdsan/sanunits/_anaerobic_reactor.py index 3505778b..496cb061 100644 --- a/qsdsan/sanunits/_anaerobic_reactor.py +++ b/qsdsan/sanunits/_anaerobic_reactor.py @@ -588,8 +588,9 @@ def solve_h2(QC, S_in, T, h=h): # S_h2_0 = QC[h2_idx] S_h2_0 = 2.8309E-07 S_h2_in = S_in[h2_idx] - S_h2 = newton(dydt_Sh2_AD, S_h2_0, grad_dydt_Sh2_AD, - args=(QC, h, params, h2_stoichio, V_liq, S_h2_in), + S_h2 = newton( + dydt_Sh2_AD, S_h2_0, grad_dydt_Sh2_AD, + args=(QC, h, params, h2_stoichio, V_liq, S_h2_in), ) return S_h2 def update_h2_dstate(dstate): From 08f883cac5a311547c65d3672e1e9de6f22ed302 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Wed, 28 Aug 2024 14:28:35 -0700 Subject: [PATCH 406/424] consume S_F, X_S if S_A gets negative in interface --- qsdsan/sanunits/_junction.py | 57 +++++++++++++++++++++++++++++------- 1 file changed, 47 insertions(+), 10 deletions(-) diff --git a/qsdsan/sanunits/_junction.py b/qsdsan/sanunits/_junction.py index b86f8561..0de0ca99 100644 --- a/qsdsan/sanunits/_junction.py +++ b/qsdsan/sanunits/_junction.py @@ -2835,15 +2835,20 @@ def _compile_reactions(self): P_SF, P_XS, P_XB, P_SI, P_XI = cmps_asm.i_P[_asm_ids] P_aa, P_su, P_pr, P_li, P_ch = cmps_adm.i_P[_adm_ids] - S_O2_idx, S_NO3_idx = cmps_asm.indices(['S_O2', 'S_NO3']) + S_O2_idx, S_NO3_idx, S_A_idx, S_F_idx, X_S_idx =\ + cmps_asm.indices(['S_O2', 'S_NO3', 'S_A', 'S_F', 'X_S']) # f_corr = self.balance_cod_tkn_tp asm = self.asm2d_model adm = self.adm1_model - p1_stoichio = np.asarray(asm.stoichiometry.loc['hetero_growth_S_A']) - p1_stoichio /= abs(p1_stoichio[S_O2_idx]) - p2_stoichio = np.asarray(asm.stoichiometry.loc['denitri_S_A']) - p2_stoichio /= abs(p2_stoichio[S_NO3_idx]) + p1a_stoichio = np.asarray(asm.stoichiometry.loc['hetero_growth_S_A']) + p1a_stoichio /= abs(p1a_stoichio[S_O2_idx]) + p1f_stoichio = np.asarray(asm.stoichiometry.loc['hetero_growth_S_F']) + p1f_stoichio /= abs(p1f_stoichio[S_O2_idx]) + p2a_stoichio = np.asarray(asm.stoichiometry.loc['denitri_S_A']) + p2a_stoichio /= abs(p2a_stoichio[S_NO3_idx]) + p2f_stoichio = np.asarray(asm.stoichiometry.loc['denitri_S_F']) + p2f_stoichio /= abs(p2f_stoichio[S_NO3_idx]) p3_stoichio = np.array([adm.parameters[f'f_{k}_xb'] for k in ('sI', 'ch', 'pr', 'li', 'xI')]) xs_to_li = self.xs_to_li @@ -2851,14 +2856,46 @@ def _compile_reactions(self): # To convert components from ASM2d to mADM1 (asm2d-2-madm1) def masm2d2adm1p(asm_vals): _asm_vals = asm_vals.copy() + # breakpoint() - # PROCESS 1: remove S_O2 with S_A with associated X_H growth (aerobic growth of X_H on S_A) + # PROCESSES 1 & 2: remove S_O2 and S_NO3 with S_A, then S_F, X_S with associated stoichiometry O2_coddm = _asm_vals[S_O2_idx] - _asm_vals += O2_coddm * p1_stoichio # makes S_O2 = 0 - - # PROCESS 2: remove S_NO3 with S_A with associated X_H growth (denitrification on S_A) NO3_coddm = _asm_vals[S_NO3_idx] - _asm_vals += NO3_coddm * p2_stoichio # makes S_NO3 = 0 + + _asm_vals += O2_coddm * p1a_stoichio # makes S_O2 = 0 + if _asm_vals[S_A_idx] > 0: # enough S_A to comsume all S_O2 for X_H growth + _asm_vals += NO3_coddm * p2a_stoichio # makes S_NO3 = 0 + if _asm_vals[S_A_idx] < 0: # not enough S_A for complete denitrification of S_NO3 + _asm_vals -= (_asm_vals[S_A_idx] / p2a_stoichio[S_A_idx])*p2a_stoichio # make S_A = 0 + NO3_coddm = _asm_vals[S_NO3_idx] + _asm_vals += NO3_coddm * p2f_stoichio # makes S_NO3 = 0 thru X_H growth w S_F + subst_cod = _asm_vals[X_S_idx] + _asm_vals[S_F_idx] + if subst_cod < 0: # not enough S_F + X_S for complete denitrification of S_NO3 + _asm_vals -= (subst_cod / p2f_stoichio[S_F_idx])*p2f_stoichio # S_NO3 stays positive + _asm_vals[[S_F_idx, X_S_idx]] = 0 + warn('not enough S_A, S_F, X_S for complete denitrification of S_NO3') + elif _asm_vals[S_F_idx] < 0: + _asm_vals[X_S_idx] += _asm_vals[S_F_idx] + _asm_vals[S_F_idx] = 0 + else: + _asm_vals -= (_asm_vals[S_A_idx] / p1a_stoichio[S_A_idx])*p1a_stoichio # make S_A = 0 + O2_coddm = _asm_vals[S_O2_idx] + _asm_vals += O2_coddm * p1f_stoichio # makes S_O2 = 0 by consuming S_F + subst_cod = _asm_vals[X_S_idx] + _asm_vals[S_F_idx] + if subst_cod < 0: # not enough S_F + X_S for complete consumption of S_O2 + _asm_vals -= (subst_cod / p1f_stoichio[S_F_idx])*p1f_stoichio # S_O2 and S_NO3 stays positive + _asm_vals[[S_F_idx, X_S_idx]] = 0 + warn('not enough S_A, S_F, X_S for complete consumption of S_O2 and S_NO3') + else: + _asm_vals += NO3_coddm * p2f_stoichio # makes S_NO3 = 0 by consuming S_F + subst_cod = _asm_vals[X_S_idx] + _asm_vals[S_F_idx] + if subst_cod < 0: # not enough S_F + X_S for complete denitrification of S_NO3 + _asm_vals -= (subst_cod / p2f_stoichio[S_F_idx])*p2f_stoichio # S_NO3 stays positive + _asm_vals[[S_F_idx, X_S_idx]] = 0 + warn('not enough S_A, S_F, X_S for complete denitrification of S_NO3') + elif _asm_vals[S_F_idx] < 0: + _asm_vals[X_S_idx] += _asm_vals[S_F_idx] + _asm_vals[S_F_idx] = 0 S_O2, S_N2, S_NH4, S_NO3, S_PO4, S_F, S_A, S_I, S_IC, S_K, S_Mg, \ X_I, X_S, X_H, X_PAO, X_PP, X_PHA, X_AUT, S_Ca, X_CaCO3, \ From 1d599cf3e3e3aae7c930a50bf3e58a6ecbcf9710 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Wed, 28 Aug 2024 16:06:11 -0700 Subject: [PATCH 407/424] dissolve minerals in interface if S_IC/S_IN/S_IP becomes negative --- qsdsan/sanunits/_junction.py | 38 +++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/qsdsan/sanunits/_junction.py b/qsdsan/sanunits/_junction.py index 0de0ca99..689242dd 100644 --- a/qsdsan/sanunits/_junction.py +++ b/qsdsan/sanunits/_junction.py @@ -2851,6 +2851,19 @@ def _compile_reactions(self): p2f_stoichio /= abs(p2f_stoichio[S_NO3_idx]) p3_stoichio = np.array([adm.parameters[f'f_{k}_xb'] for k in ('sI', 'ch', 'pr', 'li', 'xI')]) + _mmp_idx = cmps_adm.indices(('X_CaCO3', 'X_struv', 'X_newb', 'X_ACP', 'X_MgCO3', 'X_AlPO4', 'X_FePO4')) + mmp_ic = cmps_adm.i_C[_mmp_idx] + mmp_in = cmps_adm.i_N[_mmp_idx] + mmp_ip = cmps_adm.i_P[_mmp_idx] + ic_idx, in_idx, ip_idx = cmps_adm.indices(['S_IC', 'S_IN', 'S_IP']) + cac_sto = np.asarray(adm.stoichiometry.loc['CaCO3_precipitation_dissolution']) + struv_sto = np.asarray(adm.stoichiometry.loc['struvite_precipitation_dissolution']) + newb_sto = np.asarray(adm.stoichiometry.loc['newberyite_precipitation_dissolution']) + acp_sto = np.asarray(adm.stoichiometry.loc['ACP_precipitation_dissolution']) + mgc_sto = np.asarray(adm.stoichiometry.loc['MgCO3_precipitation_dissolution']) + alp_sto = np.asarray(adm.stoichiometry.loc['AlPO4_precipitation_dissolution']) + fep_sto = np.asarray(adm.stoichiometry.loc['FePO4_precipitation_dissolution']) + xs_to_li = self.xs_to_li # To convert components from ASM2d to mADM1 (asm2d-2-madm1) @@ -2965,7 +2978,30 @@ def masm2d2adm1p(asm_vals): X_I, X_PHA, X_PP, X_PAO, S_K, S_Mg, S_Ca, X_CaCO3, X_struv, X_newb, X_ACP, X_MgCO3, X_AlOH, X_AlPO4, X_FeOH, X_FePO4, S_Na, S_Cl, H2O]) - + + # Dissolve precipitated minerals if S_IC, S_IN or S_IP becomes negative + if S_IC < 0: + xc_mmp = sum(adm_vals[_mmp_idx] * mmp_ic) + if xc_mmp > 0: + fraction_dissolve = max(0, min(1, - S_IC / xc_mmp)) + adm_vals -= fraction_dissolve * X_CaCO3 * cac_sto + adm_vals -= fraction_dissolve * X_MgCO3 * mgc_sto + if S_IN < 0: + xn_mmp = sum(adm_vals[_mmp_idx] * mmp_in) + if xn_mmp > 0: + fraction_dissolve = max(0, min(1, - S_IN / xn_mmp)) + adm_vals -= fraction_dissolve * X_struv * struv_sto + X_struv = adm_vals[_mmp_idx[0]] + if S_IP < 0: + xp_mmp = sum(adm_vals[_mmp_idx] * mmp_ip) + if xp_mmp > 0: + fraction_dissolve = max(0, min(1, - S_IP / xp_mmp)) + adm_vals -= fraction_dissolve * X_struv * struv_sto + adm_vals -= fraction_dissolve * X_newb * newb_sto + adm_vals -= fraction_dissolve * X_ACP * acp_sto + adm_vals -= fraction_dissolve * X_AlPO4 * alp_sto + adm_vals -= fraction_dissolve * X_FePO4 * fep_sto + # adm_vals = f_corr(asm_vals, adm_vals) # adm_vals = f_corr(_asm_vals, adm_vals) return adm_vals From 74630235f6be641c666d24702cbae9510c498b22 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Wed, 11 Sep 2024 11:11:16 -0700 Subject: [PATCH 408/424] dissolve minerals in interface if S_IC/S_NH4/S_PO4 < 0 --- qsdsan/sanunits/_junction.py | 39 +++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/qsdsan/sanunits/_junction.py b/qsdsan/sanunits/_junction.py index 689242dd..fa66af31 100644 --- a/qsdsan/sanunits/_junction.py +++ b/qsdsan/sanunits/_junction.py @@ -2622,11 +2622,25 @@ def _compile_reactions(self): P_su, P_aa, P_fa, P_va, P_bu, P_pro, P_ac, P_pr, P_li, P_ch = cmps_adm.i_P[_adm_ids] adm = self.adm1_model + asm = self.asm2d_model adm_p1_idx = cmps_adm.indices(('X_su', 'X_aa', 'X_fa', 'X_c4', 'X_pro', 'X_ac', 'X_h2', 'X_PAO', 'X_PP', 'X_PHA')) decay_idx = [i for i in adm.IDs if i.startswith(('decay', 'lysis'))] decay_stoichio = np.asarray(adm.stoichiometry.loc[decay_idx]) + + _mmp_idx = cmps_asm.indices(('X_CaCO3', 'X_struv', 'X_newb', 'X_ACP', 'X_MgCO3', 'X_AlPO4', 'X_FePO4')) + mmp_ic = cmps_asm.i_C[_mmp_idx] + mmp_in = cmps_asm.i_N[_mmp_idx] + mmp_ip = cmps_asm.i_P[_mmp_idx] + ic_idx, in_idx, ip_idx = cmps_asm.indices(['S_IC', 'S_NH4', 'S_PO4']) + cac_sto = np.asarray(asm.stoichiometry.loc['CaCO3_precipitation_dissolution']) + struv_sto = np.asarray(asm.stoichiometry.loc['struvite_precipitation_dissolution']) + newb_sto = np.asarray(asm.stoichiometry.loc['newberyite_precipitation_dissolution']) + acp_sto = np.asarray(asm.stoichiometry.loc['ACP_precipitation_dissolution']) + mgc_sto = np.asarray(asm.stoichiometry.loc['MgCO3_precipitation_dissolution']) + alp_sto = np.asarray(asm.stoichiometry.loc['AlPO4_precipitation_dissolution']) + fep_sto = np.asarray(asm.stoichiometry.loc['FePO4_precipitation_dissolution']) # f_corr = self.balance_cod_tkn # To convert components from ADM1p to ASM2d (A1) @@ -2678,7 +2692,30 @@ def adm1p2masm2d(adm_vals): X_I, X_S, 0,0,0,0,0, # X_H, X_PAO, X_PP, X_PHA, X_AUT, S_Ca, X_CaCO3, X_struv, X_newb, X_ACP, X_MgCO3, # directly mapped - X_AlOH, X_AlPO4, X_FeOH, X_FePO4, S_Na, S_Cl, H2O])) + X_AlOH, X_AlPO4, X_FeOH, X_FePO4, S_Na, S_Cl, H2O])) + + # Dissolve precipitated minerals if S_IC, S_IN or S_IP becomes negative + if S_IC < 0: + xc_mmp = sum(asm_vals[_mmp_idx] * mmp_ic) + if xc_mmp > 0: + fraction_dissolve = max(0, min(1, - S_IC / xc_mmp)) + asm_vals -= fraction_dissolve * X_CaCO3 * cac_sto + asm_vals -= fraction_dissolve * X_MgCO3 * mgc_sto + if S_IN < 0: + xn_mmp = sum(asm_vals[_mmp_idx] * mmp_in) + if xn_mmp > 0: + fraction_dissolve = max(0, min(1, - S_IN / xn_mmp)) + asm_vals -= fraction_dissolve * X_struv * struv_sto + X_struv = asm_vals[_mmp_idx[0]] + if S_IP < 0: + xp_mmp = sum(asm_vals[_mmp_idx] * mmp_ip) + if xp_mmp > 0: + fraction_dissolve = max(0, min(1, - S_IP / xp_mmp)) + asm_vals -= fraction_dissolve * X_struv * struv_sto + asm_vals -= fraction_dissolve * X_newb * newb_sto + asm_vals -= fraction_dissolve * X_ACP * acp_sto + asm_vals -= fraction_dissolve * X_AlPO4 * alp_sto + asm_vals -= fraction_dissolve * X_FePO4 * fep_sto # asm_vals = f_corr(adm_vals, asm_vals) return asm_vals From 785286a5315189b232933f9b3da817a6024dbd04 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Thu, 26 Sep 2024 21:59:18 -0700 Subject: [PATCH 409/424] fix typo --- qsdsan/sanunits/_clarifier.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index 2e1b93e5..36b7095f 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -870,7 +870,7 @@ def sludge_MLSS(self): def sludge_MLSS(self, MLSS): if MLSS is not None: warn(f'sludge MLSS {MLSS} mg/L is only used to estimate ' - f'sludge flowrate or solids removal efficiency, when either' + f'sludge flowrate or solids removal efficiency, when either ' f'one of them is unspecified.') self._MLSS = MLSS From 85982e3756cfd176735f77c2d53b5866c11db742 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Mon, 30 Sep 2024 16:36:16 -0700 Subject: [PATCH 410/424] clean up util functions --- qsdsan/sanunits/_clarifier.py | 29 +- qsdsan/sanunits/_sludge_treatment.py | 298 +++++----- qsdsan/utils/wwt_design.py | 784 +++++++++------------------ 3 files changed, 417 insertions(+), 694 deletions(-) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index 36b7095f..e577a07f 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -874,29 +874,6 @@ def sludge_MLSS(self, MLSS): f'one of them is unspecified.') self._MLSS = MLSS - # def _calc_Qs(self, TSS_in=None, Q_in=None): - # if Q_in is None: Q_in = self._mixed.get_total_flow('m3/d') - # if TSS_in is None: TSS_in = self._mixed.get_TSS() - # # return Q_in*TSS_in*self._e_rmv/(self._MLSS-TSS_in) - # er = self._e_rmv - # return er * TSS_in/(self._MLSS - (1-er)*TSS_in) * Q_in - - # def _calc_ermv(self, TSS_in=None, Q_in=None): - # if Q_in is None: Q_in = self._mixed.get_total_flow('m3/d') - # if TSS_in is None: TSS_in = self._mixed.get_TSS() - # # return self._Qs*(self._MLSS-TSS_in)/TSS_in/(Q_in-self._Qs) - # Q_e = Q_in - self._Qs - # TSS_e = (Q_in * TSS_in - self._Qs * self._MLSS) / Q_e - # return 1 - TSS_e / TSS_in - - # def _calc_SS(self, SS_in=None, Q_in=None): - # if Q_in is None: Q_in = self._mixed.get_total_flow('m3/d') - # if SS_in is None: SS_in = self._mixed.get_TSS() - # SS_e = (1-self._e_rmv)*SS_in - # Qs = self._Qs - # Qe = Q_in - Qs - # return SS_e, (Q_in*SS_in - Qe*SS_e)/Qs - def _run(self): inf = self._mixed inf.mix_from(self.ins) @@ -1352,12 +1329,14 @@ class PrimaryClarifier(IdealClarifier): def __init__(self, ID='', ins=None, outs=(), sludge_flow_rate=2000, solids_removal_efficiency=0.6, - thermo=None, isdynamic=False, init_with='WasteStream', - surface_overflow_rate = 41, depth_clarifier=4.5, + sludge_MLSS=None, thermo=None, isdynamic=False, + init_with='WasteStream', + surface_overflow_rate=41, depth_clarifier=4.5, downward_flow_velocity=36, F_BM=default_F_BM, **kwargs): super().__init__(ID, ins, outs, thermo, sludge_flow_rate=sludge_flow_rate, solids_removal_efficiency=solids_removal_efficiency, + sludge_MLSS=sludge_MLSS, isdynamic=isdynamic, init_with=init_with) diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py index c2ffe82a..70b6201d 100644 --- a/qsdsan/sanunits/_sludge_treatment.py +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -641,11 +641,11 @@ class Centrifuge(Thickener): References ---------- - .. [1] Gernaey, Krist V., Ulf Jeppsson, Peter A. Vanrolleghem, and John B. Copp. + [1] Gernaey, Krist V., Ulf Jeppsson, Peter A. Vanrolleghem, and John B. Copp. Benchmarking of control strategies for wastewater treatment plants. IWA publishing, 2014. [2] Metcalf, Leonard, Harrison P. Eddy, and Georg Tchobanoglous. Wastewater engineering: treatment, disposal, and reuse. Vol. 4. New York: McGraw-Hill, 1991. - [3]Design of Municipal Wastewater Treatment Plants: WEF Manual of Practice + [3] Design of Municipal Wastewater Treatment Plants: WEF Manual of Practice No. 8 ASCE Manuals and Reports on Engineering Practice No. 76, Fifth Edition. [4] https://www.alibaba.com/product-detail/Multifunctional-Sludge-Dewatering-Decanter-Centrifuge_1600285055254.html?spm=a2700.galleryofferlist.normal_offer.d_title.1cd75229sPf1UW&s=p [5] United States Environmental Protection Agency (EPA) 'Biosolids Technology Fact Sheet Centrifuge Thickening and Dewatering' @@ -659,9 +659,9 @@ class Centrifuge(Thickener): pumps = ('sludge',) - # Costs - stainless_steel_unit_cost=1.8 # $/Kg (Taken from Joy's METAB code) https://www.alibaba.com/product-detail/brushed-stainless-steel-plate-304l-stainless_1600391656401.html?spm=a2700.details.0.0.230e67e6IKwwFd - polymer_cost_by_weight = 2.2 # $/Kg (Source: https://www.alibaba.com/product-detail/dewatering-pool-chemicals-cationic-polyacrylamide-cas_1600194474507.html?spm=a2700.galleryofferlist.topad_classic.i5.5de8615c4zGAhg) + # # Costs + # stainless_steel_unit_cost=1.8 # $/Kg (Taken from Joy's METAB code) https://www.alibaba.com/product-detail/brushed-stainless-steel-plate-304l-stainless_1600391656401.html?spm=a2700.details.0.0.230e67e6IKwwFd + # polymer_cost_by_weight = 2.2 # $/Kg (Source: https://www.alibaba.com/product-detail/dewatering-pool-chemicals-cationic-polyacrylamide-cas_1600194474507.html?spm=a2700.galleryofferlist.topad_classic.i5.5de8615c4zGAhg) def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, init_with='WasteStream', F_BM_default=default_F_BM, @@ -683,170 +683,170 @@ def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, self.h_cylindrical = h_cylindrical self.h_conical = h_conical - _units = { - 'Number of centrifuges': 'ea', - 'Diameter of bowl': 'm', - 'Total length of bowl': 'm', - 'Length of cylindrical portion': 'm', - 'Length of conical portion': 'm', - 'Volume of bowl': 'm3', - 'Stainless steel for bowl': 'kg', - 'Polymer feed rate': 'kg/hr', - 'Pump pipe stainless steel' : 'kg', - 'Pump stainless steel': 'kg', - 'Number of pumps': 'ea' - } + # _units = { + # 'Number of centrifuges': 'ea', + # 'Diameter of bowl': 'm', + # 'Total length of bowl': 'm', + # 'Length of cylindrical portion': 'm', + # 'Length of conical portion': 'm', + # 'Volume of bowl': 'm3', + # 'Stainless steel for bowl': 'kg', + # 'Polymer feed rate': 'kg/hr', + # 'Pump pipe stainless steel' : 'kg', + # 'Pump stainless steel': 'kg', + # 'Number of pumps': 'ea' + # } - def _design_pump(self): - ID, pumps = self.ID, self.pumps - self._sludge.copy_like(self.outs[0]) - sludge = self._sludge - ins_dct = { - 'sludge': sludge, - } - type_dct = dict.fromkeys(pumps, 'sludge') - inputs_dct = dict.fromkeys(pumps, (1,)) - - D = self.design_results - influent_Q = sludge.get_total_flow('m3/hr')/D['Number of centrifuges'] - influent_Q_mgd = influent_Q*0.00634 # m3/hr to MGD + # def _design_pump(self): + # ID, pumps = self.ID, self.pumps + # self._sludge.copy_like(self.outs[0]) + # sludge = self._sludge + # ins_dct = { + # 'sludge': sludge, + # } + # type_dct = dict.fromkeys(pumps, 'sludge') + # inputs_dct = dict.fromkeys(pumps, (1,)) + + # D = self.design_results + # influent_Q = sludge.get_total_flow('m3/hr')/D['Number of centrifuges'] + # influent_Q_mgd = influent_Q*0.00634 # m3/hr to MGD - for i in pumps: - if hasattr(self, f'{i}_pump'): - p = getattr(self, f'{i}_pump') - setattr(p, 'add_inputs', inputs_dct[i]) - else: - ID = f'{ID}_{i}' - capacity_factor=1 - # No. of pumps = No. of influents - pump = WWTpump( - ID=ID, ins= ins_dct[i], thermo = self.thermo, pump_type=type_dct[i], - Q_mgd=influent_Q_mgd, add_inputs=inputs_dct[i], - capacity_factor=capacity_factor, - include_pump_cost=True, - include_building_cost=False, - include_OM_cost=True, - ) - setattr(self, f'{i}_pump', pump) + # for i in pumps: + # if hasattr(self, f'{i}_pump'): + # p = getattr(self, f'{i}_pump') + # setattr(p, 'add_inputs', inputs_dct[i]) + # else: + # ID = f'{ID}_{i}' + # capacity_factor=1 + # # No. of pumps = No. of influents + # pump = WWTpump( + # ID=ID, ins= ins_dct[i], thermo = self.thermo, pump_type=type_dct[i], + # Q_mgd=influent_Q_mgd, add_inputs=inputs_dct[i], + # capacity_factor=capacity_factor, + # include_pump_cost=True, + # include_building_cost=False, + # include_OM_cost=True, + # ) + # setattr(self, f'{i}_pump', pump) - pipe_ss, pump_ss = 0., 0. - for i in pumps: - p = getattr(self, f'{i}_pump') - p.simulate() - p_design = p.design_results - pipe_ss += p_design['Pump pipe stainless steel'] - pump_ss += p_design['Pump stainless steel'] - return pipe_ss, pump_ss + # pipe_ss, pump_ss = 0., 0. + # for i in pumps: + # p = getattr(self, f'{i}_pump') + # p.simulate() + # p_design = p.design_results + # pipe_ss += p_design['Pump pipe stainless steel'] + # pump_ss += p_design['Pump stainless steel'] + # return pipe_ss, pump_ss - def _design(self): - self._mixed.mix_from(self.ins) - mixed = self._mixed + # def _design(self): + # self._mixed.mix_from(self.ins) + # mixed = self._mixed - D = self.design_results - TSS_rmv = self._TSS_rmv - solids_feed_rate = 44.66*self.solids_feed_rate # 44.66 is factor to convert tonne/day to kg/hr - # Cake's total solids and TSS are essentially the same (pg. 24-6 [3]) - # If TSS_rmv = 98, then total_mass_dry_solids_removed = (0.98)*(influent TSS mass) - total_mass_dry_solids_removed = (TSS_rmv/100)*((mixed.get_TSS()*self.ins[0].F_vol)/1000) # in kg/hr - D['Number of centrifuges'] = np.ceil(total_mass_dry_solids_removed/solids_feed_rate) + # D = self.design_results + # TSS_rmv = self._TSS_rmv + # solids_feed_rate = 44.66*self.solids_feed_rate # 44.66 is factor to convert tonne/day to kg/hr + # # Cake's total solids and TSS are essentially the same (pg. 24-6 [3]) + # # If TSS_rmv = 98, then total_mass_dry_solids_removed = (0.98)*(influent TSS mass) + # total_mass_dry_solids_removed = (TSS_rmv/100)*((mixed.get_TSS()*self.ins[0].F_vol)/1000) # in kg/hr + # D['Number of centrifuges'] = np.ceil(total_mass_dry_solids_removed/solids_feed_rate) - # HAVE COMMENTED ALL OF THIS SINCE CENTRIFUGE WOULD PROBABLY BE BROUGHT NOT CONSTRUCTED AT THE FACILITY + # # HAVE COMMENTED ALL OF THIS SINCE CENTRIFUGE WOULD PROBABLY BE BROUGHT NOT CONSTRUCTED AT THE FACILITY - # k = 0.00000056 # Based on emprical formula (pg. 24-23 of [3]) - # g = 9.81 # m/s2 - # # The inner diameterof the bowl is calculated based on an empirical formula. 1000 is used to convert mm to m. - # D['Diameter of bowl'] = (self.g_factor*g)/(k*np.square(self.rotational_speed)*1000) # in m - # D['Total length of bowl'] = self.LtoD*D['Diameter of bowl'] - # # Sanity check: L should be between 1-7 m, diameter should be around 0.25-0.8 m (Source: [4]) + # # k = 0.00000056 # Based on emprical formula (pg. 24-23 of [3]) + # # g = 9.81 # m/s2 + # # # The inner diameterof the bowl is calculated based on an empirical formula. 1000 is used to convert mm to m. + # # D['Diameter of bowl'] = (self.g_factor*g)/(k*np.square(self.rotational_speed)*1000) # in m + # # D['Total length of bowl'] = self.LtoD*D['Diameter of bowl'] + # # # Sanity check: L should be between 1-7 m, diameter should be around 0.25-0.8 m (Source: [4]) - # fraction_cylindrical_portion = 0.8 - # fraction_conical_portion = 1 - fraction_cylindrical_portion - # D['Length of cylindrical portion'] = fraction_cylindrical_portion*D['Total length of bowl'] - # D['Length of conical portion'] = fraction_conical_portion*D['Total length of bowl'] - # thickness_of_bowl_wall = 0.1 # in m (!!! NEED A RELIABLE SOURCE !!!) - # inner_diameter = D['Diameter of bowl'] - # outer_diameter = inner_diameter + 2*thickness_of_bowl_wall + # # fraction_cylindrical_portion = 0.8 + # # fraction_conical_portion = 1 - fraction_cylindrical_portion + # # D['Length of cylindrical portion'] = fraction_cylindrical_portion*D['Total length of bowl'] + # # D['Length of conical portion'] = fraction_conical_portion*D['Total length of bowl'] + # # thickness_of_bowl_wall = 0.1 # in m (!!! NEED A RELIABLE SOURCE !!!) + # # inner_diameter = D['Diameter of bowl'] + # # outer_diameter = inner_diameter + 2*thickness_of_bowl_wall - # volume_cylindrical_wall = (np.pi*D['Length of cylindrical portion']/4)*(outer_diameter**2 - inner_diameter**2) - # volume_conical_wall = (np.pi/3)*(D['Length of conical portion']/4)*(outer_diameter**2 - inner_diameter**2) - # D['Volume of bowl'] = volume_cylindrical_wall + volume_conical_wall # in m3 + # # volume_cylindrical_wall = (np.pi*D['Length of cylindrical portion']/4)*(outer_diameter**2 - inner_diameter**2) + # # volume_conical_wall = (np.pi/3)*(D['Length of conical portion']/4)*(outer_diameter**2 - inner_diameter**2) + # # D['Volume of bowl'] = volume_cylindrical_wall + volume_conical_wall # in m3 - # density_ss = 7930 # kg/m3, 18/8 Chromium - # D['Stainless steel for bowl'] = D['Volume of bowl']*density_ss # in kg + # # density_ss = 7930 # kg/m3, 18/8 Chromium + # # D['Stainless steel for bowl'] = D['Volume of bowl']*density_ss # in kg - polymer_dosage_rate = 0.000453592*self.polymer_dosage # convert from (polymer (lbs)/solids (tonne)) to (polymer (kg)/solids (kg)) - D['Polymer feed rate'] = (polymer_dosage_rate*solids_feed_rate) # in polymer (kg)/hr + # polymer_dosage_rate = 0.000453592*self.polymer_dosage # convert from (polymer (lbs)/solids (tonne)) to (polymer (kg)/solids (kg)) + # D['Polymer feed rate'] = (polymer_dosage_rate*solids_feed_rate) # in polymer (kg)/hr - # Pumps - pipe, pumps = self._design_pump() - D['Pump pipe stainless steel'] = pipe - D['Pump stainless steel'] = pumps + # # Pumps + # pipe, pumps = self._design_pump() + # D['Pump pipe stainless steel'] = pipe + # D['Pump stainless steel'] = pumps - # For centrifuges - D['Number of pumps'] = D['Number of centrifuges'] + # # For centrifuges + # D['Number of pumps'] = D['Number of centrifuges'] - def _cost(self): + # def _cost(self): - D = self.design_results - C = self.baseline_purchase_costs + # D = self.design_results + # C = self.baseline_purchase_costs - self._mixed.mix_from(self.ins) - mixed = self._mixed + # self._mixed.mix_from(self.ins) + # mixed = self._mixed - # HAVE COMMENTED SINCE CENTRIFUGE WOULD PROBABLY BE BROUGHT NOT CONSTRUCTED AT THE FACILITY - # Construction of concrete and stainless steel walls - # C['Bowl stainless steel'] = D['Number of centrifuges']*D['Stainless steel for bowl']*self.stainless_steel_unit_cost - - # Conveyor - # Source: https://www.alibaba.com/product-detail/Sludge-Dewatering-Centrifuge-Decanter-Centrifuge-For_60448094522.html?spm=a2700.galleryofferlist.p_offer.d_title.1c5c5229I5pQeP&s=p - base_cost_centrifuge = 16000 - base_mass_flow_centrifuge = 80 # in tonne/hr - thickener_mass_flow = (mixed.get_total_flow('m3/hr')*mixed.get_TSS())/D['Number of centrifuges'] # IN gm/hr - gm_to_tonne = 0.000001 - thickener_mass_flow = thickener_mass_flow*gm_to_tonne - C['Centrifuge'] = D['Number of centrifuges']*base_cost_centrifuge*(thickener_mass_flow/base_mass_flow_centrifuge)**0.6 - - base_power_motor = 55 # in kW - # THIS IS NOT THE CORRECT EXPRESSION TO SCALE UP POWER OF CENTRIFUGE - motor_power = base_power_motor*(thickener_mass_flow/base_mass_flow_centrifuge) - total_motor_power = D['Number of centrifuges']*motor_power - - # Pump (construction and maintainance) - pumps = self.pumps - add_OPEX = self.add_OPEX - pump_cost = 0. - building_cost = 0. - opex_o = 0. - opex_m = 0. - - add_OPEX['Polymer'] = D['Number of centrifuges']*D['Polymer feed rate']*self.polymer_cost_by_weight + # # HAVE COMMENTED SINCE CENTRIFUGE WOULD PROBABLY BE BROUGHT NOT CONSTRUCTED AT THE FACILITY + # # Construction of concrete and stainless steel walls + # # C['Bowl stainless steel'] = D['Number of centrifuges']*D['Stainless steel for bowl']*self.stainless_steel_unit_cost + + # # Conveyor + # # Source: https://www.alibaba.com/product-detail/Sludge-Dewatering-Centrifuge-Decanter-Centrifuge-For_60448094522.html?spm=a2700.galleryofferlist.p_offer.d_title.1c5c5229I5pQeP&s=p + # base_cost_centrifuge = 16000 + # base_mass_flow_centrifuge = 80 # in tonne/hr + # thickener_mass_flow = (mixed.get_total_flow('m3/hr')*mixed.get_TSS())/D['Number of centrifuges'] # IN gm/hr + # gm_to_tonne = 0.000001 + # thickener_mass_flow = thickener_mass_flow*gm_to_tonne + # C['Centrifuge'] = D['Number of centrifuges']*base_cost_centrifuge*(thickener_mass_flow/base_mass_flow_centrifuge)**0.6 + + # base_power_motor = 55 # in kW + # # THIS IS NOT THE CORRECT EXPRESSION TO SCALE UP POWER OF CENTRIFUGE + # motor_power = base_power_motor*(thickener_mass_flow/base_mass_flow_centrifuge) + # total_motor_power = D['Number of centrifuges']*motor_power + + # # Pump (construction and maintainance) + # pumps = self.pumps + # add_OPEX = self.add_OPEX + # pump_cost = 0. + # building_cost = 0. + # opex_o = 0. + # opex_m = 0. + + # add_OPEX['Polymer'] = D['Number of centrifuges']*D['Polymer feed rate']*self.polymer_cost_by_weight - for i in pumps: - p = getattr(self, f'{i}_pump') - p_cost = p.baseline_purchase_costs - p_add_opex = p.add_OPEX - pump_cost += p_cost['Pump'] - building_cost += p_cost['Pump building'] - opex_o += p_add_opex['Pump operating'] - opex_m += p_add_opex['Pump maintenance'] + # for i in pumps: + # p = getattr(self, f'{i}_pump') + # p_cost = p.baseline_purchase_costs + # p_add_opex = p.add_OPEX + # pump_cost += p_cost['Pump'] + # building_cost += p_cost['Pump building'] + # opex_o += p_add_opex['Pump operating'] + # opex_m += p_add_opex['Pump maintenance'] - C['Pumps'] = pump_cost*D['Number of pumps'] - C['Pump building'] = building_cost*D['Number of pumps'] - add_OPEX['Pump operating'] = opex_o*D['Number of pumps'] - add_OPEX['Pump maintenance'] = opex_m*D['Number of pumps'] + # C['Pumps'] = pump_cost*D['Number of pumps'] + # C['Pump building'] = building_cost*D['Number of pumps'] + # add_OPEX['Pump operating'] = opex_o*D['Number of pumps'] + # add_OPEX['Pump maintenance'] = opex_m*D['Number of pumps'] - # Power - pumping = 0. - for ID in self.pumps: - p = getattr(self, f'{ID}_pump') - if p is None: - continue - pumping += p.power_utility.rate + # # Power + # pumping = 0. + # for ID in self.pumps: + # p = getattr(self, f'{ID}_pump') + # if p is None: + # continue + # pumping += p.power_utility.rate - pumping = pumping*D['Number of pumps'] - self.power_utility.rate += pumping - self.power_utility.rate += total_motor_power + # pumping = pumping*D['Number of pumps'] + # self.power_utility.rate += pumping + # self.power_utility.rate += total_motor_power #%% Incinerator @@ -1067,13 +1067,9 @@ def _init_state(self): # def _cost(self): # C = self.baseline_purchase_costs - # sludge, air, fuel = self.ins - # solids_load_treated = sludge.get_total_flow('m3/hr')*sludge.get_TSS('mg/L')/1000 # in kg/hr - # C = self.baseline_purchase_costs - # # Based on regression equations obtained from CapdetWorks # C['Construction and equipment costs'] = 119629*(solids_load_treated)**0.9282 # C['Installed incinerator costs'] = 114834*(solids_load_treated)**0.9284 diff --git a/qsdsan/utils/wwt_design.py b/qsdsan/utils/wwt_design.py index c7d454cf..136a505d 100644 --- a/qsdsan/utils/wwt_design.py +++ b/qsdsan/utils/wwt_design.py @@ -4,7 +4,9 @@ QSDsan: Quantitative Sustainable Design for sanitation and resource recovery systems This module is developed by: + Joy Zhang + Saumitra Rai This module is under the University of Illinois/NCSA Open Source License. @@ -37,7 +39,7 @@ def get_SRT(system, biomass_IDs, wastage=None, active_unit_IDs=None): """ - Estimate sludge residence time (SRT) of an activated sludge system. + Estimate sludge residence time (SRT) [day] of an activated sludge system. Parameters ---------- @@ -51,10 +53,6 @@ def get_SRT(system, biomass_IDs, wastage=None, active_unit_IDs=None): IDs of activated sludge units. The default is None, meaning to include all units in the system. - Returns - ------- - [float] Estimated sludge residence time in days. - .. note:: [1] This function uses component flowrates of the system's product `WasteStream` @@ -78,74 +76,51 @@ def get_SRT(system, biomass_IDs, wastage=None, active_unit_IDs=None): retain = sum([u.get_retained_mass(biomass_IDs) for u in units if u.isdynamic]) return retain/waste -# def get_oxygen_heterotrophs(system, influent=None, eff_COD_soluble = None, f_d = 0.1, b_H = 0.4, SRT = 10, Y_H = 0.625): def get_oxygen_heterotrophs(flow, influent_COD, eff_COD_soluble, f_d=0.1, b_H=0.4, SRT=10, Y_H=0.625): """ + Estimates oxygen requirement [kg-O2/day] for heterotrophic biological processes in + an activated sludge system given design and performance assumptions, + following equation 10.10 in [1]. + Parameters ---------- - # system : :class:`biosteam.System` - # The system whose aeration airflow will be calculated. flow : float Volumetric flow rate through the system [m3/d]. influent_COD : float - Influent COD concentration [mg/L]. The default is None. + Influent COD concentration [mg/L]. eff_COD_soluble : float Maximum effluent soluble COD concentration [mg/L]. f_d : float, optional - Fraction of biomass that remains as cell debris after decay. Default value is - 0.1 gCOD/gCOD based on ASM2d 'f_XI_H'. + Fraction of biomass that remains as cell debris after decay. + Default value is 0.1 gCOD/gCOD, based on ASM2d 'f_XI_H'. b_H : float, optional - Decay rate constant of heterotrophs [d^-1]. The default is 0.4 based on ASM2d. + Decay rate constant of heterotrophs [d^(-1)]. The default is 0.4, based on ASM2d. SRT : float, optional - Estimated sludge retention time of the system. Default is 10 days. + Design sludge retention time of the system. Default is 10 days. Y_H : float, optional Yield of heterotrophs [gCOD/gCOD]. The default is 0.625. - - Returns - ------- - float - Oxygen requirement for heterotrophs in kg/day. - + References ---------- - [1] Adapted from Equation 10.10 in GDLF [Grady Jr, C.P.L.; Daigger, G.T.; Love, N.G.; Filipe, C.D.M. Biological - Wastewater Treatment. 3rd ed. Boca Raton, FL: CRC Press 2011.] - - """ - # if influent is None: - # influent = [inf for inf in system.feeds if inf.phase == 'l'] - - # influent_flow = np.array([inf.F_vol*24 for inf in influent]) # in m3/day - # influent_COD = np.array([inf.COD for inf in influent]) # in mg/L - - # if eff_COD_soluble is None: - # eff_COD_soluble = np.array([eff.composite('COD', particle_size='s', unit = 'mg/L') \ - # for eff in system.products if eff.phase == 'l']) - - # mass_influent_COD = np.sum(influent_flow*influent_COD/1000) # in kg/day - # mass_effluent_COD = np.sum(influent_flow*eff_COD_soluble/1000) # in kg/day - - # mass_COD_treated = mass_influent_COD - mass_effluent_COD # kg/day - + [1] Grady Jr, C.P.L.; Daigger, G.T.; Love, N.G.; Filipe, C.D.M. Biological + Wastewater Treatment. 3rd ed. Boca Raton, FL: CRC Press 2011. + """ mass_COD_treated = flow * (influent_COD - eff_COD_soluble) * 1e-3 # kg/day aeration_factor = 1 - (1 + f_d*b_H*SRT)*Y_H/(1 + b_H*SRT) return mass_COD_treated*aeration_factor -# def get_oxygen_autotrophs(system, influent=None, eff_COD_soluble = None, f_d = 0.1, b_H = 0.4, b_AUT = 0.15, SRT = 10, -# Y_H = 0.625, Y_AUT = 0.24, K_NH = 1, U_AUT = 1, SF_DO = 1.375, ammonia_component_ID = 'S_NH4'): def get_oxygen_autotrophs(flow, influent_COD, eff_COD_soluble, influent_TKN, f_d=0.1, b_H=0.4, b_AUT=0.15, SRT=10, Y_H=0.625, Y_AUT=0.24, K_NH=1, U_AUT=1, SF_DO=1.375): """ + Estimates oxygen requirement [kg-O2/day] for autotrophic biological processes in + an activated sludge system given design and performance assumptions, + following equations 11.16-11.19 in [1]. Parameters ---------- - # system : :class:`biosteam.System` - # The system whose aeration airflow will be calculated. - # influent : iterable[:class:`WasteStream`], optional - # Streams incoming to activated sludge process. Default is None. flow : float Volumetric flow rate through the system [m3/d]. influent_COD : float @@ -156,82 +131,58 @@ def get_oxygen_autotrophs(flow, influent_COD, eff_COD_soluble, influent_TKN, Influent TKN concentration [mg-N/L]. The default is None. f_d : float fraction of biomass that remains as cell debris. Default value is - 0.1 gCOD/gCOD based on ASM2d 'f_XI_A'. + 0.1 gCOD/gCOD, based on ASM2d 'f_XI_A'. b_H : float - Decay rate constant of heterotrophs [d^-1]. The default is 0.4 based on ASM2d. + Decay rate constant of heterotrophs [d^(-1)]. The default is 0.4, based on ASM2d. b_AUT : float - Decay rate constant of autotrophs [d^-1]. The default is 0.15 based on ASM2d. + Decay rate constant of autotrophs [d^(-1)]. The default is 0.15, based on ASM2d. SRT : float - Estimated sludge retention time of the system. Default is 10 days. + Design sludge retention time of the system. Default is 10 days. Y_H : float Yield of heterotrophs [gCOD/gCOD]. The default is 0.625. Y_AUT : float Yield of autotrophs [g COD/g N]. The default is 0.24. K_NH: float - Substrate (Ammonium (nutrient)) half saturation coefficient for autotrophs, in [g N/m^3]. + Ammonium (nutrient) half saturation coefficient for autotrophs, in [g N/m^3]. The default is 1.0. U_AUT: float Autotrophic maximum specific growth rate, in [d^(-1)]. The default is 1.0. SF_DO: float Safety factor for dissolved oxygen. The default is 1.375. - ammonia_component_ID : str - Component ID for ammonia. The default is 'S_NH4'. - Returns - ------- - float - Oxygen requirement for autotrophs in kg/day. - References ---------- - [1] Adapted from Equation 11.16-11.19 in GDLF [Grady Jr, C.P.L.; Daigger, G.T.; Love, N.G.; Filipe, C.D.M. - Biological Wastewater Treatment. 3rd ed. Boca Raton, FL: CRC Press 2011.] - + [1] Grady Jr, C.P.L.; Daigger, G.T.; Love, N.G.; Filipe, C.D.M. + Biological Wastewater Treatment. 3rd ed. Boca Raton, FL: CRC Press 2011. """ - - # if influent is None: - # influent = [inf for inf in system.feeds if inf.phase == 'l'] - - # influent_flow = np.array([inf.F_vol*24 for inf in influent]) # in m3/day - # influent_COD = np.array([inf.COD for inf in influent]) # in mg/L - - # if eff_COD_soluble is None: - # eff_COD_soluble = np.array([eff.composite('COD', particle_size='s', unit='mg/L') \ - # for eff in system.products if eff.phase == 'l']) NR = 0.087*(1 + f_d*b_H*SRT)*Y_H/(1 + b_H*SRT) - # TKN = np.array([inf.composite('N', organic=True) + - # inf.composite('N', subgroup=(ammonia_component_ID,)) \ - # for inf in influent]) TKN = influent_TKN S_N_a = TKN - NR*(influent_COD - eff_COD_soluble) S_NH = K_NH*(1/SRT + b_AUT)/(U_AUT/SF_DO - (1 + b_AUT/SRT)) aeration_factor = 4.57 - (1 + f_d*b_AUT*SRT)*Y_AUT/(1 + b_AUT*SRT) - # mass_N_removed = np.sum(influent_flow*(S_N_a - S_NH)/1000) # kg/day mass_N_removed = flow*(S_N_a - S_NH)/1000 # kg/day return mass_N_removed*aeration_factor def get_airflow(oxygen_heterotrophs, oxygen_autotrophs, oxygen_transfer_efficiency=12): """ + Estimates diffused aeration air flow rate [m3/min] of an activated sludge + system based on oxygen requirements, following equation 11.2 in [1]. Parameters ---------- oxygen_heterotrophs : float - In kg/day. + Oxygen requirement for heterotrophic biological processes, in kg-O2/day. oxygen_autotrophs : float - In kg/day. + Oxygen requirement for autotrophic biological processes, in kg-O2/day. oxygen_transfer_efficiency : float Field oxygen transfer efficiency in percentage. The default is 12. - - Returns - ------- - Airflow in m3/min. References ---------- - [1] Adapted from Equation 11.2 in GDLF [Grady Jr, C.P.L.; Daigger, G.T.; Love, N.G.; Filipe, C.D.M. - Biological Wastewater Treatment. 3rd ed. Boca Raton, FL: CRC Press 2011.] + [1] Grady Jr, C.P.L.; Daigger, G.T.; Love, N.G.; Filipe, C.D.M. + Biological Wastewater Treatment. 3rd ed. Boca Raton, FL: CRC Press 2011. """ @@ -240,10 +191,12 @@ def get_airflow(oxygen_heterotrophs, oxygen_autotrophs, oxygen_transfer_efficien return Q_air -def get_P_blower(q_air, T=20, p_atm=101.325, P_inlet_loss=1, +def get_P_blower(q_air, T=20, P_atm=101.325, P_inlet_loss=1, P_diffuser_loss=7, h_submergance=5.18, efficiency=0.7, K=0.283): """ + Estimates blower power requirement [kW] for diffused aeration, following + equation 5-77 in [1] and equation 4.27 in [2]. Parameters ---------- @@ -251,7 +204,7 @@ def get_P_blower(q_air, T=20, p_atm=101.325, P_inlet_loss=1, Air volumetric flow rate [m3/min]. T : float Air temperature [degree Celsius]. - p_atm : float + P_atm : float Atmostpheric pressure [kPa] P_inlet_loss : float Head loss at inlet [kPa]. @@ -260,33 +213,26 @@ def get_P_blower(q_air, T=20, p_atm=101.325, P_inlet_loss=1, h_submergance : float Diffuser submergance depth in m. The default is 17 feet (5.18 m) efficiency : float - Blower efficiency. Default is 0.7. + Blower efficiency. The default is 0.7, usual range is 0.7 to 0.9. K : float, optional - Equal to (kappa - 1)/kappa, where kappa is the adiabatic exponent of air. - Default is 0.283, equivalent to kappa = 1.3947. + Equal to (kappa - 1)/kappa, where kappa is the adiabatic exponent of air, + i.e., the specific heat ratio. For single-stage centrifugal blower, + the default is 0.283, equivalent to kappa = 1.3947 for dry air. - Returns - ------- - Power of blower [kW]. - References ---------- - [1] Eq.(5-77) in Metcalf & Eddy, Wastewater Engineering: Treatment and + [1] Metcalf & Eddy, Wastewater Engineering: Treatment and Resource Recovery. 5th ed. New York: McGraw-Hill Education. 2014. - [2] Eq.(4.27) in Mueller, J.; William C.B.; and Popel, H.J. Aeration: + [2] Mueller, J.; William C.B.; and Popel, H.J. Aeration: Principles and Practice, Volume 11. CRC press, 2002. """ T += 273.15 - air_molar_vol = 22.4e-3 * (T/273.15)/(p_atm/101.325) # m3/mol + air_molar_vol = 22.4e-3 * (T/273.15)/(P_atm/101.325) # m3/mol R = 8.31446261815324 # J/mol/K, universal gas constant - p_in = p_atm - P_inlet_loss - p_out = p_atm + 9.81*h_submergance + P_diffuser_loss - - # Q_air = q_air*(24*60) # m3/min to m3/day - # P_blower = 1.4161e-5*(T + 273.15)*Q_air*((p_out/p_in)**0.283 - 1)/efficiency - # return P_blower + p_in = P_atm - P_inlet_loss + p_out = P_atm + 9.81*h_submergance + P_diffuser_loss Q_air = q_air/60 # m3/min to m3/s WP = (Q_air / air_molar_vol) * R * T / K * ((p_out/p_in)**K - 1) / efficiency # in W @@ -294,98 +240,80 @@ def get_P_blower(q_air, T=20, p_atm=101.325, P_inlet_loss=1, def get_power_utility(system, active_unit_IDs=None): ''' + Total power of the specified unit operations [kW]. + Parameters ---------- system : :class:`biosteam.System` The system whose power will be calculated. active_unit_IDs : tuple[str], optional IDs of all units whose power needs to be accounted for. The default is None. - - Returns - ------- - Cumulative power of sludge pumps [kW]. ''' - - power_consumption = 0 - - for y in system.flowsheet.unit: - if y.ID in active_unit_IDs: - power_consumption += y.power_utility.power - - return power_consumption -def get_cost_sludge_disposal(sludge, unit_weight_disposal_cost = 375): + return sum([y.power_utility.power for y in system.flowsheet.unit if y.ID in active_unit_IDs]) + +def get_cost_sludge_disposal(sludge, unit_weight_disposal_cost=375): ''' + Returns the daily operating cost of sludge treatment and disposal [USD/day]. + Typical sludge disposal unit costs: + + Land application: 300 - 800 USD/US ton. [2] + Landfill: 100 - 650 USD/US ton. [2] + Incineration: 300 - 500 USD/US ton. [2] + Parameters ---------- - sludge : : iterable[:class:`WasteStream`], optional + sludge : iterable[:class:`WasteStream`] Effluent sludge from the system for which treatment and disposal costs are being calculated. The default is None. - unit_weight_disposal_cost : float - The sludge treatment and disposal cost per unit weight (USD/ton). + unit_weight_disposal_cost : float, optional + The sludge treatment and disposal cost per unit weight [USD/ton]. Feasible range for this value lies between 100-800 USD/ton [1]. - - Land application: 300 - 800 USD/US ton. [2] - Landfill: 100 - 650 USD/US ton. [2] - Incineration: 300 - 500 USD/US ton. [2] - - The default is 375 USD/US ton, which is the close to average of lanfill. - - Returns - ------- - Cost of sludge treatment and disposal (USD/day). + The default is 375 USD/US ton, which is the close to average of landfill. References - ------- + ---------- [1] Feng, J., Li, Y., Strathmann, T. J., & Guest, J. S. (2024b). - Characterizing the opportunity space for sustainable hydrothermal valorization - of Wet Organic Wastes. Environmental Science &; Technology. - https://doi.org/10.1021/acs.est.3c07394 - + Characterizing the opportunity space for sustainable hydrothermal valorization + of Wet Organic Wastes. Environmental Science &; Technology. + https://doi.org/10.1021/acs.est.3c07394 [2] Peccia, J., & Westerhoff, P. (2015). We should expect more out of our sewage - sludge. Environmental Science & Technology, 49(14), 8271–8276. - https://doi.org/10.1021/acs.est.5b01931 + sludge. Environmental Science & Technology, 49(14), 8271–8276. + https://doi.org/10.1021/acs.est.5b01931 ''' sludge_prod = np.array([sludge.composite('solids', True, particle_size='x', unit='ton/d') \ for sludge in sludge]) # in ton/day - - cost_sludge_disposal = np.sum(sludge_prod)*unit_weight_disposal_cost #in USD/day + return np.sum(sludge_prod)*unit_weight_disposal_cost #in USD/day - return cost_sludge_disposal def get_normalized_energy(system, aeration_power, pumping_power, miscellaneous_power): ''' + Normalized operational energy consumption associated with WRRF [kWh/m3]. + Parameters ---------- system : :class:`biosteam.System` The system for which normalized energy consumption is being determined. - aeration_power : float, optional + aeration_power : float Power of blower [kW]. - pumping_power : float, optional + pumping_power : float Power rquired for sludge pumping and other equipments [kW]. - miscellaneous_power : float, optional + miscellaneous_power : float Any other power requirement in the system [kW]. - Returns - ------- - Normalized energy consumption associated with WRRF (kWh/m3). [numpy array] - ''' - - normalized_aeration_energy = aeration_power/sum([s.F_vol for s in system.feeds]) - normalized_pumping_energy = pumping_power/sum([s.F_vol for s in system.feeds]) - normalized_miscellaneous_energy = miscellaneous_power/sum([s.F_vol for s in system.feeds]) + Q = sum([s.F_vol for s in system.feeds]) + return np.array([aeration_power, pumping_power, miscellaneous_power])/Q - normalized_energy_WRRF = np.array([normalized_aeration_energy, normalized_pumping_energy, \ - normalized_miscellaneous_energy]) - return normalized_energy_WRRF - -def get_daily_operational_cost(aeration_power, pumping_power, miscellaneous_power, \ - sludge_disposal_cost, unit_electricity_cost = 0.161): +def get_daily_operational_costs(aeration_power, pumping_power, miscellaneous_power, \ + sludge_disposal_cost, unit_electricity_cost=0.161): ''' + Normalized daily operational costs associated with WRRF [USD/day], in the + order of aeration, sludge pumping, sludge disposal, and miscellaneous. + Parameters ---------- aeration_power : float, optional @@ -399,42 +327,48 @@ def get_daily_operational_cost(aeration_power, pumping_power, miscellaneous_powe unit_electricity_cost : float Unit cost of electricity. Default value is taken as $0.161/kWh [1]. - Returns - ------- - Normalized operational cost associated with WRRF (USD/day). [numpy array] - + References + ---------- [1] https://www.bls.gov/regions/midwest/data/averageenergyprices_selectedareas_table.htm ''' aeration_cost = aeration_power*24*unit_electricity_cost # in (kWh/day)*(USD/kWh) = USD/day pumping_cost = pumping_power*24*unit_electricity_cost # in (kWh/day)*(USD/kWh) = USD/day - sludge_disposal_costs = sludge_disposal_cost miscellaneous_cost = miscellaneous_power*24*unit_electricity_cost # in (kWh/day)*(USD/kWh) = USD/day - operational_costs_WRRF = np.array([aeration_cost, pumping_cost, sludge_disposal_costs, miscellaneous_cost]) #5 + return np.array([aeration_cost, pumping_cost, sludge_disposal_cost, miscellaneous_cost]) #5 - return operational_costs_WRRF +get_daily_operational_cost = get_daily_operational_costs def get_total_operational_cost(q_air, # aeration (blower) power - sludge, # sludge disposal costs - system, active_unit_IDs=None, # sludge pumping power - T=20, p_atm=101.325, P_inlet_loss=1, P_diffuser_loss=7, - h_submergance=5.18, efficiency=0.7, K=0.283, # aeration (blower) power - miscellaneous_power = 0, - unit_weight_disposal_cost = 375, # sludge disposal costs - unit_electricity_cost = 0.161): + sludge, # sludge disposal costs + system, active_unit_IDs=None, # sludge pumping power + T=20, P_atm=101.325, P_inlet_loss=1, P_diffuser_loss=7, + h_submergance=5.18, efficiency=0.7, K=0.283, # aeration (blower) power + miscellaneous_power=0, + unit_weight_disposal_cost=375, # sludge disposal costs + unit_electricity_cost=0.161): ''' + Normalized daily operational cost associated with WRRF [USD/day]. + Parameters ---------- q_air : float - Air volumetric flow rate [m3/min]. + Air volumetric flow rate for diffused aeration [m3/min]. + sludge : iterable[:class:`WasteStream`], optional + Effluent sludge from the system for which treatment and disposal costs are being calculated. + The default is None. + system : :class:`biosteam.System` + The system whose power will be calculated. + active_unit_IDs : tuple[str], optional + IDs of all units whose power needs to be accounted for. The default is None. T : float Air temperature [degree Celsius]. - p_atm : float + P_atm : float Atmostpheric pressure [kPa] P_inlet_loss : float - Head loss at inlet [kPa]. The default is 1 kPa. + Head loss at aeration blower inlet [kPa]. The default is 1 kPa. P_diffuser_loss : float Head loss due to piping and diffuser [kPa]. The default is 7 kPa. h_submergance : float @@ -444,182 +378,110 @@ def get_total_operational_cost(q_air, # aeration (blower) power K : float, optional Equal to (kappa - 1)/kappa, where kappa is the adiabatic exponent of air. Default is 0.283, equivalent to kappa = 1.3947. - - ------------------------------------------------------------------------------ - - system : :class:`biosteam.System` - The system whose power will be calculated. - active_unit_IDs : tuple[str], optional - IDs of all units whose power needs to be accounted for. The default is None. - - ------------------------------------------------------------------------------ - - sludge : : iterable[:class:`WasteStream`], optional - Effluent sludge from the system for which treatment and disposal costs are being calculated. - The default is None. unit_weight_disposal_cost : float - The sludge treatment and disposal cost per unit weight (USD/ton). - Feasible range for this value lies between 100-800 USD/ton [1]. - - Land application: 300 - 800 USD/US ton. [2] - Landfill: 100 - 650 USD/US ton. [2] - Incineration: 300 - 500 USD/US ton. [2] - - The default is 375 USD/US ton, which is the close to average of lanfill. - - ------------------------------------------------------------------------------ - + The sludge treatment and disposal cost per unit weight [USD/ton]. + Feasible range for this value lies between 100-800 USD/ton. + The default is 375 USD/US ton, which is the close to average of landfill. miscellaneous_power : float, optional Any other power requirement in the system [kW]. - unit_electricity_cost : float - Unit cost of electricity. Default value is taken as $0.161/kWh [1]. - - Returns - ------- - Normalized operational cost associated with WRRF (USD/day). [int] + Unit cost of electricity. Default value is taken as $0.161/kWh. ''' - - T += 273.15 - air_molar_vol = 22.4e-3 * (T/273.15)/(p_atm/101.325) # m3/mol - R = 8.31446261815324 # J/mol/K, universal gas constant - - p_in = p_atm - P_inlet_loss - p_out = p_atm + 9.81*h_submergance + P_diffuser_loss - - # Q_air = q_air*(24*60) # m3/min to m3/day - # P_blower = 1.4161e-5*(T + 273.15)*Q_air*((p_out/p_in)**0.283 - 1)/efficiency - # return P_blower - - Q_air = q_air/60 # m3/min to m3/s - WP = (Q_air / air_molar_vol) * R * T / K * ((p_out/p_in)**K - 1) / efficiency # in W - - aeration_power = WP/1000 # in kW - - power_consumption = 0 - - for y in system.flowsheet.unit: - if y.ID in active_unit_IDs: - power_consumption += y.power_utility.power - - pumping_power = power_consumption - - aeration_cost = aeration_power*24*unit_electricity_cost # in (kWh/day)*(USD/kWh) = USD/day - pumping_cost = pumping_power*24*unit_electricity_cost # in (kWh/day)*(USD/kWh) = USD/day - - sludge_prod = np.array([sludge.composite('solids', True, particle_size='x', unit='ton/d') \ - for sludge in sludge]) # in ton/day - sludge_disposal_costs = np.sum(sludge_prod)*unit_weight_disposal_cost #in USD/day - - miscellaneous_cost = miscellaneous_power*24*unit_electricity_cost # in (kWh/day)*(USD/kWh) = USD/day - - operational_costs_WRRF = np.array([aeration_cost, pumping_cost, sludge_disposal_costs, miscellaneous_cost]) - - total_operational_cost = np.sum(operational_costs_WRRF) #5 - - return total_operational_cost - -def get_GHG_emissions_sec_treatment(system = None, influent=None, effluent = None, + aeration_power = get_P_blower(q_air, T, P_atm, P_inlet_loss, P_diffuser_loss, + h_submergance, efficiency, K) + pumping_power = get_power_utility(system, active_unit_IDs) + sludge_disposal_cost = get_cost_sludge_disposal(sludge, unit_weight_disposal_cost) + opex = get_daily_operational_costs(aeration_power, pumping_power, + miscellaneous_power, + sludge_disposal_cost, + unit_electricity_cost) + return sum(opex) + +def get_GHG_emissions_sec_treatment(system=None, influent=None, effluent=None, CH4_EF=0.0075, N2O_EF=0.016): ''' + Returns a 2-tuple of the fugitive emissions of CH4 and N2O [kg/day] + during secondary treatment. + Parameters ---------- system : :class:`biosteam.System` - The system for which emissions during secondary treatment are being calculated. The default is None. - influent : : iterable[:class:`WasteStream`], optional - Influent wastestreams to the system whose wastewater composition determine the potential for GHG emissions. The default is None. - effluent : : iterable[:class:`WasteStream`], optional - The wastestreams which represents the effluent from the treatment process. The default is None. - CH4_EF : float, optional. - The emission factor used to calculate methane emissions in secondary treatment. The default is 0.0075 kg CH4/ kg rCOD. [1] + The system for which emissions during secondary treatment are being + calculated. The default is None. + influent : iterable[:class:`WasteStream`], optional + Influent wastewater to secondary treatment. The default is None. + effluent : iterable[:class:`WasteStream`], optional + Effluent wastewater from the secondary treatment process. The default is None. + CH4_EF : float, optional. + The emission factor used to calculate methane emissions in secondary + treatment. The default is 0.0075 kg CH4/ kg COD removed. [1] N2O_EF : float, optional - The emission factor used to calculate nitrous oxide emissions in secondary treatment. The default is 0.016 kg N2O-N/ kg N. [1] - - Returns - ------- - CH4_emitted : float - The amount of methane emitted during secondary treatment (kg/day). - N2O_emitted : float - The amount of nitrous oxide emitted during secondary treatment (kg/day). + The emission factor used to calculate nitrous oxide emissions in + secondary treatment. The default is 0.016 kg N2O-N/ kg N. [1] References ---------- - [1] Chapter - 6, IPCC. (2019). In 2019 Refinement to the 2006 IPCC Guidelines for National Greenhouse Gas Inventories. + [1] IPCC (2019). Chapter 6 in 2019 Refinement to the 2006 IPCC Guidelines + for National Greenhouse Gas Inventories. ''' if influent is None: influent = [inf for inf in system.feeds if inf.phase == 'l'] + mass_influent_COD = sum(inf.F_vol*24*inf.COD for inf in influent)/1000 # in kg/day + mass_effluent_COD = sum(eff.F_vol*24*eff.COD for eff in effluent)/1000 # in kg/day - influent_flow = np.array([inf.F_vol*24 for inf in influent]) # in m3/day - influent_COD = np.array([inf.COD for inf in influent]) # in mg/L - mass_influent_COD = np.sum(influent_flow*influent_COD/1000) # in kg/day - - effluent_flow = np.array([eff.F_vol*24 for eff in effluent]) - effluent_COD = np.array([eff.COD for eff in effluent]) # in mg/L - mass_effluent_COD = np.sum(effluent_flow*effluent_COD/1000) # in kg/day - - mass_removed_COD = mass_influent_COD - mass_effluent_COD - CH4_emitted = CH4_EF*mass_removed_COD - - influent_N = np.array([inf.TN for inf in influent]) # in mg/L - mass_influent_N = np.sum(influent_flow*influent_N/1000) # in kg/day + CH4_emitted = CH4_EF*(mass_influent_COD - mass_effluent_COD) + mass_influent_N = sum(inf.F_vol*24*inf.TN for inf in influent)/1000 # in kg/day N2O_emitted = N2O_EF*mass_influent_N return CH4_emitted, N2O_emitted def get_GHG_emissions_discharge(effluent=None, CH4_EF=0.009, N2O_EF=0.005): - ''' + ''' + Returns a 2-tuple of the fugitive emissions of CH4 and N2O [kg/day] + at discharge. + Parameters ---------- effluent : : iterable[:class:`WasteStream`], optional - Effluent wastestreams from the system whose wastewater composition determine the potential for GHG emissions at discharge. The default is None. + Effluent wastewater discharged from the system. The default is None. CH4_EF_discharge : float, optional. - The emission factor used to calculate methane emissions in discharge. The default is 0.009 kg CH4/ kg effluent COD. [1] + The emission factor used to calculate methane emissions in discharge. + The default is 0.009 kg CH4/ kg effluent COD. [1] N2O_EF_discharge : float, optional - The emission factor used to calculate nitrous oxide emissions in discharge. The default is 0.005 kg N2O-N/ kg effluent N. [1] - - Returns - ------- - CH4_emitted : float - The amount of methane emitted at discharge (kg/day). - N2O_emitted : float - The amount of nitrous oxide emitted at discharge (kg/day). + The emission factor used to calculate nitrous oxide emissions in discharge. + The default is 0.005 kg N2O-N/ kg effluent N. [1] References ---------- - [1] Chapter - 6, IPCC. (2019). In 2019 Refinement to the 2006 IPCC Guidelines - for National Greenhouse Gas Inventories. - + [1] IPCC, 2019. Chapter 6 in 2019 Refinement to the 2006 IPCC Guidelines + for National Greenhouse Gas Inventories. ''' - - effluent_flow = np.array([eff.F_vol*24 for eff in effluent]) # in m3/day - effluent_COD = np.array([eff.COD for eff in effluent]) # in mg/L - mass_effluent_COD = np.sum(effluent_flow*effluent_COD/1000) # in kg/day - + mass_effluent_COD = sum(eff.F_vol*24*eff.COD for eff in effluent)/1000 # in kg/day CH4_emitted = CH4_EF*mass_effluent_COD - - effluent_N = np.array([eff.TN for eff in effluent]) # in mg/L - mass_effluent_N = np.sum(effluent_flow*effluent_N/1000) # in kg/day - + mass_effluent_N = sum(eff.F_vol*24*eff.TN for eff in effluent)/1000 # in kg/day N2O_emitted = N2O_EF*mass_effluent_N return CH4_emitted, N2O_emitted def get_GHG_emissions_electricity(system, power_blower, power_pump, CO2_EF=0.675): ''' + Returns GHG emission associated with operational electricity consumption [kg CO2-eq/day]. + Parameters ---------- system : :class:`biosteam.System` - The system for which tier-2 GHG emissions due to electricity consumption are being calculated. + The system for which tier-2 GHG emissions due to electricity consumption + are being calculated. power_blower : float Power of blower [kW]. power_pump : float Power required for pumping and other utilities at treatment facility [kW]. CO2_EF : float - The emission factor used to calculate tier-2 CO2 emissions due to electricity consumption. + The emission factor used to calculate scope-2 CO2 emissions due to electricity consumption. The default is 0.675 kg-CO2-Eq/kWh. [1] The emission factor is dependent on the region, and is as follows for USA: @@ -633,12 +495,6 @@ def get_GHG_emissions_electricity(system, power_blower, power_pump, CO2_EF=0.675 Northeast Power Coordinating Council (NPCC): 0.244 kg-CO2-Eq/kWh} (HICC): 0.814 kg-CO2-Eq/kWh} (ASCC): 0.599 kg-CO2-Eq/kWh} - - - Returns - ------- - CO2_emitted : float - The amount of eq. CO2 emitted due to electrity consumption (kg-CO2-Eq/day). ''' @@ -647,106 +503,81 @@ def get_GHG_emissions_electricity(system, power_blower, power_pump, CO2_EF=0.675 return CO2_emissions -def get_GHG_emissions_sludge_disposal(sludge=None, DOC_f = 0.38, MCF = 0.8, k = 0.06, F=0.5, pl=30): +def get_GHG_emissions_sludge_disposal(sludge=None, DOC_f=0.38, MCF=0.8, + k=0.06, F=0.5, pl=30): ''' + The average amount of methane emitted from sludge disposed in landfill [kg/day], + returned in a 2-tuple representing emissions during and after project lifetime, + respectively. + Parameters ---------- - sludge : : iterable[:class:`WasteStream`], optional - Effluent sludge from the system for which GHG emissions are being calculated. The default is None. + sludge : iterable[:class:`WasteStream`], optional + Effluent sludge from the system for which GHG emissions are being + calculated. The default is None. DOC_f : float, optional - fraction of DOC that can decompose (fraction). The default value is 0.5. + fraction of DOC that can decompose. The default value is 0.5. MCF : float, optional - CH4 correction factor for aerobic decomposition in the year of deposition (fraction). The default is 0.8. - k : TYPE, optional - Methane generation rate (k). The default is 0.185. (1/year) - - The decomposition of carbon is assumed to follow first-order kinetics - (with rate constant k), and methane generation is dependent on the amount of remaining decomposable carbon - in the waste. For North America (boreal and temperate climate) the default values for wet and dry climate - are: + CH4 correction factor for aerobic decomposition in the year of + deposition. The default is 0.8. + k : float, optional + Methane generation rate [yr^(-1)]. The default is 0.185. + The decomposition of carbon is assumed to follow 1st-order kinetics + (with rate constant k), and methane generation is dependent on the + amount of remaining decomposable carbon in the waste. + For North America (boreal and temperate climate) the default values are: k (dry climate) = 0.06 k (wet climate) = 0.185 F : float, optional - Fraction of methane in generated landfill gas (volume fraction). The default is 0.5. + Volume fraction of methane in generated landfill gas. The default is 0.5. pl : float, optional - The project lifetime over which methane emissions would be calculated. (years) + The project lifetime [yr] over which methane emissions would be calculated. The default is 30 years. - - Returns - ------- - CH4_emitted : float - The average amount of methane emitted during sludge disposal (kg/day). - References - ---------- - [1] Chapter - 3: Solid Waste Disposal, IPCC. (2019). In 2019 Refinement to - the 2006 IPCC Guidelines for National Greenhouse Gas Inventories. - + References + ---------- + [1] IPCC, 2019. Chapter 3: Solid Waste Disposal, in 2019 Refinement to + the 2006 IPCC Guidelines for National Greenhouse Gas Inventories. ''' - # sludge_flow = np.array([slg.F_vol*24 for slg in sludge]) # in m3/day - # sludge_COD = np.array([slg.COD for slg in sludge]) # in mg/L DOC_mass_flow = 0 - for slg in sludge: DOC_mass_flow += slg.composite("C", flow=True, exclude_gas=True, - subgroup=None, particle_size=None, - degradability="b", organic=True, volatile=None, - specification=None, unit="kg/day") + degradability="b", organic=True, unit="kg/day") annual_DOC_mass = 365*DOC_mass_flow # in kg/year - annual_DDOC = annual_DOC_mass*DOC_f*MCF - - # decomposed_DOC = 0 - - # # sum of sum of geometric series - # for t in range(pl + 1): - # # sum of a geometric series where acc_DOC - # acc_DOC = annual_DDOC * (1 - np.exp(-1 * k * t)) / (1 - np.exp(-1 * k)) - # # all the acc_DOC at the start of the year is the only one contributing - # # to decomposition in that one year - # decomposed_DOC += acc_DOC*(1 - np.exp(-1*k)) - - # # make annumpy array from 0 to pl + 1 outside the for loop - - # # replace t with DOC_ARRAY t_vary = np.arange(pl) - decomposed_DOC = annual_DDOC * (1 - np.exp(-1 * k * t_vary)) - total_decomposed_DOC = np.sum(decomposed_DOC) - CH4_emitted_during_pl = total_decomposed_DOC*F*16/12 + decomposed_DOC = annual_DDOC * (1 - np.exp(-k * t_vary)) + CH4_emitted_during_pl = sum(decomposed_DOC)*F*16/12 - accumulated_DOC_at_pl = annual_DDOC* (1 - np.exp(-1 * k * (pl-1))) / (1 - np.exp(-1 * k)) + accumulated_DOC_at_pl = annual_DDOC* (1 - np.exp(-k * (pl-1))) / (1 - np.exp(-k)) CH4_emitted_after_pl = accumulated_DOC_at_pl*F*16/12 - days_in_year = 365 + return CH4_emitted_during_pl/(pl*365), CH4_emitted_after_pl/(pl*365) - return CH4_emitted_during_pl/(pl*days_in_year), CH4_emitted_after_pl/(pl*days_in_year) - -def get_CO2_eq_WRRF (system, GHG_treatment, GHG_discharge, GHG_electricity, - GHG_sludge_disposal, CH4_CO2eq=29.8, N2O_CO2eq=273): +def get_CO2_eq_WRRF(system, GHG_treatment, GHG_discharge, GHG_electricity, + GHG_sludge_disposal, CH4_CO2eq=29.8, N2O_CO2eq=273): ''' + Normalized GHG emissions from onsite and offsite operations associated + with WRRF [kg CO2 eq./m3]. Parameters ---------- system : :class:`biosteam.System` The system for which normalized GHG emission is being determined. - GHG_treatment : tuple[int], optional + GHG_treatment : tuple[float], optional The amount of methane and nitrous oxide emitted during secondary treatment (kg/day). - GHG_discharge : tuple[int], optional + GHG_discharge : tuple[float], optional The amount of methane and nitrous oxide emitted during effluent discharge (kg/day). GHG_electricity : float The amount of eq. CO2 emitted due to electrity consumption (kg-CO2-Eq/day). GHG_sludge_disposal : int The average amount of methane emitted during sludge disposal (kg/day). - CH4_CO2eq : TYPE, optional - DESCRIPTION. The default is 29.8 kg CO2eq/kg CH4 [1]. - N2O_CO2eq : TYPE, optional - DESCRIPTION. The default is 273 kg CO2eq/kg CH4 [1]. - - Returns - ------- - Normalized GHG emissions from onsite and offsite operations associated with WRRF (kg CO2 eq./m3). [numpy array] + CH4_CO2eq : float, optional + Conversion factor of CH4 to equivalent CO2. The default is 29.8 kg CO2eq/kg CH4 [1]. + N2O_CO2eq : float, optional + Conversion factor of N2O to equivalent CO2. The default is 273 kg CO2eq/kg CH4 [1]. References ---------- @@ -758,7 +589,6 @@ def get_CO2_eq_WRRF (system, GHG_treatment, GHG_discharge, GHG_electricity, CH4_CO2_eq_treatment = GHG_treatment[0]*CH4_CO2eq N2O_CO2_eq_treatment = GHG_treatment[1]*N2O_CO2eq - # source 3 (off-site) CH4_CO2_eq_discharge = GHG_discharge[0]*CH4_CO2eq N2O_CO2_eq_discharge = GHG_discharge[1]*N2O_CO2eq @@ -780,20 +610,19 @@ def get_CO2_eq_WRRF (system, GHG_treatment, GHG_discharge, GHG_electricity, return normalized_CO2_eq_WRRF -def get_total_CO2_eq(system, q_air, influent_sc =None, effluent_sc = None, effluent_sys =None, active_unit_IDs=None, sludge=None, - p_atm=101.325, K=0.283, CH4_CO2eq=29.8, N2O_CO2eq=273, - - CH4_EF_sc =0.0075, N2O_EF_sc =0.016, CH4_EF_discharge=0.009, N2O_EF_discharge=0.005, - T=20, F=0.5, - - # uncertain parameters - P_inlet_loss=1, P_diffuser_loss=7, h_submergance=5.18, efficiency=0.7, - - CO2_EF=0.675, DOC_f = 0.38, MCF = 0.8, k = 0.06, pl=30 - ): +def get_total_CO2_eq( + system, q_air, influent_sc=None, effluent_sc=None, effluent_sys=None, + active_unit_IDs=None, sludge=None, P_atm=101.325, K=0.283, + CH4_CO2eq=29.8, N2O_CO2eq=273, CH4_EF_sc=0.0075, N2O_EF_sc=0.016, + CH4_EF_discharge=0.009, N2O_EF_discharge=0.005, T=20, F=0.5, + P_inlet_loss=1, P_diffuser_loss=7, h_submergance=5.18, efficiency=0.7, + CO2_EF=0.675, DOC_f=0.38, MCF=0.8, k=0.06, pl=30 + ): ''' - + Returns the total normalized GHG emissions from onsite and offsite operations + associated with WRRF [kg CO2 eq./m3]. + Parameters ---------- system : :class:`biosteam.System` @@ -801,38 +630,44 @@ def get_total_CO2_eq(system, q_air, influent_sc =None, effluent_sc = None, efflu ----Secondary treatment---- - influent_sc : : iterable[:class:`WasteStream`], optional - Influent wastestreams to secondary treatment whose wastewater composition determine the potential for GHG emissions. The default is None. - effluent_sc : : iterable[:class:`WasteStream`], optional - The wastestreams which represents the effluent from the secondary treatment process. The default is None. - CH4_EF_sc : float, optional. - The emission factor used to calculate methane emissions in secondary treatment. The default is 0.0075 kg CH4/ kg rCOD. [1] - N2O_EF_sc : float, optional - The emission factor used to calculate nitrous oxide emissions in secondary treatment. The default is 0.016 kg N2O-N/ kg N. [1] + influent_sc : iterable[:class:`WasteStream`], optional + Influent wastewater to secondary treatment. The default is None. + effluent_sc : iterable[:class:`WasteStream`], optional + Effluent wastewater from the secondary treatment process. The default is None. + CH4_EF_sc : float, optional. + The emission factor used to calculate methane emissions in secondary + treatment. The default is 0.0075 kg CH4/ kg rCOD. + N2O_EF_sc : float, optional + The emission factor used to calculate nitrous oxide emissions in + secondary treatment. The default is 0.016 kg N2O-N/ kg N. ----Discharge---- - effluent_sys : : iterable[:class:`WasteStream`], optional - Effluent wastestreams from the system whose wastewater composition determine the potential for GHG emissions at discharge. The default is None. - CH4_EF_discharge : float, optional. - The emission factor used to calculate methane emissions in discharge. The default is 0.009 kg CH4/ kg effluent COD. [1] + effluent_sys : iterable[:class:`WasteStream`], optional + Effluent wastewater discharged from the system. The default is None. + CH4_EF_discharge : float, optional. + The emission factor used to calculate methane emissions in discharge. + The default is 0.009 kg CH4/ kg effluent COD. N2O_EF_discharge : float, optional - The emission factor used to calculate nitrous oxide emissions in discharge. The default is 0.005 kg N2O-N/ kg effluent N. [1] - + The emission factor used to calculate nitrous oxide emissions in + discharge. The default is 0.005 kg N2O-N/ kg effluent N. ----Electricity--- + CO2_EF : float + The emission factor used to calculate scope-2 CO2 emissions due to + electricity consumption. The default is 0.675 kg-CO2-Eq/kWh. --blower power- q_air : float - Air volumetric flow rate [m3/min]. + Air volumetric flow rate for diffused aeration [m3/min]. T : float Air temperature [degree Celsius]. - p_atm : float + P_atm : float Atmostpheric pressure [kPa] P_inlet_loss : float - Head loss at inlet [kPa]. + Head loss at aeration blower inlet [kPa]. The default is 1 kPa. P_diffuser_loss : float - Head loss due to piping and diffuser [kPa]. + Head loss due to piping and diffuser [kPa]. The default is 7 kPa. h_submergance : float Diffuser submergance depth in m. The default is 17 feet (5.18 m) efficiency : float @@ -844,151 +679,64 @@ def get_total_CO2_eq(system, q_air, influent_sc =None, effluent_sc = None, efflu --pump power-- active_unit_IDs : tuple[str], optional IDs of all units whose power needs to be accounted for. The default is None. - ----Sludge disposal--- - sludge : : iterable[:class:`WasteStream`], optional - Effluent sludge from the system for which GHG emissions are being calculated. The default is None. + sludge : iterable[:class:`WasteStream`], optional + Effluent sludge from the system for which GHG emissions are being + calculated. The default is None. DOC_f : float, optional - fraction of DOC that can decompose (fraction). The default value is 0.5. + fraction of DOC that can decompose. The default value is 0.5. MCF : float, optional - CH4 correction factor for aerobic decomposition in the year of deposition (fraction). The default is 0.8. - k : TYPE, optional - Methane generation rate (k). The default is 0.185. (1/year) - - The decomposition of carbon is assumed to follow first-order kinetics - (with rate constant k), and methane generation is dependent on the amount of remaining decomposable carbon - in the waste. For North America (boreal and temperate climate) the default values for wet and dry climate - are: + CH4 correction factor for aerobic decomposition in the year of + deposition. The default is 0.8. + k : float, optional + Methane generation rate [yr^(-1)]. The default is 0.185. + The decomposition of carbon is assumed to follow 1st-order kinetics + (with rate constant k), and methane generation is dependent on the + amount of remaining decomposable carbon in the waste. + For North America (boreal and temperate climate) the default values are: k (dry climate) = 0.06 k (wet climate) = 0.185 F : float, optional - Fraction of methane in generated landfill gas (volume fraction). The default is 0.5. + Volume fraction of methane in generated landfill gas. The default is 0.5. pl : float, optional - The project lifetime over which methane emissions would be calculated. (years) + The project lifetime [yr] over which methane emissions would be calculated. The default is 30 years. - --------- Eq CO2 EFs --------- - CH4_CO2eq : TYPE, optional - DESCRIPTION. The default is 29.8 kg CO2eq/kg CH4 [1]. - N2O_CO2eq : TYPE, optional - DESCRIPTION. The default is 273 kg CO2eq/kg CH4 [1]. - - Returns - ------- - Total Normalized GHG emissions from onsite and offsite operations associated with WRRF (kg CO2 eq./m3) [int]. - + CH4_CO2eq : float, optional + Conversion factor of CH4 to equivalent CO2. The default is 29.8 kg CO2eq/kg CH4. + N2O_CO2eq : float, optional + Conversion factor of N2O to equivalent CO2. The default is 273 kg CO2eq/kg CH4. ''' # source 1 (on-site) - if influent_sc is None: - influent_sc = [inf for inf in system.feeds if inf.phase == 'l'] - - influent_sc_flow = np.array([inf.F_vol*24 for inf in influent_sc]) # in m3/day - influent_sc_COD = np.array([inf.COD for inf in influent_sc]) # in mg/L - mass_influent_COD_sc = np.sum(influent_sc_flow*influent_sc_COD/1000) # in kg/day - - effluent_sc_flow = np.array([eff.F_vol*24 for eff in effluent_sc]) - effluent_sc_COD = np.array([eff.COD for eff in effluent_sc]) # in mg/L - mass_effluent_COD_sc = np.sum(effluent_sc_flow*effluent_sc_COD/1000) # in kg/day - - mass_removed_COD_sc = mass_influent_COD_sc - mass_effluent_COD_sc - CH4_emitted_sc = CH4_EF_sc*mass_removed_COD_sc - - influent_N_sc = np.array([inf.TN for inf in influent_sc]) # in mg/L - mass_influent_N = np.sum(influent_sc_flow*influent_N_sc/1000) # in kg/day - - N2O_emitted_sc = N2O_EF_sc*mass_influent_N - - # source 1 (on-site) - CH4_CO2_eq_treatment = CH4_emitted_sc*CH4_CO2eq - N2O_CO2_eq_treatment = N2O_emitted_sc*N2O_CO2eq + CH4_treatment, N2O_treatment = get_GHG_emissions_sec_treatment( + system, influent_sc, effluent_sc, CH4_EF_sc, N2O_EF_sc + ) # source 3 (off-site) - effluent_flow = np.array([eff.F_vol*24 for eff in effluent_sys]) # in m3/day - effluent_COD = np.array([eff.COD for eff in effluent_sys]) # in mg/L - mass_effluent_COD = np.sum(effluent_flow*effluent_COD/1000) # in kg/day - - CH4_emitted_discharge = CH4_EF_discharge*mass_effluent_COD - - effluent_N = np.array([eff.TN for eff in effluent_sys]) # in mg/L - mass_effluent_N = np.sum(effluent_flow*effluent_N/1000) # in kg/day - - N2O_emitted_discharge = N2O_EF_discharge*mass_effluent_N - - # source 3 (off-site) - CH4_CO2_eq_discharge = CH4_emitted_discharge*CH4_CO2eq - N2O_CO2_eq_discharge = N2O_emitted_discharge*N2O_CO2eq - + CH4_discharge, N2O_discharge = get_GHG_emissions_discharge( + effluent_sys, CH4_EF_discharge, N2O_EF_discharge + ) # source 5 (off-site) - T += 273.15 - air_molar_vol = 22.4e-3 * (T/273.15)/(p_atm/101.325) # m3/mol - R = 8.31446261815324 # J/mol/K, universal gas constant - - p_in = p_atm - P_inlet_loss - p_out = p_atm + 9.81*h_submergance + P_diffuser_loss - - # Q_air = q_air*(24*60) # m3/min to m3/day - # P_blower = 1.4161e-5*(T + 273.15)*Q_air*((p_out/p_in)**0.283 - 1)/efficiency - # return P_blower - - Q_air = q_air/60 # m3/min to m3/s - WP = (Q_air / air_molar_vol) * R * T / K * ((p_out/p_in)**K - 1) / efficiency # in W - - blower_power = WP/1000 - - pumping_power = 0 - - for y in system.flowsheet.unit: - if y.ID in active_unit_IDs: - pumping_power += y.power_utility.power # in kW - - total_energy_consumed = (blower_power + pumping_power)*24 # in kWh/day - - GHG_electricity = total_energy_consumed*CO2_EF # in kg-CO2-Eq/day - - # source 5 (off-site) - CO2_eq_electricity = GHG_electricity*1 + blower_power = get_P_blower(q_air, T, P_atm, P_inlet_loss, P_diffuser_loss, + h_submergance, efficiency, K) + pumping_power = get_power_utility(system, active_unit_IDs) + CO2_eq_electricity = (blower_power + pumping_power)*24*CO2_EF # in kg-CO2-Eq/day # source 4 (off-site) - - DOC_mass_flow = 0 - - for slg in sludge: - DOC_mass_flow += slg.composite("C", flow=True, exclude_gas=True, - subgroup=None, particle_size=None, - degradability="b", organic=True, volatile=None, - specification=None, unit="kg/day") - - annual_DOC_mass = 365*DOC_mass_flow # in kg/year - - annual_DDOC = annual_DOC_mass*DOC_f*MCF - - t_vary = np.arange(pl) - decomposed_DOC = annual_DDOC * (1 - np.exp(-1 * k * t_vary)) - total_decomposed_DOC = np.sum(decomposed_DOC) - CH4_emitted_during_pl = total_decomposed_DOC*F*16/12 - - accumulated_DOC_at_pl = annual_DDOC* (1 - np.exp(-1 * k * (pl-1))) / (1 - np.exp(-1 * k)) - CH4_emitted_after_pl = accumulated_DOC_at_pl*F*16/12 - - days_in_year = 365 - - GHG_sludge_disposal = (CH4_emitted_during_pl + CH4_emitted_after_pl)/(pl*days_in_year) - - CH4_CO2_eq_sludge_disposal_pl = GHG_sludge_disposal*CH4_CO2eq - - - CO2_eq_WRRF = np.array([CH4_CO2_eq_treatment, N2O_CO2_eq_treatment, #1 - CH4_CO2_eq_discharge, N2O_CO2_eq_discharge, #3 - CH4_CO2_eq_sludge_disposal_pl, #4 - CO2_eq_electricity]) #5 + CH4_sludge_disposal = get_GHG_emissions_sludge_disposal( + sludge, DOC_f, MCF, k, F, pl + ) - normalized_total_CO2_eq_WRRF = np.sum(CO2_eq_WRRF)/sum([24*s.F_vol for s in system.feeds]) + CO2_eq_WRRF = np.sum([CH4_treatment*CH4_CO2eq, N2O_treatment*N2O_CO2eq, #1 + CH4_discharge*CH4_CO2eq, N2O_discharge*N2O_CO2eq, #3 + sum(CH4_sludge_disposal)*CH4_CO2eq, #4 + CO2_eq_electricity]) #5 - return normalized_total_CO2_eq_WRRF \ No newline at end of file + return CO2_eq_WRRF/sum([24*s.F_vol for s in system.feeds]) \ No newline at end of file From 71983605da61e5a5f581376e6df741effec7c0c0 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Wed, 2 Oct 2024 09:07:28 -0700 Subject: [PATCH 411/424] clean up `PrimaryClarifier._design()` --- qsdsan/sanunits/_clarifier.py | 356 +++++++++++++++++----------------- 1 file changed, 173 insertions(+), 183 deletions(-) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index e577a07f..c9ce2db7 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -16,6 +16,7 @@ ''' from numpy import maximum as npmax, minimum as npmin, exp as npexp +from math import sqrt, pi from warnings import warn from numba import njit from .. import SanUnit, WasteStream @@ -1230,7 +1231,7 @@ class PrimaryClarifier(IdealClarifier): Typically SOR lies between 30-50 (m3/day)/m2. Here default value of 41 (m3/day)/m2 is used. depth_clarifier : float - Depth of clarifier. Typical depths range from 3 m to 4.9 m [1,2]. + Depth of clarifier. Typical depths range from 3 m to 4.9 m [1], [2]. Default value of 4.5 m would be used here. downward_flow_velocity : float, optional Speed on the basis of which center feed diameter is designed [m/hr]. [3] @@ -1320,6 +1321,8 @@ class PrimaryClarifier(IdealClarifier): _ins_size_is_fixed = False _outs_size_is_fixed = True + peak_flow_safety_factor = 2.5 # assumed based on average and maximum velocities + # # Costs # wall_concrete_unit_cost = 1081.73 # $/m3 (Hydromantis. CapdetWorks 4.0. https://www.hydromantis.com/CapdetWorks.html) # slab_concrete_unit_cost = 582.48 # $/m3 (Hydromantis. CapdetWorks 4.0. https://www.hydromantis.com/CapdetWorks.html) @@ -1344,7 +1347,7 @@ def __init__(self, ID='', ins=None, outs=(), self.depth_clarifier = depth_clarifier self.downward_flow_velocity = downward_flow_velocity self.F_BM.update(F_BM) - self._sludge = WasteStream(f'{ID}_sludge') + self._sludge = WasteStream(f'{ID}_sludge') # @property # def solids_loading_rate(self): @@ -1401,198 +1404,185 @@ def __init__(self, ID='', ins=None, outs=(), # pump_ss += p_design['Pump stainless steel'] # return pipe_ss, pump_ss - # _units = { - # 'Number of clarifiers': 'ea', - # 'SOR': 'm3/day/m2', - # 'Volumetric flow': 'm3/day', - # 'Surface area': 'm2', - # 'Cylindrical diameter': 'm', - # 'Conical radius': 'm', - # 'Conical depth': 'm', - # 'Clarifier depth': 'm', - # 'Cylindrical depth': 'm', - # 'Cylindrical volume': 'm3', - # 'Conical volume': 'm3', - # 'Volume': 'm3', - # 'Hydraulic Retention Time': 'hr', - # 'Center feed depth': 'm', - # 'Downward flow velocity': 'm/hr', - # 'Center feed diameter': 'm', - # 'Volume of concrete wall': 'm3', - # 'Volume of concrete slab': 'm3', - # 'Stainless steel': 'kg', - # 'Pump pipe stainless steel' : 'kg', - # 'Pump stainless steel': 'kg', - # 'Number of pumps': 'ea' - # } + _units = { + 'Number of clarifiers': 'ea', + 'SOR': 'm3/day/m2', + 'Volumetric flow': 'm3/day', + 'Surface area': 'm2', + 'Cylindrical diameter': 'm', + 'Conical radius': 'm', + 'Conical depth': 'm', + 'Clarifier depth': 'm', + 'Cylindrical depth': 'm', + 'Cylindrical volume': 'm3', + 'Conical volume': 'm3', + 'Volume': 'm3', + 'Hydraulic Retention Time': 'hr', + 'Center feed depth': 'm', + 'Downward flow velocity': 'm/hr', + 'Center feed diameter': 'm', + 'Volume of concrete wall': 'm3', + 'Volume of concrete slab': 'm3', + 'Stainless steel': 'kg', + 'Pump pipe stainless steel' : 'kg', + 'Pump stainless steel': 'kg', + 'Number of pumps': 'ea' + } - # def _design(self): - - # mixed = self._mixed - # mixed.mix_from(self.ins) - # D = self.design_results - - # # Number of clarifiers based on tentative suggestions by Jeremy - # # (would be verified through collaboration with industry) - # total_flow = (mixed.get_total_flow('m3/hr')*24)/3785 # in MGD - # if total_flow <= 3: - # D['Number of clarifiers'] = 2 - # elif total_flow > 3 and total_flow <= 8: - # D['Number of clarifiers'] = 3 - # elif total_flow > 8 and total_flow <=20: - # D['Number of clarifiers'] = 4 - # else: - # D['Number of clarifiers'] = 4 - # total_flow -= 20 - # D['Number of clarifiers'] += np.ceil(total_flow/20) - - # D['SOR'] = self.surface_overflow_rate # in (m3/day)/m2 - # D['Volumetric flow'] = (mixed.get_total_flow('m3/hr')*24)/D['Number of clarifiers'] # m3/day - # D['Surface area'] = D['Volumetric flow']/D['SOR'] # in m2 - # D['Cylindrical diameter'] = np.sqrt(4*D['Surface area']/np.pi) #in m - - # #Check on cylindrical diameter [2, 3] - # if D['Cylindrical diameter'] < 3 or D['Cylindrical diameter'] > 60: - # Cylindrical_dia = D['Cylindrical diameter'] - # warn(f'Cylindrical diameter = {Cylindrical_dia} is not between 3 m and 60 m') - - # D['Conical radius'] = D['Cylindrical diameter']/2 - # # The slope of the bottom conical floor lies between 1:10 to 1:12 [3, 4] - # D['Conical depth'] = D['Conical radius']/12 - # D['Clarifier depth'] = self.depth_clarifier #in m - # D['Cylindrical depth'] = D['Clarifier depth'] - D['Conical depth'] - - # # Check on cylindrical and conical depths - # if D['Cylindrical depth'] < D['Conical depth']: - # Cylindrical_depth = D['Cylindrical depth'] - # Conical_depth = D['Conical depth'] - # warn(f'Cylindrical depth = {Cylindrical_depth} is lower than Conical depth = {Conical_depth}') - - # D['Cylindrical volume'] = np.pi*np.square(D['Cylindrical diameter']/2)*D['Cylindrical depth'] #in m3 - # D['Conical volume'] = (3.14/3)*(D['Conical radius']**2)*D['Conical depth'] #in m3 - # D['Volume'] = D['Cylindrical volume'] + D['Conical volume'] #in m3 - - # D['Hydraulic Retention Time'] = D['Volume']/(D['Volumetric flow']/24) #in hrs - - # # Check on cylinderical HRT [3] - # if D['Hydraulic Retention Time'] < 1.5 or D['Hydraulic Retention Time'] > 2.5: - # HRT = D['Hydraulic Retention Time'] - # warn(f'HRT = {HRT} is not between 1.5 and 2.5 hrs') - - # # The design here is for center feed of clarifier. - - # # Depth of the center feed lies between 30-75% of sidewater depth. [3, 4] - # D['Center feed depth'] = 0.5*D['Cylindrical depth'] - # # Typical conventional feed wells are designed for an average downflow velocity - # # of 10-13 mm/s and maximum velocity of 25-30 mm/s. [4] - # peak_flow_safety_factor = 2.5 # assumed based on average and maximum velocities - # D['Downward flow velocity'] = self.downward_flow_velocity*peak_flow_safety_factor # in m/hr - - # Center_feed_area = (D['Volumetric flow']/24)/D['Downward flow velocity'] # in m2 - - # D['Center feed diameter'] = np.sqrt(4*Center_feed_area/np.pi) - - # #Sanity check: Diameter of the center feed lies between 15-25% of tank diameter [4] - # #The lower limit of this check has been modified to 10% based on advised range of down flow velocity in [4]. - # if D['Center feed diameter'] < 0.10*D['Cylindrical diameter'] or D['Center feed diameter'] > 0.25*D['Cylindrical diameter']: - # cf_dia = D['Center feed diameter'] - # tank_dia = D['Cylindrical diameter'] - # warn(f'Diameter of the center feed does not lie between 15-25% of tank diameter. It is {cf_dia*100/tank_dia}% of tank diameter') - - # # Amount of concrete required - # D_tank = D['Cylindrical depth']*39.37 # m to inches - # # Thickness of the wall concrete [m]. Default to be minimum of 1 feet with 1 inch added for every feet of depth over 12 feet. - # thickness_concrete_wall = (1 + max(D_tank-12, 0)/12)*0.3048 # from feet to m - # inner_diameter = D['Cylindrical diameter'] - # outer_diameter = inner_diameter + 2*thickness_concrete_wall - # volume_cylindercal_wall = (np.pi*D['Cylindrical depth']/4)*(outer_diameter**2 - inner_diameter**2) - # D['Volume of concrete wall'] = volume_cylindercal_wall # in m3 - - # # Concrete slab thickness, [ft], default to be 2 in thicker than the wall thickness. (Brian's code) - # thickness_concrete_slab = thickness_concrete_wall + (2/12)*0.3048 # from inch to m - # outer_diameter_cone = inner_diameter + 2*(thickness_concrete_wall + thickness_concrete_slab) - # volume_conical_wall = (np.pi/(3*4))*(((D['Conical depth'] + thickness_concrete_wall + thickness_concrete_slab)*(outer_diameter_cone**2)) - (D['Conical depth']*(inner_diameter)**2)) - # D['Volume of concrete slab'] = volume_conical_wall - - # # Amount of metal required for center feed - # thickness_metal_wall = 0.3048 # equal to 1 feet, in m (!! NEED A RELIABLE SOURCE !!) - # inner_diameter_center_feed = D['Center feed diameter'] - # outer_diameter_center_feed = inner_diameter_center_feed + 2*thickness_metal_wall - # volume_center_feed = (3.14*D['Center feed depth']/4)*(outer_diameter_center_feed**2 - inner_diameter_center_feed**2) - # density_ss = 7930 # kg/m3, 18/8 Chromium - # D['Stainless steel'] = volume_center_feed*density_ss # in kg - - # # Pumps - # pipe, pumps = self._design_pump() - # D['Pump pipe stainless steel'] = pipe - # D['Pump stainless steel'] = pumps - - # #For primary clarifier - # D['Number of pumps'] = D['Number of clarifiers'] - - # def _cost(self): - # D = self.design_results - # C = self.baseline_purchase_costs + def _design(self): + mixed = self._mixed + mixed.mix_from(self.ins) + D = self.design_results + + # Number of clarifiers based on tentative suggestions by Jeremy + # (would be verified through collaboration with industry) + Q_mgd = mixed.get_total_flow('MGD') + if Q_mgd <= 3: N = 2 + elif Q_mgd <= 8: N = 3 + elif Q_mgd <= 20: N = 4 + else: N = 3 + int(Q_mgd / 20) + D['Number of clarifiers'] = D['Number of pumps'] = N + + SOR = D['SOR'] = self.surface_overflow_rate # in (m3/day)/m2 + Q = D['Volumetric flow'] = mixed.get_total_flow('m3/d')/N # m3/day + A = D['Surface area'] = Q/SOR # in m2 + dia = D['Cylindrical diameter'] = sqrt(4*A/pi) #in m + + # Check on cylindrical diameter d [2, 3] + if dia < 3 or dia > 60: + warn(f'Cylindrical diameter = {dia:.2f} is not between 3 m and 60 m') + + rad = D['Conical radius'] = dia/2 + # The slope of the bottom conical floor lies between 1:10 to 1:12 [3, 4] + h_cone = D['Conical height'] = rad/12 + h = D['Clarifier depth'] = self.depth_clarifier # in m + h_cyl = D['Cylindrical height'] = h - h_cone + + # Check on cylindrical and conical depths + if h_cyl < h_cone: + warn(f'Cylindrical highet = {h_cyl} is lower than conical height = {h_cone}') + + V_cyl = D['Cylindrical volume'] = A * h_cyl # in m3 + V_cone = D['Conical volume'] = A * h_cone / 3 # in m3 + V = D['Volume'] = V_cyl + V_cone # in m3 + + HRT = D['Hydraulic Retention Time'] = V/(Q/24) # in hrs + + # Check on cylinderical HRT [3] + if HRT < 1.5 or HRT > 2.5: + warn(f'HRT = {HRT} is not between 1.5 and 2.5 hrs') + + # The design here is for center feed of clarifier. + + # Depth of the center feed lies between 30-75% of sidewater depth. [3, 4] + D['Center feed depth'] = 0.5*h_cyl + # Typical conventional feed wells are designed for an average downflow velocity + # of 10-13 mm/s and maximum velocity of 25-30 mm/s. [4] + v_down = D['Downward flow velocity'] = self.downward_flow_velocity*self.peak_flow_safety_factor # in m/hr + + A_cf = (Q/24)/v_down # in m2 + + dia_cf = D['Center feed diameter'] = sqrt(4*A_cf/pi) + + #Sanity check: Diameter of the center feed lies between 15-25% of tank diameter [4] + #The lower limit of this check has been modified to 10% based on advised range of down flow velocity in [4]. + if dia_cf < 0.10*dia or dia_cf > 0.25*dia: + warn(f'Diameter of the center feed does not lie between 15-25% of tank diameter. It is {dia_cf*100/dia:.2f}% of tank diameter') + + # Amount of concrete required + # D_tank = D['Cylindrical depth']*39.37 # m to inches + h_ft = h*3.2808398950131235 # m to feet + # Thickness of the wall concrete [m]. Default to be minimum of 1 feet with 1 inch added for every feet of depth over 12 feet. + # thickness_concrete_wall = (1 + max(D_tank-12, 0)/12)*0.3048 # from feet to m + d_wall = (1 + max(h_ft-12, 0)/12) * 0.3048 # feet to m + OD = dia + 2*d_wall + D['Volume of concrete wall'] = pi*h_cyl/4*(OD**2 - dia**2) # m3 + + # Concrete slab thickness, [ft], default to be 2 in thicker than the wall thickness. (Brian's code) + d_slab = d_wall + (2/12)*0.3048 # from inch to m + # outer_diameter_cone = inner_diameter + 2*(thickness_concrete_wall + thickness_concrete_slab) + OD_cone = dia + 2*d_slab + # volume_conical_wall = (np.pi/(3*4))*(((D['Conical depth'] + thickness_concrete_wall + thickness_concrete_slab)*(outer_diameter_cone**2)) - (D['Conical depth']*(inner_diameter)**2)) + # D['Volume of concrete slab'] = volume_conical_wall + D['Volume of concrete slab'] = pi/3*((h_cone + d_slab)*(OD_cone/2)**2 - h_cone*(dia/2)**2) + + # Amount of metal required for center feed + #!!! consider empirical estimation of steel volume for all equipment (besides center feed, e.g., scrapper, support column, EDI, skimmer, walkway etc.) + thickness_metal_wall = 0.3048 # equal to 1 feet, in m (!! NEED A RELIABLE SOURCE !!) + inner_diameter_center_feed = D['Center feed diameter'] + outer_diameter_center_feed = inner_diameter_center_feed + 2*thickness_metal_wall + volume_center_feed = (3.14*D['Center feed depth']/4)*(outer_diameter_center_feed**2 - inner_diameter_center_feed**2) + density_ss = 7930 # kg/m3, 18/8 Chromium + D['Stainless steel'] = volume_center_feed*density_ss # in kg - # # Construction of concrete and stainless steel walls - # C['Wall concrete'] = D['Number of clarifiers']*D['Volume of concrete wall']*self.wall_concrete_unit_cost + # Pumps + pipe, pumps = self._design_pump() + D['Pump pipe stainless steel'] = pipe + D['Pump stainless steel'] = pumps + - # C['Slab concrete'] = D['Number of clarifiers']*D['Volume of concrete slab']*self.slab_concrete_unit_cost + def _cost(self): + D = self.design_results + C = self.baseline_purchase_costs + N = D['Number of clarifiers'] - # C['Wall stainless steel'] = D['Number of clarifiers']*D['Stainless steel']*self.stainless_steel_unit_cost + # Construction of concrete and stainless steel walls + C['Wall concrete'] = N*D['Volume of concrete wall']*self.wall_concrete_unit_cost + C['Slab concrete'] = N*D['Volume of concrete slab']*self.slab_concrete_unit_cost + C['Wall stainless steel'] = N*D['Stainless steel']*self.stainless_steel_unit_cost - # # Cost of equipment + # Cost of equipment - # # Source of scaling exponents: Process Design and Economics for Biochemical Conversion of Lignocellulosic Biomass to Ethanol by NREL. + # Source of scaling exponents: Process Design and Economics for Biochemical Conversion of Lignocellulosic Biomass to Ethanol by NREL. - # # Scraper - # # Source: https://www.alibaba.com/product-detail/Peripheral-driving-clarifier-mud-scraper-waste_1600891102019.html?spm=a2700.details.0.0.47ab45a4TP0DLb - # # base_cost_scraper = 2500 - # # base_flow_scraper = 1 # in m3/hr (!!! Need to know whether this is for solids or influent !!!) - # clarifier_flow = D['Volumetric flow']/24 + # Scraper + # Source: https://www.alibaba.com/product-detail/Peripheral-driving-clarifier-mud-scraper-waste_1600891102019.html?spm=a2700.details.0.0.47ab45a4TP0DLb + # base_cost_scraper = 2500 + # base_flow_scraper = 1 # in m3/hr (!!! Need to know whether this is for solids or influent !!!) + clarifier_flow = D['Volumetric flow']/24 - # # C['Scraper'] = D['Number of clarifiers']*base_cost_scraper*(clarifier_flow/base_flow_scraper)**0.6 + # C['Scraper'] = D['Number of clarifiers']*base_cost_scraper*(clarifier_flow/base_flow_scraper)**0.6 - # # base_power_scraper = 2.75 # in kW - # # THE EQUATION BELOW IS NOT CORRECT TO SCALE SCRAPER POWER REQUIREMENTS - # # scraper_power = D['Number of clarifiers']*base_power_scraper*(clarifier_flow/base_flow_scraper)**0.6 + # base_power_scraper = 2.75 # in kW + # THE EQUATION BELOW IS NOT CORRECT TO SCALE SCRAPER POWER REQUIREMENTS + # scraper_power = D['Number of clarifiers']*base_power_scraper*(clarifier_flow/base_flow_scraper)**0.6 - # # v notch weir - # # Source: https://www.alibaba.com/product-detail/50mm-Tube-Settler-Media-Modules-Inclined_1600835845218.html?spm=a2700.galleryofferlist.normal_offer.d_title.69135ff6o4kFPb - # base_cost_v_notch_weir = 6888 - # base_flow_v_notch_weir = 10 # in m3/hr - # C['v notch weir'] = D['Number of clarifiers']*base_cost_v_notch_weir*(clarifier_flow/base_flow_v_notch_weir)**0.6 + # v notch weir + # Source: https://www.alibaba.com/product-detail/50mm-Tube-Settler-Media-Modules-Inclined_1600835845218.html?spm=a2700.galleryofferlist.normal_offer.d_title.69135ff6o4kFPb + base_cost_v_notch_weir = 6888 + base_flow_v_notch_weir = 10 # in m3/hr + C['v notch weir'] = D['Number of clarifiers']*base_cost_v_notch_weir*(clarifier_flow/base_flow_v_notch_weir)**0.6 - # # Pump (construction and maintainance) - # pumps = self.pumps - # add_OPEX = self.add_OPEX - # pump_cost = 0. - # building_cost = 0. - # opex_o = 0. - # opex_m = 0. + # Pump (construction and maintainance) + pumps = self.pumps + add_OPEX = self.add_OPEX + pump_cost = 0. + building_cost = 0. + opex_o = 0. + opex_m = 0. - # for i in pumps: - # p = getattr(self, f'{i}_pump') - # p_cost = p.baseline_purchase_costs - # p_add_opex = p.add_OPEX - # pump_cost += p_cost['Pump'] - # building_cost += p_cost['Pump building'] - # opex_o += p_add_opex['Pump operating'] - # opex_m += p_add_opex['Pump maintenance'] - - # N = D['Number of clarifiers'] - # C['Pumps'] = pump_cost*N - # C['Pump building'] = building_cost*N - # add_OPEX['Pump operating'] = opex_o*N - # add_OPEX['Pump maintenance'] = opex_m*N + for i in pumps: + p = getattr(self, f'{i}_pump') + p_cost = p.baseline_purchase_costs + p_add_opex = p.add_OPEX + pump_cost += p_cost['Pump'] + building_cost += p_cost['Pump building'] + opex_o += p_add_opex['Pump operating'] + opex_m += p_add_opex['Pump maintenance'] + + N = D['Number of clarifiers'] + C['Pumps'] = pump_cost*N + C['Pump building'] = building_cost*N + add_OPEX['Pump operating'] = opex_o*N + add_OPEX['Pump maintenance'] = opex_m*N - # # Power - # pumping = 0. - # for ID in self.pumps: - # p = getattr(self, f'{ID}_pump') - # if p is None: - # continue - # pumping += p.power_utility.rate + # Power + pumping = 0. + for ID in self.pumps: + p = getattr(self, f'{ID}_pump') + if p is None: + continue + pumping += p.power_utility.rate - # self.power_utility.rate += pumping*N - # # self.power_utility.rate += scraper_power + self.power_utility.rate += pumping*N + # self.power_utility.rate += scraper_power From d86c7f0b046d471e61234ab55978bad11bc110c0 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Wed, 2 Oct 2024 10:38:55 -0700 Subject: [PATCH 412/424] Update `PrimaryClarifier._design_pump()` --- qsdsan/sanunits/_clarifier.py | 196 +++++++++++++++------------------- 1 file changed, 84 insertions(+), 112 deletions(-) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index c9ce2db7..448891fa 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -1317,7 +1317,7 @@ class PrimaryClarifier(IdealClarifier): """ _N_ins = 1 - _N_outs = 2 + _N_outs = 2 # [0] overflow effluent [1] underflow sludge _ins_size_is_fixed = False _outs_size_is_fixed = True @@ -1328,8 +1328,6 @@ class PrimaryClarifier(IdealClarifier): # slab_concrete_unit_cost = 582.48 # $/m3 (Hydromantis. CapdetWorks 4.0. https://www.hydromantis.com/CapdetWorks.html) # stainless_steel_unit_cost=1.8 # Alibaba. Brushed Stainless Steel Plate 304. https://www.alibaba.com/product-detail/brushed-stainless-steel-plate-304l-stainless_1600391656401.html?spm=a2700.details.0.0.230e67e6IKwwFd - # pumps = ('sludge',) - def __init__(self, ID='', ins=None, outs=(), sludge_flow_rate=2000, solids_removal_efficiency=0.6, sludge_MLSS=None, thermo=None, isdynamic=False, @@ -1347,7 +1345,17 @@ def __init__(self, ID='', ins=None, outs=(), self.depth_clarifier = depth_clarifier self.downward_flow_velocity = downward_flow_velocity self.F_BM.update(F_BM) - self._sludge = WasteStream(f'{ID}_sludge') + self._sludge = uf = WasteStream(f'{ID}_sludge') + pump_id = f'{ID}_sludge_pump' + pump = WWTpump( + ID=pump_id, ins=uf, thermo=thermo, pump_type='sludge', + prefix=f'{ID}_sludge', + include_pump_cost=True, + include_building_cost=False, + include_OM_cost=True, + ) + setattr(self, pump_id, pump) + self.auxiliary_unit_names = tuple({*self.auxiliary_unit_names, pump_id}) # @property # def solids_loading_rate(self): @@ -1361,48 +1369,13 @@ def __init__(self, ID='', ins=None, outs=(), # else: # raise ValueError('solids_loading_rate of the clarifier expected from user') - # def _design_pump(self): - # ID, pumps = self.ID, self.pumps - # sludge = self._sludge - # sludge.copy_like(self.outs[1]) - - # ins_dct = { - # 'sludge': sludge, - # } - - # type_dct = dict.fromkeys(pumps, 'sludge') - # inputs_dct = dict.fromkeys(pumps, (1,)) - - # D = self.design_results - # influent_Q = sludge.get_total_flow('m3/hr')/D['Number of clarifiers'] - # influent_Q_mgd = influent_Q*0.00634 # m3/hr to MGD - - # for i in pumps: - # if hasattr(self, f'{i}_pump'): - # p = getattr(self, f'{i}_pump') - # setattr(p, 'add_inputs', inputs_dct[i]) - # else: - # ID = f'{ID}_{i}' - # capacity_factor=1 - # pump = WWTpump( - # ID=ID, ins= ins_dct[i], thermo = self.thermo, pump_type=type_dct[i], - # Q_mgd=influent_Q_mgd, add_inputs=inputs_dct[i], - # capacity_factor=capacity_factor, - # include_pump_cost=True, - # include_building_cost=False, - # include_OM_cost=True, - # ) - - # setattr(self, f'{i}_pump', pump) - - # pipe_ss, pump_ss = 0., 0. - # for i in pumps: - # p = getattr(self, f'{i}_pump') - # p.simulate() - # p_design = p.design_results - # pipe_ss += p_design['Pump pipe stainless steel'] - # pump_ss += p_design['Pump stainless steel'] - # return pipe_ss, pump_ss + def _design_pump(self): + D = self.design_results + field = f'{self.ID}_sludge_pump' + pump = getattr(self, field) + self._sludge.copy_like(self.outs[1]) + pump.simulate() + D.update(pump.design_results) _units = { 'Number of clarifiers': 'ea', @@ -1424,11 +1397,13 @@ def __init__(self, ID='', ins=None, outs=(), 'Volume of concrete wall': 'm3', 'Volume of concrete slab': 'm3', 'Stainless steel': 'kg', - 'Pump pipe stainless steel' : 'kg', - 'Pump stainless steel': 'kg', + # 'Pump pipe stainless steel' : 'kg', + # 'Pump stainless steel': 'kg', 'Number of pumps': 'ea' } + density_ss = 7930 # kg/m3, 18/8 Chromium + def _design(self): mixed = self._mixed mixed.mix_from(self.ins) @@ -1475,7 +1450,7 @@ def _design(self): # The design here is for center feed of clarifier. # Depth of the center feed lies between 30-75% of sidewater depth. [3, 4] - D['Center feed depth'] = 0.5*h_cyl + h_cf = D['Center feed depth'] = 0.5*h_cyl # Typical conventional feed wells are designed for an average downflow velocity # of 10-13 mm/s and maximum velocity of 25-30 mm/s. [4] v_down = D['Downward flow velocity'] = self.downward_flow_velocity*self.peak_flow_safety_factor # in m/hr @@ -1508,81 +1483,78 @@ def _design(self): # Amount of metal required for center feed #!!! consider empirical estimation of steel volume for all equipment (besides center feed, e.g., scrapper, support column, EDI, skimmer, walkway etc.) - thickness_metal_wall = 0.3048 # equal to 1 feet, in m (!! NEED A RELIABLE SOURCE !!) - inner_diameter_center_feed = D['Center feed diameter'] - outer_diameter_center_feed = inner_diameter_center_feed + 2*thickness_metal_wall - volume_center_feed = (3.14*D['Center feed depth']/4)*(outer_diameter_center_feed**2 - inner_diameter_center_feed**2) - density_ss = 7930 # kg/m3, 18/8 Chromium - D['Stainless steel'] = volume_center_feed*density_ss # in kg + d_wall_cf = 0.3048 # equal to 1 feet, in m (!! NEED A RELIABLE SOURCE !!) + OD_cf = dia_cf + 2*d_wall_cf + volume_center_feed = (pi*h_cf/4)*(OD_cf**2 - dia_cf**2) + D['Stainless steel'] = volume_center_feed*self.density_ss # in kg # Pumps - pipe, pumps = self._design_pump() - D['Pump pipe stainless steel'] = pipe - D['Pump stainless steel'] = pumps + self._design_pump() + - def _cost(self): - D = self.design_results - C = self.baseline_purchase_costs - N = D['Number of clarifiers'] + # def _cost(self): + # D = self.design_results + # C = self.baseline_purchase_costs + # N = D['Number of clarifiers'] - # Construction of concrete and stainless steel walls - C['Wall concrete'] = N*D['Volume of concrete wall']*self.wall_concrete_unit_cost - C['Slab concrete'] = N*D['Volume of concrete slab']*self.slab_concrete_unit_cost - C['Wall stainless steel'] = N*D['Stainless steel']*self.stainless_steel_unit_cost + # # Construction of concrete and stainless steel walls + # C['Wall concrete'] = N*D['Volume of concrete wall']*self.wall_concrete_unit_cost + # C['Slab concrete'] = N*D['Volume of concrete slab']*self.slab_concrete_unit_cost + # C['Wall stainless steel'] = N*D['Stainless steel']*self.stainless_steel_unit_cost - # Cost of equipment + # # Cost of equipment - # Source of scaling exponents: Process Design and Economics for Biochemical Conversion of Lignocellulosic Biomass to Ethanol by NREL. + # # Source of scaling exponents: Process Design and Economics for Biochemical Conversion of Lignocellulosic Biomass to Ethanol by NREL. - # Scraper - # Source: https://www.alibaba.com/product-detail/Peripheral-driving-clarifier-mud-scraper-waste_1600891102019.html?spm=a2700.details.0.0.47ab45a4TP0DLb - # base_cost_scraper = 2500 - # base_flow_scraper = 1 # in m3/hr (!!! Need to know whether this is for solids or influent !!!) - clarifier_flow = D['Volumetric flow']/24 + # # Scraper + # # Source: https://www.alibaba.com/product-detail/Peripheral-driving-clarifier-mud-scraper-waste_1600891102019.html?spm=a2700.details.0.0.47ab45a4TP0DLb + # # base_cost_scraper = 2500 + # # base_flow_scraper = 1 # in m3/hr (!!! Need to know whether this is for solids or influent !!!) + # clarifier_flow = D['Volumetric flow']/24 - # C['Scraper'] = D['Number of clarifiers']*base_cost_scraper*(clarifier_flow/base_flow_scraper)**0.6 + # # C['Scraper'] = D['Number of clarifiers']*base_cost_scraper*(clarifier_flow/base_flow_scraper)**0.6 - # base_power_scraper = 2.75 # in kW - # THE EQUATION BELOW IS NOT CORRECT TO SCALE SCRAPER POWER REQUIREMENTS - # scraper_power = D['Number of clarifiers']*base_power_scraper*(clarifier_flow/base_flow_scraper)**0.6 + # # base_power_scraper = 2.75 # in kW + # # THE EQUATION BELOW IS NOT CORRECT TO SCALE SCRAPER POWER REQUIREMENTS + # # scraper_power = D['Number of clarifiers']*base_power_scraper*(clarifier_flow/base_flow_scraper)**0.6 - # v notch weir - # Source: https://www.alibaba.com/product-detail/50mm-Tube-Settler-Media-Modules-Inclined_1600835845218.html?spm=a2700.galleryofferlist.normal_offer.d_title.69135ff6o4kFPb - base_cost_v_notch_weir = 6888 - base_flow_v_notch_weir = 10 # in m3/hr - C['v notch weir'] = D['Number of clarifiers']*base_cost_v_notch_weir*(clarifier_flow/base_flow_v_notch_weir)**0.6 + # # v notch weir + # # Source: https://www.alibaba.com/product-detail/50mm-Tube-Settler-Media-Modules-Inclined_1600835845218.html?spm=a2700.galleryofferlist.normal_offer.d_title.69135ff6o4kFPb + # base_cost_v_notch_weir = 6888 + # base_flow_v_notch_weir = 10 # in m3/hr + # C['v notch weir'] = D['Number of clarifiers']*base_cost_v_notch_weir*(clarifier_flow/base_flow_v_notch_weir)**0.6 - # Pump (construction and maintainance) - pumps = self.pumps - add_OPEX = self.add_OPEX - pump_cost = 0. - building_cost = 0. - opex_o = 0. - opex_m = 0. + # # Pump (construction and maintainance) + # pumps = self.pumps + # add_OPEX = self.add_OPEX + # pump_cost = 0. + # building_cost = 0. + # opex_o = 0. + # opex_m = 0. - for i in pumps: - p = getattr(self, f'{i}_pump') - p_cost = p.baseline_purchase_costs - p_add_opex = p.add_OPEX - pump_cost += p_cost['Pump'] - building_cost += p_cost['Pump building'] - opex_o += p_add_opex['Pump operating'] - opex_m += p_add_opex['Pump maintenance'] - - N = D['Number of clarifiers'] - C['Pumps'] = pump_cost*N - C['Pump building'] = building_cost*N - add_OPEX['Pump operating'] = opex_o*N - add_OPEX['Pump maintenance'] = opex_m*N + # for i in pumps: + # p = getattr(self, f'{i}_pump') + # p_cost = p.baseline_purchase_costs + # p_add_opex = p.add_OPEX + # pump_cost += p_cost['Pump'] + # building_cost += p_cost['Pump building'] + # opex_o += p_add_opex['Pump operating'] + # opex_m += p_add_opex['Pump maintenance'] + + # N = D['Number of clarifiers'] + # C['Pumps'] = pump_cost*N + # C['Pump building'] = building_cost*N + # add_OPEX['Pump operating'] = opex_o*N + # add_OPEX['Pump maintenance'] = opex_m*N - # Power - pumping = 0. - for ID in self.pumps: - p = getattr(self, f'{ID}_pump') - if p is None: - continue - pumping += p.power_utility.rate + # # Power + # pumping = 0. + # for ID in self.pumps: + # p = getattr(self, f'{ID}_pump') + # if p is None: + # continue + # pumping += p.power_utility.rate - self.power_utility.rate += pumping*N - # self.power_utility.rate += scraper_power + # self.power_utility.rate += pumping*N + # # self.power_utility.rate += scraper_power From 7453eb4cdb1492b51c4fcb12bbc728a2c83737fa Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Wed, 2 Oct 2024 11:30:36 -0700 Subject: [PATCH 413/424] clean up `PrimarycClarifier._cost()` --- qsdsan/sanunits/_clarifier.py | 100 +++++++++++++--------------------- 1 file changed, 37 insertions(+), 63 deletions(-) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index 448891fa..4f5c728e 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -1347,15 +1347,15 @@ def __init__(self, ID='', ins=None, outs=(), self.F_BM.update(F_BM) self._sludge = uf = WasteStream(f'{ID}_sludge') pump_id = f'{ID}_sludge_pump' - pump = WWTpump( + self.sludge_pump = WWTpump( ID=pump_id, ins=uf, thermo=thermo, pump_type='sludge', - prefix=f'{ID}_sludge', + prefix='Sludge', include_pump_cost=True, include_building_cost=False, include_OM_cost=True, ) - setattr(self, pump_id, pump) - self.auxiliary_unit_names = tuple({*self.auxiliary_unit_names, pump_id}) + + # self.auxiliary_unit_names = tuple({*self.auxiliary_unit_names, pump_id}) # @property # def solids_loading_rate(self): @@ -1371,9 +1371,11 @@ def __init__(self, ID='', ins=None, outs=(), def _design_pump(self): D = self.design_results + N = D['Number of pumps'] field = f'{self.ID}_sludge_pump' pump = getattr(self, field) self._sludge.copy_like(self.outs[1]) + self._sludge.scale(1/N) pump.simulate() D.update(pump.design_results) @@ -1490,71 +1492,43 @@ def _design(self): # Pumps self._design_pump() - - - # def _cost(self): - # D = self.design_results - # C = self.baseline_purchase_costs - # N = D['Number of clarifiers'] + def _cost(self): + D = self.design_results + C = self.baseline_purchase_costs + N = D['Number of clarifiers'] - # # Construction of concrete and stainless steel walls - # C['Wall concrete'] = N*D['Volume of concrete wall']*self.wall_concrete_unit_cost - # C['Slab concrete'] = N*D['Volume of concrete slab']*self.slab_concrete_unit_cost - # C['Wall stainless steel'] = N*D['Stainless steel']*self.stainless_steel_unit_cost + # Construction of concrete and stainless steel walls + C['Wall concrete'] = N*D['Volume of concrete wall']*self.wall_concrete_unit_cost + C['Slab concrete'] = N*D['Volume of concrete slab']*self.slab_concrete_unit_cost + C['Wall stainless steel'] = N*D['Stainless steel']*self.stainless_steel_unit_cost - # # Cost of equipment + # Cost of equipment - # # Source of scaling exponents: Process Design and Economics for Biochemical Conversion of Lignocellulosic Biomass to Ethanol by NREL. + # Source of scaling exponents: Process Design and Economics for Biochemical Conversion of Lignocellulosic Biomass to Ethanol by NREL. - # # Scraper - # # Source: https://www.alibaba.com/product-detail/Peripheral-driving-clarifier-mud-scraper-waste_1600891102019.html?spm=a2700.details.0.0.47ab45a4TP0DLb - # # base_cost_scraper = 2500 - # # base_flow_scraper = 1 # in m3/hr (!!! Need to know whether this is for solids or influent !!!) - # clarifier_flow = D['Volumetric flow']/24 + # Scraper + # Source: https://www.alibaba.com/product-detail/Peripheral-driving-clarifier-mud-scraper-waste_1600891102019.html?spm=a2700.details.0.0.47ab45a4TP0DLb + # base_cost_scraper = 2500 + # base_flow_scraper = 1 # in m3/hr (!!! Need to know whether this is for solids or influent !!!) + Q = D['Volumetric flow']/24 - # # C['Scraper'] = D['Number of clarifiers']*base_cost_scraper*(clarifier_flow/base_flow_scraper)**0.6 + # C['Scraper'] = D['Number of clarifiers']*base_cost_scraper*(clarifier_flow/base_flow_scraper)**0.6 - # # base_power_scraper = 2.75 # in kW - # # THE EQUATION BELOW IS NOT CORRECT TO SCALE SCRAPER POWER REQUIREMENTS - # # scraper_power = D['Number of clarifiers']*base_power_scraper*(clarifier_flow/base_flow_scraper)**0.6 + # base_power_scraper = 2.75 # in kW + # THE EQUATION BELOW IS NOT CORRECT TO SCALE SCRAPER POWER REQUIREMENTS + # scraper_power = D['Number of clarifiers']*base_power_scraper*(clarifier_flow/base_flow_scraper)**0.6 - # # v notch weir - # # Source: https://www.alibaba.com/product-detail/50mm-Tube-Settler-Media-Modules-Inclined_1600835845218.html?spm=a2700.galleryofferlist.normal_offer.d_title.69135ff6o4kFPb - # base_cost_v_notch_weir = 6888 - # base_flow_v_notch_weir = 10 # in m3/hr - # C['v notch weir'] = D['Number of clarifiers']*base_cost_v_notch_weir*(clarifier_flow/base_flow_v_notch_weir)**0.6 + # v notch weir + # Source: https://www.alibaba.com/product-detail/50mm-Tube-Settler-Media-Modules-Inclined_1600835845218.html?spm=a2700.galleryofferlist.normal_offer.d_title.69135ff6o4kFPb + base_cost_v_notch_weir = 6888 + base_flow_v_notch_weir = 10 # in m3/hr + C['v notch weir'] = N*base_cost_v_notch_weir*(Q/base_flow_v_notch_weir)**0.6 - # # Pump (construction and maintainance) - # pumps = self.pumps - # add_OPEX = self.add_OPEX - # pump_cost = 0. - # building_cost = 0. - # opex_o = 0. - # opex_m = 0. - - # for i in pumps: - # p = getattr(self, f'{i}_pump') - # p_cost = p.baseline_purchase_costs - # p_add_opex = p.add_OPEX - # pump_cost += p_cost['Pump'] - # building_cost += p_cost['Pump building'] - # opex_o += p_add_opex['Pump operating'] - # opex_m += p_add_opex['Pump maintenance'] - - # N = D['Number of clarifiers'] - # C['Pumps'] = pump_cost*N - # C['Pump building'] = building_cost*N - # add_OPEX['Pump operating'] = opex_o*N - # add_OPEX['Pump maintenance'] = opex_m*N - - # # Power - # pumping = 0. - # for ID in self.pumps: - # p = getattr(self, f'{ID}_pump') - # if p is None: - # continue - # pumping += p.power_utility.rate - - # self.power_utility.rate += pumping*N - # # self.power_utility.rate += scraper_power + # Pump (construction and maintainance) + pump = self.sludge_pump + add_OPEX = self.add_OPEX + add_OPEX.update({k: v*N for k,v in pump.add_OPEX.items()}) + C.update({k: v*N for k,v in pump.baseline_purchase_costs}) + self.power_utility.rate += pump.power_utility.rate*N + # self.power_utility.rate += scraper_power From c3a6c981bf5bfc1e446c021955f2e822ba39a95c Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Wed, 2 Oct 2024 11:41:05 -0700 Subject: [PATCH 414/424] minor bug fix --- qsdsan/sanunits/_clarifier.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index 4f5c728e..870d264e 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -1373,7 +1373,7 @@ def _design_pump(self): D = self.design_results N = D['Number of pumps'] field = f'{self.ID}_sludge_pump' - pump = getattr(self, field) + pump = self.sludge_pump self._sludge.copy_like(self.outs[1]) self._sludge.scale(1/N) pump.simulate() From 1889e1080b694f260a0fc8b61c0a68e6a76fc57b Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Wed, 2 Oct 2024 11:50:04 -0700 Subject: [PATCH 415/424] minor bug fix --- qsdsan/sanunits/_clarifier.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index 870d264e..ca6b7470 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -1323,10 +1323,10 @@ class PrimaryClarifier(IdealClarifier): peak_flow_safety_factor = 2.5 # assumed based on average and maximum velocities - # # Costs - # wall_concrete_unit_cost = 1081.73 # $/m3 (Hydromantis. CapdetWorks 4.0. https://www.hydromantis.com/CapdetWorks.html) - # slab_concrete_unit_cost = 582.48 # $/m3 (Hydromantis. CapdetWorks 4.0. https://www.hydromantis.com/CapdetWorks.html) - # stainless_steel_unit_cost=1.8 # Alibaba. Brushed Stainless Steel Plate 304. https://www.alibaba.com/product-detail/brushed-stainless-steel-plate-304l-stainless_1600391656401.html?spm=a2700.details.0.0.230e67e6IKwwFd + # Costs + wall_concrete_unit_cost = 1081.73 # $/m3 (Hydromantis. CapdetWorks 4.0. https://www.hydromantis.com/CapdetWorks.html) + slab_concrete_unit_cost = 582.48 # $/m3 (Hydromantis. CapdetWorks 4.0. https://www.hydromantis.com/CapdetWorks.html) + stainless_steel_unit_cost=1.8 # Alibaba. Brushed Stainless Steel Plate 304. https://www.alibaba.com/product-detail/brushed-stainless-steel-plate-304l-stainless_1600391656401.html?spm=a2700.details.0.0.230e67e6IKwwFd def __init__(self, ID='', ins=None, outs=(), sludge_flow_rate=2000, solids_removal_efficiency=0.6, @@ -1372,7 +1372,6 @@ def __init__(self, ID='', ins=None, outs=(), def _design_pump(self): D = self.design_results N = D['Number of pumps'] - field = f'{self.ID}_sludge_pump' pump = self.sludge_pump self._sludge.copy_like(self.outs[1]) self._sludge.scale(1/N) From ba4eddfc7c99f001d272498e4b125eb7962da8bf Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Mon, 14 Oct 2024 13:43:33 -0700 Subject: [PATCH 416/424] Update _sludge_treatment.py --- qsdsan/sanunits/_sludge_treatment.py | 573 +++------------------------ 1 file changed, 57 insertions(+), 516 deletions(-) diff --git a/qsdsan/sanunits/_sludge_treatment.py b/qsdsan/sanunits/_sludge_treatment.py index 70b6201d..35c0d8e6 100644 --- a/qsdsan/sanunits/_sludge_treatment.py +++ b/qsdsan/sanunits/_sludge_treatment.py @@ -42,14 +42,17 @@ #%% Thickener def calc_f_thick(thickener_perc, TSS_in): + """Returns thickening factor, i.e., thickened sludge solid concentration to influent solids concentration""" if TSS_in > 0: - thickener_factor = thickener_perc*10000/TSS_in + thickener_factor = thickener_perc*10000/TSS_in # underlying assumption is density of mixed liquor = 1 kg/L = 1e6 mg/L if thickener_factor < 1: thickener_factor = 1 return thickener_factor else: raise ValueError(f'Influent TSS is not valid: ({TSS_in:.2f} mg/L).') def calc_f_Qu_thin(TSS_removal_perc, thickener_factor): + """Returns Qu factor (i.e., underflow flowrate to influent flowrate) and + thinning factor (i.e., overflow solids concentration to influent solids concentration)""" if thickener_factor <= 1: Qu_factor = 1 thinning_factor=0 @@ -170,17 +173,10 @@ class Thickener(SanUnit): _N_outs = 2 # [0] thickened sludge, [1] reject water _ins_size_is_fixed = False _outs_size_is_fixed = False - - # # Costs - # wall_concrete_unit_cost = 1081.73 # $/m3 (Hydromantis. CapdetWorks 4.0. https://www.hydromantis.com/CapdetWorks.html) - # slab_concrete_unit_cost = 582.48 # $/m3 (Hydromantis. CapdetWorks 4.0. https://www.hydromantis.com/CapdetWorks.html) - # stainless_steel_unit_cost=1.8 # Alibaba. Brushed Stainless Steel Plate 304. https://www.alibaba.com/product-detail/brushed-stainless-steel-plate-304l-stainless_1600391656401.html?spm=a2700.details.0.0.230e67e6IKwwFd - - # pumps = ('sludge',) - + def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, init_with='WasteStream', F_BM_default=default_F_BM, thickener_perc=7, - TSS_removal_perc=98, solids_loading_rate =4, h_thickener=4, + TSS_removal_perc=98, solids_loading_rate=4, h_thickener=4, downward_flow_velocity= 36, F_BM=default_F_BM, **kwargs): SanUnit.__init__(self, ID, ins, outs, thermo, isdynamic=isdynamic, init_with=init_with) @@ -363,241 +359,11 @@ def yt(t, QC_ins, dQC_ins): C_dot = (dQ_ins @ C_ins + Q_ins @ dC_ins - Q_dot * C)/Q _dstate[-1] = Q_dot _dstate[:-1] = C_dot - _update_parameters() _update_state() _update_dstate() self._AE = yt - # def _design_pump(self): - # ID, pumps = self.ID, self.pumps - # self._sludge.copy_like(self.outs[0]) - # sludge = self._sludge - - # ins_dct = { - # 'sludge': sludge, - # } - - # type_dct = dict.fromkeys(pumps, 'sludge') - # inputs_dct = dict.fromkeys(pumps, (1,)) - - # D = self.design_results - # influent_Q = sludge.get_total_flow('m3/hr')/D['Number of thickeners'] - # influent_Q_mgd = influent_Q*0.00634 # m3/hr to MGD - - # for i in pumps: - # if hasattr(self, f'{i}_pump'): - # p = getattr(self, f'{i}_pump') - # setattr(p, 'add_inputs', inputs_dct[i]) - # else: - # ID = f'{ID}_{i}' - # capacity_factor=1 - # # No. of pumps = No. of influents - # pump = WWTpump( - # ID=ID, ins= ins_dct[i], thermo = self.thermo, pump_type=type_dct[i], - # Q_mgd=influent_Q_mgd, add_inputs=inputs_dct[i], - # capacity_factor=capacity_factor, - # include_pump_cost=True, - # include_building_cost=False, - # include_OM_cost=True, - # ) - # setattr(self, f'{i}_pump', pump) - - # pipe_ss, pump_ss = 0., 0. - # for i in pumps: - # p = getattr(self, f'{i}_pump') - # p.simulate() - # p_design = p.design_results - # pipe_ss += p_design['Pump pipe stainless steel'] - # pump_ss += p_design['Pump stainless steel'] - # return pipe_ss, pump_ss - - # _units = { - # 'Design solids loading rate': 'kg/m2/hr', - # 'Total mass of solids handled': 'kg', - # 'Surface area': 'm2', - # 'Thickener diameter': 'm', - # 'Number of thickeners' : 'Unitless', - # 'Thickener depth': 'm', - # 'Conical depth': 'm', - # 'Cylindrical depth': 'm', - # 'Cylindrical volume': 'm3', - # 'Conical volume': 'm3', - # 'Thickener volume': 'm3', - - # 'Center feed depth': 'm', - # 'Downward flow velocity': 'm/hr', - # 'Volumetric flow': 'm3/hr', - # 'Center feed diameter': 'm', - # 'Thickener depth': 'm', - # 'Volume of concrete wall': 'm3', - # 'Volume of concrete slab': 'm3', - # 'Stainless steel': 'kg', - # 'Pump pipe stainless steel' : 'kg', - # 'Pump stainless steel': 'kg', - # 'Number of pumps': 'Unitless' - # } - - # def _design(self): - - # self._mixed.mix_from(self.ins) - # mixed = self._mixed - # D = self.design_results - - # # D['Number of thickeners'] = np.ceil(self._mixed.get_total_flow('m3/hr')/self.design_flow) - # D['Design solids loading rate'] = self.solids_loading_rate # in (kg/hr)/m2 - # D['Total mass of solids handled'] = (mixed.get_TSS()/1000)*mixed.get_total_flow('m3/hr') # (mg/L)*[1/1000(kg*L)/(mg*m3)](m3/hr) = (kg/hr) - - # # Common gravity thickener configurations have tanks with diameter between 21-24m (MOP 8) - # diameter_thickener = 24 - # number_of_thickeners = 0 - # while diameter_thickener >= 22: - # number_of_thickeners += 1 - # total_surface_area = D['Total mass of solids handled']/D['Design solids loading rate'] #m2 - # surface_area_thickener = total_surface_area/number_of_thickeners - # diameter_thickener = np.sqrt(4*surface_area_thickener/np.pi) - - # D['Surface area'] = surface_area_thickener #in m2 - # D['Thickener diameter'] = diameter_thickener #in m - # D['Number of thickeners'] = number_of_thickeners - - # # Common gravity thickener configurations have sidewater depth between 3-4m (MOP 8) - # D['Thickener depth'] = self.h_thickener #in m - # # The thickener tank floor generally has slope between 2:12 and 3:12 (MOP 8) - # D['Conical depth'] = (2/12)*(D['Thickener diameter']/2) - # D['Cylindrical depth'] = D['Thickener depth'] - D['Conical depth'] - - # # Checks on depth - # if D['Cylindrical depth'] < 0: - # cyl_depth = D['Cylindrical depth'] - # RuntimeError(f'Cylindrical depth (= {cyl_depth} ) is negative') - - # if D['Cylindrical depth'] < D['Conical depth']: - # cyl_depth = D['Cylindrical depth'] - # con_depth = D['Conical depth'] - # RuntimeError(f'Cylindrical depth (= {cyl_depth} ) is lower than Conical depth (= {con_depth})') - - # D['Cylindrical volume'] = np.pi*np.square(D['Thickener diameter']/2)*D['Cylindrical depth'] #in m3 - # D['Conical volume'] = (np.pi/3)*(D['Thickener diameter']/2)**2*D['Conical depth'] - # D['Thickener volume'] = D['Cylindrical volume'] + D['Conical volume'] - - # #Check on SOR is pending - - # # The design here is for center feed of thickener. - # # Depth of the center feed lies between 30-75% of sidewater depth. [2] - # D['Center feed depth'] = 0.5*D['Cylindrical depth'] - # # Typical conventional feed wells are designed for an average downflow velocity - # # of 10-13 mm/s and maximum velocity of 25-30 mm/s. [4] - # peak_flow_safety_factor = 2.5 # assumed based on average and maximum velocities - # D['Downward flow velocity'] = self.downward_flow_velocity*peak_flow_safety_factor # in m/hr - - # D['Volumetric flow'] = mixed.get_total_flow('m3/hr')/D['Number of thickeners'] # m3/hr - # Center_feed_area = D['Volumetric flow']/D['Downward flow velocity'] # in m2 - # D['Center feed diameter'] = np.sqrt(4*Center_feed_area/np.pi) # in m - - # #Diameter of the center feed does not exceed 40% of tank diameter [2] - # if D['Center feed diameter'] > 0.40*D['Thickener diameter']: - # cf_dia = D['Center feed diameter'] - # tank_dia = D['Thickener diameter'] - # warn(f'Diameter of the center feed exceeds 40% of tank diameter. It is {cf_dia*100/tank_dia}% of tank diameter') - - # # Amount of concrete required - # D_tank = D['Thickener depth']*39.37 # m to inches - # # Thickness of the wall concrete [m]. Default to be minimum of 1 feet with 1 inch added for every feet of depth over 12 feet. - # thickness_concrete_wall = (1 + max(D_tank-12, 0)/12)*0.3048 # from feet to m - # inner_diameter = D['Thickener diameter'] - # outer_diameter = inner_diameter + 2*thickness_concrete_wall - # volume_cylindercal_wall = (np.pi*D['Cylindrical depth']/4)*(outer_diameter**2 - inner_diameter**2) - # D['Volume of concrete wall'] = volume_cylindercal_wall # in m3 - - # # Concrete slab thickness, [ft], default to be 2 in thicker than the wall thickness. (Brian's code) - # thickness_concrete_slab = thickness_concrete_wall + (2/12)*0.3048 # from inch to m - # outer_diameter_cone = inner_diameter + 2*(thickness_concrete_wall + thickness_concrete_slab) - # volume_conical_wall = (np.pi/(3*4))*(((D['Conical depth'] + thickness_concrete_wall + thickness_concrete_slab)*(outer_diameter_cone**2)) - (D['Conical depth']*(inner_diameter)**2)) - # D['Volume of concrete slab'] = volume_conical_wall - - # # Amount of metal required for center feed - # thickness_metal_wall = 0.3048 # equal to 1 feet, in m (!! NEED A RELIABLE SOURCE !!) - # inner_diameter_center_feed = D['Center feed diameter'] - # outer_diameter_center_feed = inner_diameter_center_feed + 2*thickness_metal_wall - # volume_center_feed = (3.14*D['Center feed depth']/4)*(outer_diameter_center_feed**2 - inner_diameter_center_feed**2) - # density_ss = 7930 # kg/m3, 18/8 Chromium - # D['Stainless steel'] = volume_center_feed*density_ss # in kg - - # # Pumps - # pipe, pumps = self._design_pump() - # D['Pump pipe stainless steel'] = pipe - # D['Pump stainless steel'] = pumps - - # #For thickener - # D['Number of pumps'] = D['Number of thickeners'] - - # def _cost(self): - - # self._mixed.mix_from(self.ins) - # mixed = self._mixed - - # D = self.design_results - # C = self.baseline_purchase_costs - - # # Construction of concrete and stainless steel walls - # C['Wall concrete'] = D['Number of thickeners']*D['Volume of concrete wall']*self.wall_concrete_unit_cost - # C['Slab concrete'] = D['Number of thickeners']*D['Volume of concrete slab']*self.slab_concrete_unit_cost - # C['Wall stainless steel'] = D['Number of thickeners']*D['Stainless steel']*self.stainless_steel_unit_cost - - # # Cost of equipment - # # Source of scaling exponents: Process Design and Economics for Biochemical Conversion of Lignocellulosic Biomass to Ethanol by NREL. - - # # Scraper - # # Source: https://www.alibaba.com/product-detail/Peripheral-driving-clarifier-mud-scraper-waste_1600891102019.html?spm=a2700.details.0.0.47ab45a4TP0DLb - # # base_cost_scraper = 2500 - # # base_flow_scraper = 1 # in m3/hr (!!! Need to know whether this is for solids or influent !!!) - # thickener_flow = mixed.get_total_flow('m3/hr')/D['Number of thickeners'] - # # C['Scraper'] = D['Number of thickeners']*base_cost_scraper*(thickener_flow/base_flow_scraper)**0.6 - # # base_power_scraper = 2.75 # in kW - # # THE EQUATION BELOW IS NOT CORRECT TO SCALE SCRAPER POWER REQUIREMENTS - # # scraper_power = D['Number of thickeners']*base_power_scraper*(thickener_flow/base_flow_scraper)**0.6 - - # # v notch weir - # # Source: https://www.alibaba.com/product-detail/50mm-Tube-Settler-Media-Modules-Inclined_1600835845218.html?spm=a2700.galleryofferlist.normal_offer.d_title.69135ff6o4kFPb - # base_cost_v_notch_weir = 6888 - # base_flow_v_notch_weir = 10 # in m3/hr - # C['v notch weir'] = D['Number of thickeners']*base_cost_v_notch_weir*(thickener_flow/base_flow_v_notch_weir)**0.6 - - # # Pump (construction and maintainance) - # pumps = self.pumps - # add_OPEX = self.add_OPEX - # pump_cost = 0. - # building_cost = 0. - # opex_o = 0. - # opex_m = 0. - - # for i in pumps: - # p = getattr(self, f'{i}_pump') - # p_cost = p.baseline_purchase_costs - # p_add_opex = p.add_OPEX - # pump_cost += p_cost['Pump'] - # building_cost += p_cost['Pump building'] - # opex_o += p_add_opex['Pump operating'] - # opex_m += p_add_opex['Pump maintenance'] - - # C['Pumps'] = pump_cost*D['Number of thickeners'] - # C['Pump building'] = building_cost*D['Number of thickeners'] - # add_OPEX['Pump operating'] = opex_o*D['Number of thickeners'] - # add_OPEX['Pump maintenance'] = opex_m*D['Number of thickeners'] - - # # Power - # pumping = 0. - # for ID in self.pumps: - # p = getattr(self, f'{ID}_pump') - # if p is None: - # continue - # pumping += p.power_utility.rate - - # pumping = pumping*D['Number of thickeners'] - - # self.power_utility.rate += pumping - # # self.power_utility.rate += scraper_power #%% Centrifuge @@ -657,16 +423,10 @@ class Centrifuge(Thickener): _N_outs = 2 _ins_size_is_fixed = False - pumps = ('sludge',) - - # # Costs - # stainless_steel_unit_cost=1.8 # $/Kg (Taken from Joy's METAB code) https://www.alibaba.com/product-detail/brushed-stainless-steel-plate-304l-stainless_1600391656401.html?spm=a2700.details.0.0.230e67e6IKwwFd - # polymer_cost_by_weight = 2.2 # $/Kg (Source: https://www.alibaba.com/product-detail/dewatering-pool-chemicals-cationic-polyacrylamide-cas_1600194474507.html?spm=a2700.galleryofferlist.topad_classic.i5.5de8615c4zGAhg) - def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, init_with='WasteStream', F_BM_default=default_F_BM, thickener_perc=28, TSS_removal_perc=98, - solids_feed_rate=70, g_factor=2500, rotational_speed = 2500, + solids_feed_rate=70, g_factor=2500, rotational_speed=2500, LtoD=4, F_BM=default_F_BM, polymer_dosage=20, h_cylindrical=2, h_conical=1, **kwargs): @@ -683,177 +443,13 @@ def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, self.h_cylindrical = h_cylindrical self.h_conical = h_conical - # _units = { - # 'Number of centrifuges': 'ea', - # 'Diameter of bowl': 'm', - # 'Total length of bowl': 'm', - # 'Length of cylindrical portion': 'm', - # 'Length of conical portion': 'm', - # 'Volume of bowl': 'm3', - # 'Stainless steel for bowl': 'kg', - # 'Polymer feed rate': 'kg/hr', - # 'Pump pipe stainless steel' : 'kg', - # 'Pump stainless steel': 'kg', - # 'Number of pumps': 'ea' - # } - - # def _design_pump(self): - # ID, pumps = self.ID, self.pumps - # self._sludge.copy_like(self.outs[0]) - # sludge = self._sludge - # ins_dct = { - # 'sludge': sludge, - # } - # type_dct = dict.fromkeys(pumps, 'sludge') - # inputs_dct = dict.fromkeys(pumps, (1,)) - - # D = self.design_results - # influent_Q = sludge.get_total_flow('m3/hr')/D['Number of centrifuges'] - # influent_Q_mgd = influent_Q*0.00634 # m3/hr to MGD - - # for i in pumps: - # if hasattr(self, f'{i}_pump'): - # p = getattr(self, f'{i}_pump') - # setattr(p, 'add_inputs', inputs_dct[i]) - # else: - # ID = f'{ID}_{i}' - # capacity_factor=1 - # # No. of pumps = No. of influents - # pump = WWTpump( - # ID=ID, ins= ins_dct[i], thermo = self.thermo, pump_type=type_dct[i], - # Q_mgd=influent_Q_mgd, add_inputs=inputs_dct[i], - # capacity_factor=capacity_factor, - # include_pump_cost=True, - # include_building_cost=False, - # include_OM_cost=True, - # ) - # setattr(self, f'{i}_pump', pump) - - # pipe_ss, pump_ss = 0., 0. - # for i in pumps: - # p = getattr(self, f'{i}_pump') - # p.simulate() - # p_design = p.design_results - # pipe_ss += p_design['Pump pipe stainless steel'] - # pump_ss += p_design['Pump stainless steel'] - # return pipe_ss, pump_ss - - # def _design(self): - # self._mixed.mix_from(self.ins) - # mixed = self._mixed - - # D = self.design_results - # TSS_rmv = self._TSS_rmv - # solids_feed_rate = 44.66*self.solids_feed_rate # 44.66 is factor to convert tonne/day to kg/hr - # # Cake's total solids and TSS are essentially the same (pg. 24-6 [3]) - # # If TSS_rmv = 98, then total_mass_dry_solids_removed = (0.98)*(influent TSS mass) - # total_mass_dry_solids_removed = (TSS_rmv/100)*((mixed.get_TSS()*self.ins[0].F_vol)/1000) # in kg/hr - # D['Number of centrifuges'] = np.ceil(total_mass_dry_solids_removed/solids_feed_rate) - - - # # HAVE COMMENTED ALL OF THIS SINCE CENTRIFUGE WOULD PROBABLY BE BROUGHT NOT CONSTRUCTED AT THE FACILITY - - # # k = 0.00000056 # Based on emprical formula (pg. 24-23 of [3]) - # # g = 9.81 # m/s2 - # # # The inner diameterof the bowl is calculated based on an empirical formula. 1000 is used to convert mm to m. - # # D['Diameter of bowl'] = (self.g_factor*g)/(k*np.square(self.rotational_speed)*1000) # in m - # # D['Total length of bowl'] = self.LtoD*D['Diameter of bowl'] - # # # Sanity check: L should be between 1-7 m, diameter should be around 0.25-0.8 m (Source: [4]) - - # # fraction_cylindrical_portion = 0.8 - # # fraction_conical_portion = 1 - fraction_cylindrical_portion - # # D['Length of cylindrical portion'] = fraction_cylindrical_portion*D['Total length of bowl'] - # # D['Length of conical portion'] = fraction_conical_portion*D['Total length of bowl'] - # # thickness_of_bowl_wall = 0.1 # in m (!!! NEED A RELIABLE SOURCE !!!) - # # inner_diameter = D['Diameter of bowl'] - # # outer_diameter = inner_diameter + 2*thickness_of_bowl_wall - - # # volume_cylindrical_wall = (np.pi*D['Length of cylindrical portion']/4)*(outer_diameter**2 - inner_diameter**2) - # # volume_conical_wall = (np.pi/3)*(D['Length of conical portion']/4)*(outer_diameter**2 - inner_diameter**2) - # # D['Volume of bowl'] = volume_cylindrical_wall + volume_conical_wall # in m3 - - # # density_ss = 7930 # kg/m3, 18/8 Chromium - # # D['Stainless steel for bowl'] = D['Volume of bowl']*density_ss # in kg - - # polymer_dosage_rate = 0.000453592*self.polymer_dosage # convert from (polymer (lbs)/solids (tonne)) to (polymer (kg)/solids (kg)) - # D['Polymer feed rate'] = (polymer_dosage_rate*solids_feed_rate) # in polymer (kg)/hr - - # # Pumps - # pipe, pumps = self._design_pump() - # D['Pump pipe stainless steel'] = pipe - # D['Pump stainless steel'] = pumps - - # # For centrifuges - # D['Number of pumps'] = D['Number of centrifuges'] - - # def _cost(self): - - # D = self.design_results - # C = self.baseline_purchase_costs - - # self._mixed.mix_from(self.ins) - # mixed = self._mixed - - # # HAVE COMMENTED SINCE CENTRIFUGE WOULD PROBABLY BE BROUGHT NOT CONSTRUCTED AT THE FACILITY - # # Construction of concrete and stainless steel walls - # # C['Bowl stainless steel'] = D['Number of centrifuges']*D['Stainless steel for bowl']*self.stainless_steel_unit_cost - - # # Conveyor - # # Source: https://www.alibaba.com/product-detail/Sludge-Dewatering-Centrifuge-Decanter-Centrifuge-For_60448094522.html?spm=a2700.galleryofferlist.p_offer.d_title.1c5c5229I5pQeP&s=p - # base_cost_centrifuge = 16000 - # base_mass_flow_centrifuge = 80 # in tonne/hr - # thickener_mass_flow = (mixed.get_total_flow('m3/hr')*mixed.get_TSS())/D['Number of centrifuges'] # IN gm/hr - # gm_to_tonne = 0.000001 - # thickener_mass_flow = thickener_mass_flow*gm_to_tonne - # C['Centrifuge'] = D['Number of centrifuges']*base_cost_centrifuge*(thickener_mass_flow/base_mass_flow_centrifuge)**0.6 - - # base_power_motor = 55 # in kW - # # THIS IS NOT THE CORRECT EXPRESSION TO SCALE UP POWER OF CENTRIFUGE - # motor_power = base_power_motor*(thickener_mass_flow/base_mass_flow_centrifuge) - # total_motor_power = D['Number of centrifuges']*motor_power - - # # Pump (construction and maintainance) - # pumps = self.pumps - # add_OPEX = self.add_OPEX - # pump_cost = 0. - # building_cost = 0. - # opex_o = 0. - # opex_m = 0. - - # add_OPEX['Polymer'] = D['Number of centrifuges']*D['Polymer feed rate']*self.polymer_cost_by_weight - - # for i in pumps: - # p = getattr(self, f'{i}_pump') - # p_cost = p.baseline_purchase_costs - # p_add_opex = p.add_OPEX - # pump_cost += p_cost['Pump'] - # building_cost += p_cost['Pump building'] - # opex_o += p_add_opex['Pump operating'] - # opex_m += p_add_opex['Pump maintenance'] - - # C['Pumps'] = pump_cost*D['Number of pumps'] - # C['Pump building'] = building_cost*D['Number of pumps'] - # add_OPEX['Pump operating'] = opex_o*D['Number of pumps'] - # add_OPEX['Pump maintenance'] = opex_m*D['Number of pumps'] - - # # Power - # pumping = 0. - # for ID in self.pumps: - # p = getattr(self, f'{ID}_pump') - # if p is None: - # continue - # pumping += p.power_utility.rate - - # pumping = pumping*D['Number of pumps'] - # self.power_utility.rate += pumping - # self.power_utility.rate += total_motor_power #%% Incinerator class Incinerator(SanUnit): """ - Fluidized bed incinerator unit for metroWWTP. + Fluidized bed incinerator. Parameters ---------- @@ -951,9 +547,9 @@ class Incinerator(SanUnit): def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, init_with='WasteStream', F_BM_default=None, process_efficiency=0.90, - calorific_value_sludge= 12000, calorific_value_fuel=50000, - ash_component_ID = 'X_Ig_ISS', nitrogen_ID = 'S_N2', water_ID = 'H2O', - carbon_di_oxide_ID = 'S_CO2', **kwargs): + calorific_value_sludge=12000, calorific_value_fuel=50000, + ash_component_ID='X_Ig_ISS', nitrogen_ID='S_N2', water_ID='H2O', + carbon_dioxide_ID='S_CO2', **kwargs): SanUnit.__init__(self, ID, ins, outs, thermo, isdynamic=isdynamic, init_with=init_with, F_BM_default=F_BM_default) @@ -963,7 +559,7 @@ def __init__(self, ID='', ins=None, outs=(), thermo=None, isdynamic=False, self.ash_component_ID = ash_component_ID self.nitrogen_ID = nitrogen_ID self.water_ID = water_ID - self.carbon_di_oxide_ID = carbon_di_oxide_ID + self.carbon_dioxide_ID = carbon_dioxide_ID self.Heat_air = None self.Heat_fuel = None self.Heat_sludge = None @@ -1017,7 +613,8 @@ def _run(self): cmps = self.components nitrogen_ID = self.nitrogen_ID water_ID = self.water_ID - carbon_di_oxide_ID = self.carbon_di_oxide_ID + carbon_dioxide_ID = self.carbon_dioxide_ID + ash_cmp_ID = self.ash_component_ID if sludge.phase != 'l': raise ValueError(f'The phase of incoming sludge is expected to be liquid not {sludge.phase}') @@ -1029,22 +626,23 @@ def _run(self): inf = np.asarray(sludge.mass + air.mass + fuel.mass) idx_n2 = cmps.index(nitrogen_ID) idx_h2o = cmps.index(water_ID) + idx_co2 = cmps.index(carbon_dioxide_ID) + idx_ash = cmps.index(ash_cmp_ID) + i_mass = cmps.i_mass + i_iss = i_mass*(1-cmps.f_Vmass_Totmass) n2 = inf[idx_n2] h2o = inf[idx_h2o] - mass_ash = np.sum(inf*cmps.i_mass*(1-cmps.f_Vmass_Totmass)) \ - - h2o*cmps.H2O.i_mass*(1-cmps.H2O.f_Vmass_Totmass) - n2*cmps.N2.i_mass*(1-cmps.N2.f_Vmass_Totmass) + mass_ash = np.sum(inf*i_iss) - n2*i_mass[idx_n2] - h2o*i_mass[idx_h2o] # Conservation of mass - mass_flue_gas = np.sum(inf*cmps.i_mass) - mass_ash - mass_co2 = mass_flue_gas - n2*cmps.N2.i_mass - h2o*cmps.H2O.i_mass + mass_flue_gas = np.sum(inf*i_mass) - mass_ash + mass_co2 = mass_flue_gas - n2*i_mass[idx_n2] - h2o*i_mass[idx_h2o] - flue_gas.set_flow([n2, h2o, (mass_co2/cmps.S_CO2.i_mass)], - 'kg/hr', (nitrogen_ID, water_ID, carbon_di_oxide_ID)) - ash_cmp_ID = self.ash_component_ID - ash_idx = cmps.index(ash_cmp_ID) - ash.set_flow([mass_ash/cmps.i_mass[ash_idx]/(1-cmps.f_Vmass_Totmass[ash_idx])],'kg/hr', (ash_cmp_ID,)) + flue_gas.set_flow([n2, h2o, (mass_co2/i_mass[idx_co2])], + 'kg/hr', (nitrogen_ID, water_ID, carbon_dioxide_ID)) + ash.set_flow(mass_ash/i_mass[idx_ash],'kg/hr', (ash_cmp_ID,)) # Energy balance # self.Heat_sludge = sludge.dry_mass*sludge.F_vol*self.calorific_value_sludge/1000 #in KJ/hr (mg/L)*(m3/hr)*(KJ/kg)=KJ/hr*(1/1000) @@ -1056,97 +654,35 @@ def _run(self): # self.Heat_loss = self.Heat_sludge + self.Heat_air + self.Heat_fuel - self.Heat_flue_gas def _init_state(self): - - sludge, air, fuel = self.ins - inf = np.asarray(sludge.mass + air.mass + fuel.mass) - self._state = (24*inf)/1000 + self._state = np.zeros(4) self._dstate = self._state * 0. self._cached_state = self._state.copy() self._cached_t = 0 - - # def _cost(self): - - # C = self.baseline_purchase_costs - # sludge, air, fuel = self.ins - # solids_load_treated = sludge.get_total_flow('m3/hr')*sludge.get_TSS('mg/L')/1000 # in kg/hr - - # # Based on regression equations obtained from CapdetWorks - # C['Construction and equipment costs'] = 119629*(solids_load_treated)**0.9282 - # C['Installed incinerator costs'] = 114834*(solids_load_treated)**0.9284 - # C['Slab concrete costs'] = 782.28*(solids_load_treated)**0.9111 - # C['Building costs'] = 4429.8*(solids_load_treated)**0.911 - - # # Based on regression equations obtained from CapdetWorks - # add_OPEX = self.add_OPEX - # add_OPEX['Material and supply costs'] = 0.0614*(solids_load_treated)**0.9282 - # add_OPEX['Energy costs'] = 1.3079*(solids_load_treated)**0.9901 - + def _update_state(self): cmps = self.components - for ws in self.outs: - if ws.state is None: - ws.state = np.zeros(len(self._state)+1) - - nitrogen_ID = self.nitrogen_ID - water_ID = self.water_ID - carbon_di_oxide_ID = self.carbon_di_oxide_ID - - idx_h2o = cmps.index(water_ID) - idx_n2 = cmps.index(nitrogen_ID) - idx_co2 = cmps.index(carbon_di_oxide_ID) - ash_idx = cmps.index(self.ash_component_ID) - cmps_i_mass = cmps.i_mass - cmps_v2tmass = cmps.f_Vmass_Totmass - - inf = self._state - mass_in_tot = np.sum(inf*cmps_i_mass) - self._outs[0].state[idx_h2o] = h2o = inf[idx_h2o] - self._outs[0].state[idx_n2] = n2 = inf[idx_n2] - - mass_ash = np.sum(inf*cmps_i_mass*(1-cmps_v2tmass)) \ - - h2o*cmps.H2O.i_mass*(1-cmps_v2tmass[idx_h2o]) - n2*cmps.N2.i_mass*(1-cmps_v2tmass[idx_n2]) - mass_flue_gas = mass_in_tot - mass_ash - mass_co2 = mass_flue_gas - n2 - h2o - - self._outs[0].state[idx_co2] = mass_co2/cmps_i_mass[idx_co2] - self._outs[1].state[ash_idx] = mass_ash/cmps_i_mass[ash_idx]/(1-cmps_v2tmass[ash_idx]) + flue_gas, ash = self.outs + idx_ash = cmps.index(self.ash_component_ID) + idx_gases = cmps.indices([self.carbon_dioxide_ID, self.nitrogen_ID, self.water_ID]) - self._outs[0].state[-1] = 1 - self._outs[1].state[-1] = 1 + if flue_gas.state is None: flue_gas.state = np.array([0]*len(cmps)+[1]) + if ash.state is None: ash.state = np.array([0]*len(cmps)+[1]) - def _update_dstate(self): + flue_gas.state[idx_gases] = self._state[1:] + ash.state[idx_ash] = self._state[0] + def _update_dstate(self): cmps = self.components - nitrogen_ID = self.nitrogen_ID - water_ID = self.water_ID - carbon_di_oxide_ID = self.carbon_di_oxide_ID - - idx_h2o = cmps.index(water_ID) - idx_n2 = cmps.index(nitrogen_ID) - idx_co2 = cmps.index(carbon_di_oxide_ID) - ash_idx = cmps.index(self.ash_component_ID) - d_state = self._dstate - cmps_i_mass = cmps.i_mass - cmps_v2tmass = cmps.f_Vmass_Totmass - d_n2 = d_state[idx_n2] - d_h2o = d_state[idx_h2o] - - for ws in self.outs: - if ws.dstate is None: - ws.dstate = np.zeros(len(self._dstate)+1) - - self._outs[0].dstate[idx_n2] = d_n2 - self._outs[0].dstate[idx_h2o] = d_h2o - - d_mass_in_tot = np.sum(d_state*cmps_i_mass) - - d_mass_ash = np.sum(d_state*cmps_i_mass*(1-cmps_v2tmass)) \ - - d_h2o*cmps.H2O.i_mass*(1-cmps_v2tmass[idx_h2o]) - d_n2*cmps.N2.i_mass*(1-cmps_v2tmass[idx_n2]) - d_mass_flue_gas = d_mass_in_tot - d_mass_ash - d_mass_co2 = d_mass_flue_gas - d_n2 - d_h2o + flue_gas, ash = self.outs + idx_ash = cmps.index(self.ash_component_ID) + idx_gases = cmps.indices([self.carbon_dioxide_ID, self.nitrogen_ID, self.water_ID]) + + if flue_gas.dstate is None: flue_gas.dstate = np.zeros(len(cmps)+1) + if ash.dstate is None: ash.dstate = np.zeros(len(cmps)+1) - self._outs[0].dstate[idx_co2] = d_mass_co2/cmps_i_mass[idx_co2] - self._outs[1].dstate[ash_idx] = d_mass_ash/cmps_i_mass[ash_idx]/(1-cmps_v2tmass[ash_idx]) + flue_gas.dstate[idx_gases] = self._dstate[1:] + ash.dstate[idx_ash] = self._dstate[0] + @property def AE(self): @@ -1160,17 +696,22 @@ def _compile_AE(self): _update_state = self._update_state _update_dstate = self._update_dstate _cached_state = self._cached_state - + cmps = self.components + idx_h2o = cmps.index(self.water_ID) + idx_n2 = cmps.index(self.nitrogen_ID) + idx_co2 = cmps.index(self.carbon_dioxide_ID) + idx_ash = cmps.index(self.ash_component_ID) + i_mass = cmps.i_mass + i_iss = i_mass*(1-cmps.f_Vmass_Totmass) + def yt(t, QC_ins, dQC_ins): - # Mass_in is basically the mass flowrate array where each row - # corresponds to the flowrates of individual components (in columns) - # This strcuture is achieved by multiplying the first (n-1) rows of - # Q_ins (which corresponds to concentration) to the nth row (which - # is the volumetric flowrate) Mass_ins = np.diag(QC_ins[:,-1]) @ QC_ins[:,:-1] - # the _state array is formed by adding each column of the Mass_in - # array, thus providing the total massflowrate of each component - _state[:] = np.sum(Mass_ins, axis=0) + n2 = Mass_ins[idx_n2] + h2o = Mass_ins[idx_h2o] + ash = np.sum(Mass_ins*i_iss) - n2*i_mass[idx_n2] - h2o*i_mass[idx_h2o] + co2 = np.sum(Mass_ins*i_mass) - ash - n2*i_mass[idx_n2] - h2o*i_mass[idx_h2o] + + _state[:] = [ash/i_mass[idx_ash], co2/i_mass[idx_co2], n2, h2o] if t > self._cached_t: _dstate[:] = (_state - _cached_state)/(t - self._cached_t) From 500b58b38f949227e36b97b9b26c90eca7f09305 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Mon, 14 Oct 2024 13:53:28 -0700 Subject: [PATCH 417/424] minor bug fix --- qsdsan/sanunits/_clarifier.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index ca6b7470..6b0ea7a4 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -1528,6 +1528,6 @@ def _cost(self): pump = self.sludge_pump add_OPEX = self.add_OPEX add_OPEX.update({k: v*N for k,v in pump.add_OPEX.items()}) - C.update({k: v*N for k,v in pump.baseline_purchase_costs}) + C.update({k: v*N for k,v in pump.baseline_purchase_costs.items()}) self.power_utility.rate += pump.power_utility.rate*N # self.power_utility.rate += scraper_power From 5e1f75b33aae3ccab9fb9f6438f04de46a9b11e6 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Tue, 15 Oct 2024 11:24:09 -0700 Subject: [PATCH 418/424] fix bug in MMP models --- qsdsan/processes/_adm1_p_extension.py | 8 +++++--- qsdsan/processes/_asm2d.py | 10 ++++++---- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/qsdsan/processes/_adm1_p_extension.py b/qsdsan/processes/_adm1_p_extension.py index 2171641d..5c2b2aae 100644 --- a/qsdsan/processes/_adm1_p_extension.py +++ b/qsdsan/processes/_adm1_p_extension.py @@ -775,7 +775,7 @@ def _rhos_adm1p(state_arr, params, h=None): if S_Ca > 0 and po4 > 0: SI = (S_Ca**3 * po4**2 / Ksp[3])**(1/5) - if SI > 1: rhos_p[28] = X_ACP * (SI-1)**5 + if SI > 1: rhos_p[28] = X_ACP * (SI-1)**2 if S_Mg > 0 and co3 > 0: SI = (S_Mg * co3 / Ksp[4])**(1/2) @@ -921,8 +921,10 @@ def __new__(cls, components=None, path=None, K_H_dH=[-4180, -14240, -19410], # k_mmp=(5.0, 300, 0.05, 150, 50, 1.0, 1.0), # pKsp=(6.45, 13.16, 5.8, 23, 7, 21, 26), - k_mmp=(0.024, 120, 0.024, 72, 0.024, 0.024, 0.024), # Flores-Alsina 2016 - pKsp=(8.3, 13.6, 18.175, 28.92, 7.46, 18.2, 37.76), # Flores-Alsina 2016 + # k_mmp=(0.024, 120, 0.024, 72, 0.024, 0.024, 0.024), # Flores-Alsina 2016 + # pKsp=(8.3, 13.6, 18.175, 28.92, 7.46, 18.2, 37.76), # Flores-Alsina 2016 + k_mmp=(8.4, 240, 1.0, 72, 1.0, 1.0, 1.0), # MATLAB + pKsp=(8.5, 13.7, 5.9, 28.6, 7.6, 18.2, 26.5), # MINTEQ (except newberyite), 35 C K_dis=(1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0), K_AlOH=1.0e-6, K_FeOH=1.0e-6, # kg/m3 **kwargs): diff --git a/qsdsan/processes/_asm2d.py b/qsdsan/processes/_asm2d.py index 9cbd6d94..2fba3f56 100644 --- a/qsdsan/processes/_asm2d.py +++ b/qsdsan/processes/_asm2d.py @@ -12,7 +12,7 @@ import numpy as np from thermosteam.utils import chemicals_user from thermosteam import settings -from qsdsan import Component, Components, Process, Processes, CompiledProcesses +from qsdsan import Component, Components, Processes, CompiledProcesses from ..utils import ospath, data_path, load_data from . import Monod, ion_speciation from scipy.optimize import brenth @@ -686,7 +686,7 @@ def _rhos_masm2d(state_arr, params, acceptor_dependent_decay=True, h=None): if SI > 1: rhos[21] = X_newb * (SI-1)**2 SI = (S_Ca**3 * po4**2 / Ksp[3])**(1/5) - if SI > 1: rhos[22] = X_ACP * (SI-1)**5 + if SI > 1: rhos[22] = X_ACP * (SI-1)**2 SI = (S_Mg * co3 / Ksp[4])**(1/2) if SI > 1: rhos[23] = X_MgCO3 * (SI-1)**2 @@ -825,10 +825,12 @@ def __new__(cls, components=None, path=None, K_NH4_H=0.05, K_NH4_PAO=0.05, K_NH4_AUT=1.0, K_P_H=0.01, K_P_PAO=0.01, K_P_AUT=0.01, K_P_S=0.2, K_PP=0.01, K_MAX=0.34, K_IPP=0.02, K_PHA=0.01, - k_mmp=(5.0, 300, 0.05, 150, 50, 1.0, 1.0), - pKsp=(6.45, 13.16, 5.8, 23, 7, 21, 26), + # k_mmp=(5.0, 300, 0.05, 150, 50, 1.0, 1.0), + # pKsp=(6.45, 13.16, 5.8, 23, 7, 21, 26), # k_mmp=(0.024, 120, 0.024, 72, 0.024, 0.024, 0.024), # Flores-Alsina 2016 # pKsp=(8.3, 13.6, 18.175, 28.92, 7.46, 18.2, 37.76), # Flores-Alsina 2016 + k_mmp=(8.4, 240, 1.0, 72, 1.0, 1.0e-5, 1.0e-5), # MATLAB + pKsp=(8.45, 13.5, 5.7, 29.1, 7.4, 18.2, 26.4), # MINTEQ (except newberyite), 20 C K_dis=(1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0), K_AlOH=0.001, K_FeOH=0.001, pKa=(14, 9.25, 6.37, 10.32, 2.12, 7.21, 12.32, 4.76), From a02354bd16244e21b4e262fc950c07217c508950 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Tue, 15 Oct 2024 13:30:26 -0700 Subject: [PATCH 419/424] clean up for merge --- qsdsan/processes/__init__.py | 15 +- qsdsan/processes/_adm1.py | 4 +- qsdsan/sanunits/_activated_sludge_process.py | 442 +------------------ 3 files changed, 19 insertions(+), 442 deletions(-) diff --git a/qsdsan/processes/__init__.py b/qsdsan/processes/__init__.py index dfeda1eb..5aacbc5a 100644 --- a/qsdsan/processes/__init__.py +++ b/qsdsan/processes/__init__.py @@ -37,8 +37,19 @@ def mass2mol_conversion(cmps): R = 8.3145e-2 # Universal gas constant, [bar/M/K] def T_correction_factor(T1, T2, delta_H): - """compute temperature correction factor for equilibrium constants based on - the Van't Holf equation.""" + """ + Returns temperature correction factor for equilibrium constants based on + the Van't Holf equation. + + Parameters + ---------- + T1 : float + Base temperature, in K. + T2 : float + Actual temperature, in K. + delta_H : float + Heat of reaction, in J/mol. + """ if T1 == T2: return 1 return exp(delta_H/(R*100) * (1/T1 - 1/T2)) # R converted to SI diff --git a/qsdsan/processes/_adm1.py b/qsdsan/processes/_adm1.py index e6a0ed13..c6c88505 100644 --- a/qsdsan/processes/_adm1.py +++ b/qsdsan/processes/_adm1.py @@ -217,8 +217,8 @@ def solve_pH(state_arr, Ka, unit_conversion): cmps_in_M = state_arr[:27] * unit_conversion weak_acids = cmps_in_M[[24, 25, 10, 9, 6, 5, 4, 3]] h = brenth(acid_base_rxn, 1e-14, 1.0, - args=(weak_acids, Ka), - xtol=1e-12, maxiter=100) + args=(weak_acids, Ka), + xtol=1e-12, maxiter=100) return h rhos_adm1 = lambda state_arr, params: _rhos_adm1(state_arr, params, h=None) diff --git a/qsdsan/sanunits/_activated_sludge_process.py b/qsdsan/sanunits/_activated_sludge_process.py index 3f3ae89e..14026924 100644 --- a/qsdsan/sanunits/_activated_sludge_process.py +++ b/qsdsan/sanunits/_activated_sludge_process.py @@ -18,22 +18,15 @@ from warnings import warn from math import ceil from biosteam import Stream -from .. import SanUnit, WasteStream -from ..sanunits import HXutility, WWTpump, Mixer - -from ..sanunits._pumping import ( - default_F_BM as default_WWTpump_F_BM, - default_equipment_lifetime as default_WWTpump_equipment_lifetime, - ) +from .. import SanUnit +from ..sanunits import HXutility, WWTpump from ..equipments import Blower, GasPiping from ..utils import auom, calculate_excavation_volume -from qsdsan.utils import ospath, time_printer, load_data, get_SRT, get_oxygen_heterotrophs, get_oxygen_autotrophs, get_airflow, get_P_blower - - -__all__ = ('ActivatedSludgeProcess','TreatmentTrains',) +__all__ = ('ActivatedSludgeProcess',) +#%% _ft2_to_m2 = auom('ft2').conversion_factor('m2') F_BM_pump = 1.18*(1+0.007/100) # 0.007 is for miscellaneous costs default_F_BM = { @@ -739,430 +732,3 @@ def constr_access(self): def constr_access(self, i): self._constr_access = i - -class TreatmentTrains(Mixer): - ''' - Dummy unit with no run function of its own. To be used to calculate the - design and cost of treatment trains in ASP. Code largely dereived from code scripts - for [1]_. - - Parameters - ---------- - ins : Iterable - None expected (dummy unit). - outs : Iterable - None expected (dummy unit). - N_train : int - Number of treatment train, should be at least two in case one failing. - V_tank : float - Volume of tank (represents one treatment chain), [m3]. Default is 1000 m3. - W_tank : float - Width of tank, [m]. Default is 6.4 m. [1] - D_tank : float - Depth of tank, [m]. Default is 3.66 m. [1] - freeboard : float - Freeboard added to the depth of the reactor tank, [m]. Default is 0.61 m. [1] - W_dist : float - Width of distribution channel, [m]. Default is 1.37 m. [1] - W_eff: float - Width of effluent channel, [m]. Default is 1.5 m. [1] - W_recirculation: float - Width of recirculation channel, [m]. Default is 1.5 m. - excav_slope: float - Slope for excavation (horizontal/vertical). Default is 1.5. [1] - constr_access: float - Extra room for construction access, [m]. Default is 0.92 m. [1] - Q_air_design: float - Used to calculate the power of blower. - Air flow required for one treatment train [m3/hr]. - Q_recirculation: float - Used to calculate pumping power. - Design recirculated flow for one treatment train [m3/day]. ** confirm the logic of 'one' ** - kwargs : dict - Other attributes to be set. - - References - ---------- - .. [1] Shoener, B. D.; Zhong, C.; Greiner, A. D.; Khunjar, W. O.; Hong, P.-Y.; Guest, J. S. - Design of Anaerobic Membrane Bioreactors for the Valorization - of Dilute Organic Carbon Waste Streams. - Energy Environ. Sci. 2016, 9 (3), 1102–1112. - https://doi.org/10.1039/C5EE03715H. - - See Also - -------- - `MATLAB codes `_ used in ref 1, - especially the system layout `diagrams `_. - - ''' - - # Costs - wall_concrete_unit_cost = 1081.73 # $/m3 (Hydromantis. CapdetWorks 4.0. https://www.hydromantis.com/CapdetWorks.html) - slab_concrete_unit_cost = 582.48 # $/m3 (Hydromantis. CapdetWorks 4.0. https://www.hydromantis.com/CapdetWorks.html) - excav_unit_cost = (8 + 0.3) / 0.765 # $/m3, 0.765 is to convert from $/yd3 **NOT UPDATED** (taken from Shoener et al.) - - _t_wall = None - _t_slab = None - - pumps = ('recirculation_CSTR',) - - def __init__(self, ID='', ins= None, outs= (), thermo=None, init_with='WasteStream', - F_BM_default=1, isdynamic=False, N_train=2, V_tank=1000, - W_tank = 6.4, D_tank = 3.66, freeboard = 0.61, W_dist = 1.37, W_eff = 1.5, # all in meter (converted from feet to m, Shoener et al.2016) - W_recirculation = 1.5, # in m (assumed same as W_eff) - excav_slope = 1.5, # horizontal/vertical (Shoener et al.2016) - constr_access = 0.92, # in meter (converted from feet to m, Shoener et al. 2016) - Q_air_design = 1000, # in m3/hr **NO SOURCE FOR DEFAULT VALUE YET** - Q_recirculation = 1000, # in m3/day **NO SOURCE FOR DEFAULT VALUE YET** - F_BM=default_F_BM, lifetime=default_equipment_lifetime, rigorous=False, - **kwargs): - SanUnit.__init__(self, ID, ins, outs, thermo, init_with, - F_BM_default=F_BM_default, isdynamic=isdynamic) - - self._effluent = self.outs[0].copy(f'{ID}_effluent') - self.rigorous = rigorous - self._mixed = WasteStream(f'{ID}_mixed') - self.N_train = N_train - self.V_tank = V_tank - self.W_tank = W_tank - self.D_tank = D_tank - self.freeboard = freeboard - self.W_dist = W_dist - self.W_eff = W_eff - self.W_recirculation = W_recirculation - self.excav_slope = excav_slope - self.constr_access = constr_access - self.Q_air_design = Q_air_design # **NO SOURCE FOR DEFAULT VALUE YET** - self.Q_recirculation = Q_recirculation - - self.blower = blower = Blower('blower', linked_unit=self, N_reactor=N_train) - self.air_piping = air_piping = GasPiping('air_piping', linked_unit=self, N_reactor=N_train) - self.equipments = (blower, air_piping) - self.F_BM.update(F_BM) - - @property - def N_train(self): - ''' - [int] Number of treatment train, should be at least two in case one failing. - ''' - return self._N_train - - @N_train.setter - def N_train(self, i): - i = ceil(i) - if i < 2: - raise ValueError('`N_train` should be at least 2.') - self._N_train = i - - @property - def V_tank(self): - '''[float] Volume of tank, [m3].''' - return self._V_tank - - @V_tank.setter - def V_tank(self, i): - self._V_tank = i - - @property - def W_tank(self): - '''[float] Width of one tank, [m].''' - return self._W_tank - - @W_tank.setter - def W_tank(self, i): - self._W_tank = i - - @property - def D_tank(self): - '''[float] Depth of one tank, [m].''' - return self._D_tank - - @D_tank.setter - def D_tank(self, i): - self._D_tank = i - - @property - def W_dist(self): - '''[float] Width of the distribution channel, [m].''' - return self._W_dist - - @W_dist.setter - def W_dist(self, i): - self._W_dist = i - - @property - def W_eff(self): - '''[float] Width of the effluent channel, [m].''' - return self._W_eff - - @W_eff.setter - def W_eff(self, i): - self._W_eff = i - - @property - def W_recirculation(self): - '''[float] Width of the recirculation channel, [m].''' - return self._W_recirculation - - @W_recirculation.setter - def W_recirculation(self, i): - self._W_recirculation = i - - @property - def freeboard(self): - '''[float] Freeboard added to the depth of the reactor tank, [m].''' - return self._freeboard - - @freeboard.setter - def freeboard(self, i): - self._freeboard = i - - @property - def t_wall(self): - ''' - [float] Thickness of the wall concrete, [m]. - default to be minimum of 1 ft with 1 in added for every ft of depth over 12 ft. - ''' - D_tank = self.D_tank*39.37 # m to inches - return self._t_wall or (1 + max(D_tank - 12, 0)/12)*0.3048 # from feet to m - - @t_wall.setter - def t_wall(self, i): - self._t_wall = i - - @property - def t_slab(self): - ''' - [float] Concrete slab thickness, [m], - default to be 2 in thicker than the wall thickness. - ''' - return self._t_slab or (self.t_wall + 2/12)*0.3048 # from feet to m - - @t_slab.setter - def t_slab(self, i): - self._t_slab = i - - @property - def excav_slope(self): - '''[float] Slope for excavation (horizontal/vertical).''' - return self._excav_slope - - @excav_slope.setter - def excav_slope(self, i): - self._excav_slope = i - - @property - def constr_access(self): - '''[float] Extra room for construction access, [m].''' - return self._constr_access - - @constr_access.setter - def constr_access(self, i): - self._constr_access = i - - @property - def Q_air_design(self): - '''[float] Air flow required for one treatment train, [m3/hr].''' - return self._Q_air_design - - @Q_air_design.setter - def Q_air_design(self, i): - self._Q_air_design = i - - @property - def Q_recirculation(self): - '''[float] Design recirculated flow in the treatment train, [m3/day].''' - return self._Q_recirculation - - @Q_recirculation.setter - def Q_recirculation(self, i): - self._Q_recirculation = i - - def _design_pump(self): - - ID, pumps = self.ID, self.pumps - D = self.design_results - - self._effluent.copy_like(self.outs[0]) - effluent = self._effluent - - ins_dct = { - 'recirculation_CSTR': effluent, - } - - Q_recirculation = D['Q recirculation'] - meter_to_feet = 3.28 - Tank_length = D['Tank length']*meter_to_feet # in ft - - Q_recirculation_mgd = Q_recirculation*0.000264 #m3/day to MGD - - Q_mgd = { - 'recirculation_CSTR': Q_recirculation_mgd, - } - - type_dct = dict.fromkeys(pumps, 'recirculation_CSTR') - inputs_dct = dict.fromkeys(pumps, (self.N_train, Tank_length,)) - - for i in pumps: - if hasattr(self, f'{i}_pump'): - p = getattr(self, f'{i}_pump') - setattr(p, 'add_inputs', inputs_dct[i]) - else: - ID = f'{ID}_{i}' - capacity_factor=1 - pump = WWTpump( - ID=ID, ins= ins_dct[i], pump_type=type_dct[i], - Q_mgd=Q_mgd[i], add_inputs=inputs_dct[i], - capacity_factor=capacity_factor, - include_pump_cost=True, - include_building_cost=False, - include_OM_cost=True, - ) - setattr(self, f'{i}_pump', pump) - - pipe_ss, pump_ss = 0., 0. - for i in pumps: - p = getattr(self, f'{i}_pump') - p.simulate() - p_design = p.design_results - pipe_ss += p_design['Pump pipe stainless steel'] - pump_ss += p_design['Pump stainless steel'] - - return pipe_ss, pump_ss - - _units = { - 'Number of trains': 'ea', - 'Tank volume': 'm3', - 'HRT': 'hr', - 'Tank width': 'm', - 'Tank depth': 'm', - 'Tank length': 'm', - 'Wall concrete': 'm3', - 'Slab concrete': 'm3', - 'Excavation': 'm3', - 'Q recirculation': 'm3/day', - 'Pump pipe stainless steel': 'kg', - 'Pump stainless steel': 'kg', - } - - def _design(self): - self._mixed.mix_from(self.ins) - mixed = self._mixed - - D = self.design_results - - D['Number of trains'] = self.N_train - D['Tank volume'] = self.V_tank # in m3 - D['HRT'] = D['Tank volume']/mixed.get_total_flow('m3/hr') # in hr - D['Tank width'] = self.W_tank # in m - D['Tank depth'] = self.D_tank # in m - D['Tank length'] = D['Tank volume']/(D['Tank width']*D['Tank depth']) # in m - - - t_wall, t_slab = self.t_wall, self.t_slab - W_N_trains = (D['Tank width'] + 2*t_wall)*D['Number of trains'] - t_wall*(D['Number of trains'] - 1) - - D_tot = D['Tank depth'] + self.freeboard - t = t_wall + t_slab - - get_VWC = lambda L1, N: N * t_wall * L1 * D_tot # get volume of wall concrete - get_VSC = lambda L2: t * L2 * W_N_trains # get volume of slab concrete - - # Aeration tanks, [m3] - VWC = get_VWC(L1= D['Tank length'], N=(D['Number of trains'] + 1)) - VSC = get_VSC(L2= D['Tank length']) - - # Distribution channel, [m3] - W_dist, W_eff, W_recirculation = self.W_dist, self.W_eff, self.W_recirculation - VWC += get_VWC(L1=(W_N_trains+W_dist), N=2) # N =2 for two walls - VSC += get_VSC(L2=(W_dist + 2*t_wall)) - - # Effluent channel, [m3] - VWC += get_VWC(L1=(W_N_trains + W_eff), N=2) # N =2 for two walls - VSC += get_VSC(L2=(W_eff + 2*t_wall)) - - # RAS channel, [m3] - VWC += get_VWC(L1=(W_N_trains + W_recirculation), N=2) # N =2 for two walls - VSC += get_VSC(L2=(W_recirculation + 2*t_wall)) - - D['Wall concrete'] = VWC - D['Slab concrete'] = VSC - - # Excavation - excav_slope, constr_access = self.excav_slope, self.constr_access - # Aeration tank - VEX = calculate_excavation_volume( - L=(W_dist + D['Tank length']), W = W_N_trains, D = D['Tank depth'], - excav_slope=excav_slope, constr_access=constr_access) - - D['Excavation'] = VEX - - D['Q recirculation'] = self.Q_recirculation - - # Blower and gas piping (taken from 'ActivatedSludgeProcess' SanUnit) - - - # oxygen_autotroph = get_oxygen_autotrophs(inf.F_vol*24, inf.COD, eff_sCOD, - # inf_TKN, SRT=srt, Y_H=0.625, U_AUT=1.0, - # b_H=0.4, b_AUT=0.15, f_d=0.1, SF_DO=1.25) - - # oxygen_autotroph = get_oxygen_autotrophs(inf.F_vol*24, inf.COD, eff_sCOD, - # inf_TKN, SRT=srt, Y_H=0.625, U_AUT=1.0, - # b_H=0.4, b_AUT=0.15, f_d=0.1, SF_DO=1.25) - - # airflow = get_airflow(oxygen_heterotrophs = oxygen_heterotroph, oxygen_autotrophs = oxygen_autotroph, - # oxygen_transfer_efficiency = 15) # in m3/min - - - - Q_air_design = self.Q_air_design # in m3/min - air_cfm = auom('m3/min').convert(Q_air_design, 'cfm') - blower, piping = self.equipments - - blower.N_reactor = piping.N_reactor = D['Number of trains'] - blower.gas_demand_per_reactor = piping.gas_demand_per_reactor = air_cfm - self.add_equipment_design() - - # Pumps - pipe, pumps = self._design_pump() - D['Pump pipe stainless steel'] = pipe - D['Pump stainless steel'] = pumps - - def _cost(self): - D = self.design_results - C = self.baseline_purchase_costs - - ### Capital ### - # Concrete and excavation - C['Wall concrete'] = D['Wall concrete'] * self.wall_concrete_unit_cost - C['Slab concrete'] = D['Slab concrete'] * self.slab_concrete_unit_cost - C['Reactor excavation'] = D['Excavation'] * self.excav_unit_cost - - # Pump - pumps, add_OPEX = self.pumps, self.add_OPEX - pump_cost, building_cost, opex_o, opex_m = 0., 0., 0., 0. - for i in pumps: - p = getattr(self, f'{i}_pump') - p_cost, p_add_opex = p.baseline_purchase_costs, p.add_OPEX - pump_cost += p_cost['Pump'] - building_cost += p_cost['Pump building'] - opex_o += p_add_opex['Pump operating'] - opex_m += p_add_opex['Pump maintenance'] - - C['Pumps'] = pump_cost - C['Pump building'] = building_cost - add_OPEX['Pump operating'] = opex_o - add_OPEX['Pump maintenance'] = opex_m - - # Blower - self.add_equipment_cost() - - # Power - pumping = 0. - for ID in self.pumps: - p = getattr(self, f'{ID}_pump') - if p is None: - continue - pumping += p.power_utility.rate - - self.power_utility.rate = self.blower.design_results['Total blower power'] + pumping \ No newline at end of file From bbbb96585825309973567b6098ac337a35a522b0 Mon Sep 17 00:00:00 2001 From: Joy Zhang Date: Tue, 15 Oct 2024 13:56:11 -0700 Subject: [PATCH 420/424] Update _clarifier.py --- qsdsan/sanunits/_clarifier.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qsdsan/sanunits/_clarifier.py b/qsdsan/sanunits/_clarifier.py index 6b0ea7a4..586b80ad 100644 --- a/qsdsan/sanunits/_clarifier.py +++ b/qsdsan/sanunits/_clarifier.py @@ -553,6 +553,7 @@ def dy_dt(t, QC_ins, QC, dQC_ins): self._ODE = dy_dt + #!!! should consolidate design & costing equations between primary & secondary clarifiers # _units = { # 'Number of clarifiers': 'ea', # 'Volumetric flow': 'm3/day', From 0a56388453b00685031ccbfda293e39ebe35435e Mon Sep 17 00:00:00 2001 From: Yalin Date: Tue, 15 Oct 2024 21:19:32 -0400 Subject: [PATCH 421/424] update with new biosteam/thermosteam --- qsdsan/_components.py | 8 +++++--- qsdsan/sanunits/_heat_exchanging.py | 5 +++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/qsdsan/_components.py b/qsdsan/_components.py index 12322974..61beeb13 100644 --- a/qsdsan/_components.py +++ b/qsdsan/_components.py @@ -154,8 +154,10 @@ def extend(self, components): def compile(self, skip_checks=False): '''Cast as a :class:`CompiledComponents` object.''' components = tuple(self) + tmo._chemicals.prepare(components, skip_checks) setattr(self, '__class__', CompiledComponents) - try: self._compile(components, skip_checks) + + try: self._compile(components) except Exception as error: setattr(self, '__class__', Components) setattr(self, '__dict__', {i.ID: i for i in components}) @@ -614,11 +616,11 @@ def compile(self, skip_checks=False): pass - def _compile(self, components, skip_checks=False): + def _compile(self, components): dct = self.__dict__ tuple_ = tuple # this speeds up the code components = tuple_(dct.values()) - CompiledChemicals._compile(self, components, skip_checks) + CompiledChemicals._compile(self, components) for component in components: missing_properties = component.get_missing_properties(_key_component_properties) if not missing_properties: continue diff --git a/qsdsan/sanunits/_heat_exchanging.py b/qsdsan/sanunits/_heat_exchanging.py index 2c707d1e..d5beeaf4 100644 --- a/qsdsan/sanunits/_heat_exchanging.py +++ b/qsdsan/sanunits/_heat_exchanging.py @@ -220,6 +220,7 @@ def __init__( inner_fluid_pressure_drop=None, outer_fluid_pressure_drop=None, neglect_pressure_drop=True, + furnace_pressure=None, # [Pa] equivalent to 500 psig ): SanUnit.__init__(self, ID, ins, outs, thermo, init_with=init_with, F_BM_default=F_BM_default, @@ -263,6 +264,10 @@ def __init__( #: If value is None, it defaults to the heat transfer efficiency of the #: heat utility. self.heat_transfer_efficiency = heat_transfer_efficiency + + #: Optional[float] Internal pressure of combustion gas. Defaults + #: 500 psig (equivalent to 3548325.0 Pa) + self.furnace_pressure = 500 if furnace_pressure is None else furnace_pressure def _design(self, duty=None): HXU._design(self) From eb4520d0090159b3858388e40db46ba2eb4dd862 Mon Sep 17 00:00:00 2001 From: Yalin Date: Tue, 15 Oct 2024 22:28:57 -0400 Subject: [PATCH 422/424] update for naming consistency --- qsdsan/sanunits/_membrane_gas_extraction.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/qsdsan/sanunits/_membrane_gas_extraction.py b/qsdsan/sanunits/_membrane_gas_extraction.py index f28aec98..810fd8fe 100644 --- a/qsdsan/sanunits/_membrane_gas_extraction.py +++ b/qsdsan/sanunits/_membrane_gas_extraction.py @@ -18,7 +18,7 @@ from qsdsan import SanUnit import numpy as np -__all__ = ('GasExtractionMembrane',) +__all__ = ('GasExtractionMembrane', 'MembraneGasExtraction',) class GasExtractionMembrane(SanUnit): @@ -563,4 +563,8 @@ def dy_dt(t, QC_ins, QC, dQC_ins): dC_shell[:] = np.diag(sumCp_init/sumCp_fin) @ new_C_shell - C_shell dC[:] = np.hstack((dC_lumen, dC_shell)).flatten() _update_dstate() - self._ODE = dy_dt \ No newline at end of file + self._ODE = dy_dt + + +# For naming consistency +MembraneGasExtraction = GasExtractionMembrane \ No newline at end of file From cd21977503dedc5f90125dee3d3e19023081669e Mon Sep 17 00:00:00 2001 From: Yalin Date: Tue, 15 Oct 2024 22:29:10 -0400 Subject: [PATCH 423/424] update docs --- docs/source/api/processes/ADM1p.rst | 7 +++++++ docs/source/api/processes/ASM2d.rst | 3 +++ docs/source/api/processes/_index.rst | 14 ++++++++++++-- docs/source/api/processes/mADM1.rst | 4 ++++ docs/source/api/sanunits/MembraneGasExtraction.rst | 4 ++++ docs/source/api/sanunits/_index.csv | 3 +++ docs/source/api/sanunits/_index.rst | 4 +++- docs/source/api/sanunits/sludge_treatment.rst | 4 ++++ 8 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 docs/source/api/processes/ADM1p.rst create mode 100644 docs/source/api/processes/mADM1.rst create mode 100644 docs/source/api/sanunits/MembraneGasExtraction.rst create mode 100644 docs/source/api/sanunits/sludge_treatment.rst diff --git a/docs/source/api/processes/ADM1p.rst b/docs/source/api/processes/ADM1p.rst new file mode 100644 index 00000000..3b6cb324 --- /dev/null +++ b/docs/source/api/processes/ADM1p.rst @@ -0,0 +1,7 @@ +Anaerobic Digestion Model No.1 with P extension (ADM1 P/Extension) +================================================================== +.. autoclass:: qsdsan.processes.ADM1_p_extension + :members: + +.. autoclass:: qsdsan.processes.ADM1p +:members: \ No newline at end of file diff --git a/docs/source/api/processes/ASM2d.rst b/docs/source/api/processes/ASM2d.rst index d75c961c..af2d9c64 100644 --- a/docs/source/api/processes/ASM2d.rst +++ b/docs/source/api/processes/ASM2d.rst @@ -1,4 +1,7 @@ Activated Sludge Model No.2d (ASM2d) ==================================== .. autoclass:: qsdsan.processes.ASM2d + :members: + +.. autoclass:: qsdsan.processes.mASM2d :members: \ No newline at end of file diff --git a/docs/source/api/processes/_index.rst b/docs/source/api/processes/_index.rst index f807e3da..f4ed6405 100644 --- a/docs/source/api/processes/_index.rst +++ b/docs/source/api/processes/_index.rst @@ -11,12 +11,18 @@ List of Biological Kinetic Models | ADM1 | `adm`_ | `Batstone`_ et al., 2002 | | | | `Rosen and Jeppsson`_, 2006 | +----------+------------------+-----------------------------+ +| ADM1p | `bsm2`_ | `Alex`_ et al., 2008 | ++----------+------------------+-----------------------------+ +| mADM1 | | | ++----------+------------------+-----------------------------+ | ASM1 | `asm`_ & `bsm1`_ | `Henze`_ et al., 2006 | +----------+------------------+-----------------------------+ | ASM2d | `asm`_ & `bsm1`_ | `Henze`_ et al., 2006 | +----------+------------------+-----------------------------+ -| PM2 | `pm2_ecorecover`_| N/A | -| | & `pm2_batch`_ | | +| mASM2d | `bsm2`_ | `Alex`_ et al., 2008 | ++----------+------------------+-----------------------------+ +| PM2 | `pm2_ecorecover`_| | +| | & `pm2_batch`_ | | +----------+------------------+-----------------------------+ @@ -41,10 +47,12 @@ List of Other Kinetic Modules .. _adm: https://github.com/QSD-Group/EXPOsan/tree/main/exposan/adm .. _asm: https://github.com/QSD-Group/EXPOsan/tree/main/exposan/asm .. _bsm1: https://github.com/QSD-Group/EXPOsan/tree/main/exposan/bsm1 +.. _bsm2: https://github.com/QSD-Group/EXPOsan/tree/main/exposan/bsm2 .. _bwaise: https://github.com/QSD-Group/EXPOsan/tree/main/exposan/bwaise .. _pm2_batch: https://github.com/QSD-Group/EXPOsan/tree/main/exposan/pm2_batch .. _pm2_ecorecover: https://github.com/QSD-Group/EXPOsan/tree/main/exposan/pm2_ecorecover +.. _Alex: http://iwa-mia.org/wp-content/uploads/2022/09/TR3_BSM_TG_Tech_Report_no_3_BSM2_General_Description.pdf .. _Batstone: https://iwaponline.com/ebooks/book/152/Anaerobic-Digestion-Model-No-1-ADM1 .. _EPA design manual: https://nepis.epa.gov/Exe/ZyPURL.cgi?Dockey=3000464S.TXT .. _Henze: https://iwaponline.com/ebooks/book/96/ @@ -59,9 +67,11 @@ Links to docs :maxdepth: 1 ADM1 + ADM1p Aeration ASM1 ASM2d Decay KineticReaction + mADM1 PM2 \ No newline at end of file diff --git a/docs/source/api/processes/mADM1.rst b/docs/source/api/processes/mADM1.rst new file mode 100644 index 00000000..ef36aa9f --- /dev/null +++ b/docs/source/api/processes/mADM1.rst @@ -0,0 +1,4 @@ +Modified Anaerobic Digestion Model No.1 (MADM1) +=============================================== +.. autoclass:: qsdsan.processes.ModifiedADM1 + :members: \ No newline at end of file diff --git a/docs/source/api/sanunits/MembraneGasExtraction.rst b/docs/source/api/sanunits/MembraneGasExtraction.rst new file mode 100644 index 00000000..1b5c4a04 --- /dev/null +++ b/docs/source/api/sanunits/MembraneGasExtraction.rst @@ -0,0 +1,4 @@ +Membrane Gas Extraction +======================= +.. automodule:: qsdsan.sanunits._membrane_gas_extraction + :members: \ No newline at end of file diff --git a/docs/source/api/sanunits/_index.csv b/docs/source/api/sanunits/_index.csv index 1d815ca3..6169cefb 100644 --- a/docs/source/api/sanunits/_index.csv +++ b/docs/source/api/sanunits/_index.csv @@ -38,12 +38,15 @@ Lagoon,No,No,Completed LiquidTreatmentBed,No,No,Completed LumpedCost,No,No,Completed MembraneDistillation,No,No,Completed +MembraneGasExtraction,No,No,Completed MixTank,No,No,Completed Mixer,Yes,Yes,Completed MURT (multi-unit reinvented toilet),No,No,Completed +PFR (plug flow reactor),Yes,No,Under development PhaseChanger,No,Yes,Completed PitLatrine,No,No,Completed PolishingFilter,No,No,Completed +PrimaryClarifier,Yes,No,Completed Pump,Yes,No,Completed Reactor,No,No,Completed ReversedSplitter,No,Yes,Completed diff --git a/docs/source/api/sanunits/_index.rst b/docs/source/api/sanunits/_index.rst index 9723d21c..40fe67db 100644 --- a/docs/source/api/sanunits/_index.rst +++ b/docs/source/api/sanunits/_index.rst @@ -49,6 +49,7 @@ Individual Unit Operations Lagoon membrane_bioreactor MembraneDistillation + MembraneGasExtraction non_reactive PolishingFilter pumping @@ -56,8 +57,9 @@ Individual Unit Operations Screening Sedimentation SepticTank - sludge_thickening SludgePasteurization + sludge_thickening + sludge_treatment suspended_growth_bioreactor tank toilet diff --git a/docs/source/api/sanunits/sludge_treatment.rst b/docs/source/api/sanunits/sludge_treatment.rst new file mode 100644 index 00000000..f7747678 --- /dev/null +++ b/docs/source/api/sanunits/sludge_treatment.rst @@ -0,0 +1,4 @@ +Sludge Treatment +================ +.. automodule:: qsdsan.sanunits._sludge_treatment + :members: \ No newline at end of file From ba224e124e083d6b4761a97a3f6f438e85a9a001 Mon Sep 17 00:00:00 2001 From: Yalin Date: Tue, 15 Oct 2024 22:29:23 -0400 Subject: [PATCH 424/424] release new version --- CHANGELOG.rst | 27 +++++++++++++++++++++++++++ setup.py | 6 +++--- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index c19ba3e8..e448f495 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,32 @@ Change Log This document records notable changes to `QSDsan `_. We aim to follow `Semantic Versioning `_. +`1.4.0`_ +-------- +- A lot of the updates have been focused on the dynamic simulation, now the open-loop Benchmark Simulation Model No. 2 (`BSM2 `_) configuration has been implemented with new process models and unit operation including + + - :class:`qsdsan.processes.ADM1p` + - :class:`qsdsan.processes.ADM1_p_extension` + - :class:`qsdsan.processes.ModifiedADM1` + - :class:`qsdsan.processes.mASM2d` + - :class:`qsdsan.sanunits.IdealClarifier` + - :class:`qsdsan.sanunits.PrimaryClarifier` + - :class:`qsdsan.sanunits.PrimaryClarifierBSM2` + - :class:`qsdsan.sanunits.GasExtractionMembrane` + - :class:`qsdsan.sanunits.Thickener` + - :class:`qsdsan.sanunits.Centrifuge` + - :class:`qsdsan.sanunits.Incinerator` + - :class:`qsdsan.sanunits.BatchExperiment` + - :class:`qsdsan.sanunits.PFR` + - :class:`qsdsan.sanunits.BeltThickener` + - :class:`qsdsan.sanunits.SludgeCentrifuge` + - :class:`qsdsan.sanunits.SludgeThickener` + +- New publications + + - Feng et al., *Environmental Science & Technology*, on the sustainability of `hydrothermal liquefaction (HTL) `_ for resource recovery from a range of wet organic wastes. + + `1.3.0`_ -------- - Enhance and use QSDsan's capacity for dynamic simulation for emerging technologies and benchmark configurations (see EXPOsan METAB and PM2 (on the algae branch, still under development) modules). @@ -185,6 +211,7 @@ Official release of ``QSDsan`` v1.0.0! .. _Trimmer et al.: https://doi.org/10.1021/acs.est.0c03296 .. Commit links +.. _1.4.0: https://github.com/QSD-Group/QSDsan/releases/tag/v1.4.0 .. _1.3.0: https://github.com/QSD-Group/QSDsan/releases/tag/v1.3.0 .. _1.2.0: https://github.com/QSD-Group/QSDsan/releases/tag/v1.2.0 .. _1.1.0: https://github.com/QSD-Group/QSDsan/releases/tag/v1.1.0 diff --git a/setup.py b/setup.py index 57034c09..2059114b 100644 --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ setup( name='qsdsan', packages=['qsdsan'], - version='1.3.1', + version='1.4.0', license='UIUC', author='Quantitative Sustainable Design Group', author_email='quantitative.sustainable.design@gmail.com', @@ -31,8 +31,8 @@ 'Repository': 'https://github.com/QSD-Group/QSDsan', }, install_requires=[ - 'biosteam>=2.37.4', - 'thermosteam>=0.35.1', + 'biosteam>=2.46.1', + 'thermosteam>=0.45.0', 'matplotlib>=3.3.2', 'pandas>=1.3.2', 'SALib>=1.4.5',