Skip to content

Commit

Permalink
process add-on for aerobic digestion
Browse files Browse the repository at this point in the history
  • Loading branch information
joyxyz1994 committed Nov 20, 2024
1 parent 469fe17 commit b120586
Show file tree
Hide file tree
Showing 4 changed files with 173 additions and 10 deletions.
5 changes: 4 additions & 1 deletion qsdsan/processes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ def __init__(self):
from ._decay import *
from ._kinetic_reaction import *
from ._pm2 import *
from ._aerobic_digestion_addon import *

from . import (
_aeration,
Expand All @@ -77,7 +78,8 @@ def __init__(self):
# _madm1,
_decay,
_kinetic_reaction,
_pm2
_pm2,
_aerobic_digestion_addon,
)

__all__ = (
Expand All @@ -90,4 +92,5 @@ def __init__(self):
*_decay.__all__,
*_kinetic_reaction.__all__,
*_pm2.__all__,
*_aerobic_digestion_addon.__all__,
)
106 changes: 106 additions & 0 deletions qsdsan/processes/_aerobic_digestion_addon.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# -*- coding: utf-8 -*-
'''
QSDsan: Quantitative Sustainable Design for sanitation and resource recovery systems
This module is developed by:
Joy Zhang <joycheung1994@gmail.com>
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 Process
from thermosteam import settings
_load_components = settings.get_default_chemicals

__all__ = ('ASM_AeDigAddOn',)

class ASM_AeDigAddOn(Process):
'''
Creates a `Process` object representing the degradation of particulate
inert organic materials that typically occur in an aerobic digester.
Stoichiometry is determined by rules of element conservation in corresponding
activated sludge models.
Parameters
----------
k_dig : float, optional
The 1st-order degradation rate constant, in d^(-1). The default is 0.04.
See Also
--------
:class:`qsdsan.processes.ASM1`
:class:`qsdsan.processes.ASM2d`
:class:`qsdsan.processes.mASM2d`
Examples
--------
>>> import qsdsan.processes as pc
>>> cmps_asm1 = pc.create_asm1_cmps()
>>> dig_asm1 = pc.ASM_AeDigAddOn('dig_asm1')
>>> dig_asm1.show()
Process: dig_asm1
[stoichiometry] X_I: -1.00
X_S: 1.00
S_NH: 0.0600
S_ALK: 0.0515
[reference] X_I
[rate equation] X_I*k_dig
[parameters] k_dig: 0.04
[dynamic parameters]
>>> cmps_masm2d = pc.create_masm2d_cmps(set_thermo=False)
>>> dig_masm2d = pc.ASM_AeDigAddOn('dig_masm2d', components=cmps_masm2d)
>>> dig_masm2d.show()
Process: dig_masm2d
[stoichiometry] S_NH4: 0.0265
S_PO4: 0.000900
S_IC: 0.0434
X_I: -1.00
X_S: 1.00
[reference] X_I
[rate equation] X_I*k_dig
[parameters] k_dig: 0.04
[dynamic parameters]
'''

def __init__(self, ID, k_dig=0.04, components=None):
cmps = _load_components(components)
rxn = 'X_I -> X_S'
consrv = []
if 'S_ALK' in cmps.IDs:
consrv.append('charge')
rxn += ' + [?]S_ALK'
elif 'S_IC' in cmps.IDs:
consrv.append('C')
rxn += ' + [?]S_IC'

if 'S_NH' in cmps.IDs:
consrv.append('N')
rxn += ' + [?]S_NH'
elif 'S_NH4' in cmps.IDs:
consrv.append('N')
rxn += ' + [?]S_NH4'

if 'S_PO4' in cmps.IDs:
consrv.append('P')
rxn += ' +[?]S_PO4'

super().__init__(ID=ID, reaction=rxn,
rate_equation='k_dig*X_I',
ref_component='X_I',
components=cmps,
conserved_for=consrv,
parameters=('k_dig',))
self.k_dig=k_dig

@property
def k_dig(self):
'''[float] Degradation rate constant, in d^(-1).'''
return self._k
@k_dig.setter
def k_dig(self, k):
self._k = k
self.set_parameters(k_dig=k)
3 changes: 2 additions & 1 deletion qsdsan/sanunits/_membrane_bioreactor.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@

__all__ = ('AnMBR',
'CompletelyMixedMBR',
'PlugFlowMBR',)
# 'PlugFlowMBR',
)

#%%
degassing = SanStream.degassing
Expand Down
69 changes: 61 additions & 8 deletions qsdsan/sanunits/_suspended_growth_bioreactor.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
'BatchExperiment',
# 'SBR',
'PFR',
'AerobicDigester',
)

# def _add_aeration_to_growth_model(aer, model):
Expand Down Expand Up @@ -332,19 +333,20 @@ def ODE(self):
self._compile_ODE()
return self._ODE

def _compile_ODE(self):
isa = isinstance
cmps = self.components
m = cmps.size
aer = self._aeration
def _init_model(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')
r = lambda state_arr: np.zeros(m)

r = lambda state_arr: np.zeros(self.components.size)
else:
# processes = _add_aeration_to_growth_model(aer, self._model)
r = self._model.production_rates_eval
return r

def _compile_ODE(self):
isa = isinstance
aer = self._aeration
r = self._init_model()

_dstate = self._dstate
_update_dstate = self._update_dstate
Expand Down Expand Up @@ -1286,4 +1288,55 @@ def get_retained_mass(self, biomass_IDs):
return mass @ self.V_tanks

def _design(self):
pass
pass

#%%
from ..processes import ASM_AeDigAddOn

class AerobicDigester(CSTR):

def __init__(self, ID='', ins=None, outs=(), thermo=None,
init_with='WasteStream', V_max=1000, activated_sludge_model=None,
organic_particulate_inert_degradation_process=None,
aeration=1.0, DO_ID='S_O2', isdynamic=True, **kwargs):
super().__init__(ID, ins, outs, thermo=thermo, init_with=init_with,
V_max=V_max, aeration=aeration, DO_ID=DO_ID,
suspended_growth_model=activated_sludge_model,
isdynamic=isdynamic, **kwargs)
self.organic_particulate_inert_degradation_process = organic_particulate_inert_degradation_process

@property
def organic_particulate_inert_degradation_process(self):
'''[:class:`Process` or NoneType] Process object for degradation of
particulate inert organic materials in the aerobic digester. If none
specified, will attempt to create a Process model according to components
by default.'''
return self._dig_addon
@organic_particulate_inert_degradation_process.setter
def organic_particulate_inert_degradation_process(self, proc):
if isinstance(proc, Process):
self._dig_addon = proc
elif proc is None:
if self._model is None: self._dig_addon = None
else:
ID = self._model.ID + '_particulate_inert_degrade'
self._dig_addon = ASM_AeDigAddOn(
ID=ID,
components=self.thermo.chemicals
)
else:
raise TypeError('organic_particulate_inert_degradation_process must be'
f' a `Process` object if not None, not {type(proc)}')

def _init_model(self):
if self._model is None:
warn(f'{self.ID} was initialized without an activated sludge model, '
f'and thus run as a non-reactive unit')
r = lambda state_arr: np.zeros(self.components.size)
else:
dig = self.organic_particulate_inert_degradation_process
dig_stoi = dig._stoichiometry
dig_frho = dig.rate_function
asm_frate = self._model.production_rates_eval
r = lambda state_arr: asm_frate(state_arr) + dig_stoi * dig_frho(state_arr)
return r

0 comments on commit b120586

Please sign in to comment.