Skip to content

Commit

Permalink
Merge branch 'metab' of https://github.com/QSD-Group/EXPOsan into dis…
Browse files Browse the repository at this point in the history
…infection
  • Loading branch information
yalinli2 committed Oct 23, 2023
2 parents 5147501 + 671ba11 commit 0d11614
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 35 deletions.
19 changes: 13 additions & 6 deletions exposan/metab/analyses.py
Original file line number Diff line number Diff line change
Expand Up @@ -779,8 +779,9 @@ def breakdown_and_sort(data):
lca['total'] = gwp
return tea, lca#, absolute

def plot_area(df, absolute=False):
def plot_area(df, absolute=False, ylims=None):
fig, ax = plt.subplots(figsize=(4,4))
fig.subplots_adjust(left=0.15, right=0.95)
x = range(df.shape[0])
ax.axhline(y=0, color='black', linewidth=0.5)
yp = np.zeros(df.shape[0])
Expand All @@ -793,13 +794,14 @@ def plot_area(df, absolute=False):
yp += (y>=0) * y
yn += (y<0) * y
ax.set_xlim(0, df.shape[0])
if ylims: ax.set_ylim(*ylims)
ax.tick_params(labelsize=12)
ax.tick_params(axis='y', which='major', direction='inout', length=6)
ax.tick_params(axis='y', which='minor', direction='inout', length=3)
ax.set_xlabel('')
ax.set_ylabel('')
if absolute:
ax.ticklabel_format(axis='y', scilimits=[-2,3], useMathText=True)
# ax.ticklabel_format(axis='y', scilimits=[-2,3], useMathText=True)
ax.yaxis.get_offset_text().set_fontsize(12)
ax2y = ax.secondary_yaxis('right')
ax2y.tick_params(axis='y', which='major', direction='in', length=3)
Expand All @@ -815,12 +817,17 @@ def plot_area(df, absolute=False):
def breakdown_uasa(seed):
for i in ('UASB', 'FB', 'PB'):
data = load_data(ospath.join(results_path, f'{i}1P_{seed}.xlsx'),
header=[0,1], skiprows=[2,])
header=[0,1], skiprows=[2,], nrows=1000)
# tea, lca, absolute = breakdown_and_sort(data)
tea, lca = breakdown_and_sort(data)
for suffix, df in (('TEA', tea), ('LCA', lca)):
# fig, ax = plot_area(df, absolute and suffix=='LCA')
fig, ax = plot_area(df, True)
if i in ('FB', 'PB'):
if suffix == 'TEA': ylims = (-400, 15000)
else: ylims = (-300, 4000)
else:
ylims = None
fig, ax = plot_area(df, True, ylims)
fig.savefig(ospath.join(figures_path, f'breakdown/{i}_{suffix}_abs.png'),
dpi=300,
# facecolor='white',
Expand Down Expand Up @@ -934,8 +941,8 @@ def mapping(data=None, n=20, reactor_type='PB'):
# _rerun_failed_samples(364)
# data = MCF_encap_to_susp(364, False)
# MCF_bubble_plot(data)
# breakdown_uasa(364)
breakdown_uasa(364)
# mapping(suffix='specific')
# mapping(suffix='common')
# plot_univariate_kdes(364)
mapping(reactor_type='PB')
# mapping(reactor_type='PB')
18 changes: 15 additions & 3 deletions exposan/metab/process.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ def flex_rhos_adm1(state_arr, params, T_op=298.15, pH=False, gas_transfer=True):
T_base = params['T_base']
root = params['root']

state_arr[state_arr < 1e-15] = 0.
Cs_flex[:8] = state_arr[12:20]
Cs_flex[8:12] = state_arr[19:23]
Cs_flex[12:] = state_arr[16:23]
Expand All @@ -59,15 +60,27 @@ def flex_rhos_adm1(state_arr, params, T_op=298.15, pH=False, gas_transfer=True):
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]]

if T_op == T_base:
Kas = Kab
KH = KHb / unit_conversion[7:10]
else:
T_temp = params.pop('T_op', None)
if T_op == T_temp:
params['T_op'] = T_op
Kas = params['Ka']
KH = params['KH']
else:
params['T_op'] = T_op
Kas = params['Ka'] = Kab * T_correction_factor(T_base, T_op, Ka_dH)
KH = params['KH'] = KHb * T_correction_factor(T_base, T_op, KH_dH) / unit_conversion[7:10]

if pH:
Kas = Kab * T_correction_factor(T_base, T_op, Ka_dH)
h = 10**(-pH)
delta = acid_base_rxn(h, weak_acids, Kas)
S_cat = weak_acids[0] - delta
root.data['S_cat'] = S_cat
else:
Kas = Kab * T_correction_factor(T_base, T_op, Ka_dH)
h = brenth(acid_base_rxn, 1e-14, 1.0,
args=(weak_acids, Kas),
xtol=1e-12, maxiter=100)
Expand All @@ -91,7 +104,6 @@ def flex_rhos_adm1(state_arr, params, T_op=298.15, pH=False, gas_transfer=True):
if gas_transfer:
biogas_S = state_arr[7:10].copy()
biogas_p = R * T_op * state_arr[27:30]
KH = KHb * T_correction_factor(T_base, T_op, KH_dH) / unit_conversion[7:10]
co2 = weak_acids[3] - Kas[2] * weak_acids[3] / (Kas[2] + h)
biogas_S[-1] = co2 / unit_conversion[9]
rhos_flex[-3:] = kLa * (biogas_S - KH * biogas_p)
Expand Down
2 changes: 1 addition & 1 deletion exposan/metab/systems.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ def add_TEA_LCA(sys, irr, lt):
}

def create_system(n_stages=1, reactor_type='UASB', gas_extraction='P',
lifetime=30, discount_rate=0.1, T=22,
lifetime=30, discount_rate=0.05, T=22,
Q=5, inf_concs={}, tot_HRT=12,
flowsheet=None):
if not _impact_item_loaded: _load_lca_data()
Expand Down
73 changes: 55 additions & 18 deletions exposan/metab/units.py
Original file line number Diff line number Diff line change
Expand Up @@ -794,7 +794,7 @@ def recirculation_ratio(self):
if u_min <= 0: return 0
A_bed = (pi*self.V_bed**2/4/self.reactor_height_to_diameter**2)**(1/3)
A_liq = A_bed * 0.4
return u_min * A_liq/self._mixed.F_vol - 1
return max(0, u_min * A_liq/self._mixed.F_vol - 1)
return self._rQ
@recirculation_ratio.setter
def recirculation_ratio(self, r):
Expand Down Expand Up @@ -974,13 +974,6 @@ def _setup(self):
hasfield = hasattr
setfield = setattr
if self.fixed_headspace_P and not hasfield(self, 'vacuum_pump'):
# gas = self.outs[0]
# dP = max(0, (self._P_atm-self._P_gas)*1e5)
# pump = self.vacuum_pump = Pump(gas.ID+'_VacPump', ins=Stream(f'{gas.ID}_proxy'),
# dP_design=dP)
# self.construction.append(
# Construction(ID='surrogate', linked_unit=pump, item='air_compressor')
# )
self.construction.append(
Construction(ID='VacPump_surrogate', linked_unit=self, item='air_compressor')
)
Expand Down Expand Up @@ -1257,6 +1250,11 @@ def get_retained_mass(self, biomass_IDs):
'Bead volume': 'm3',
}

def _static_lift_equivalent(self):
dia = (self.V_bed*4/self.reactor_height_to_diameter/pi) ** (1/3)
h = dia * self.reactor_height_to_diameter
return h

def _design(self):
D = self.design_results
den = self._density
Expand Down Expand Up @@ -1322,14 +1320,18 @@ def _design(self):
HDPE_pipes = []
rQ = self.recirculation_ratio or 0
for ws in self.ins:
_inch, _kg_per_m = pipe_design(ws.F_vol*(1+rQ), self._l_min_velocity)
_inch, _kg_per_m = pipe_design(ws.F_vol, self._l_min_velocity)
pipe_IDs.append(_inch)
HDPE_pipes.append(_kg_per_m*L_inlets)
if rQ > 0:
_inch, _kg_per_m = pipe_design(mixed.F_vol*rQ, self._l_min_velocity)
pipe_IDs.append(_inch)
HDPE_pipes.append(_kg_per_m*(L_inlets+L_outlets))
for ws in self.outs:
if ws.phase == 'g':
D['Stainless steel'] += pipe_design(ws.F_vol, self._g_min_velocity, True)[1] * L_gas
else:
_inch, _kg_per_m = pipe_design(ws.F_vol*(1+rQ), self._l_min_velocity)
_inch, _kg_per_m = pipe_design(ws.F_vol, self._l_min_velocity)
pipe_IDs.append(_inch)
HDPE_pipes.append(_kg_per_m*L_outlets)
D['HDPE pipes'] = sum(HDPE_pipes)
Expand All @@ -1340,28 +1342,48 @@ def _design(self):
getfield = getattr
creg = Construction.registry
if self.fixed_headspace_P:
# vac = self.vacuum_pump
# vac.ins[0].copy_like(self.outs[0])
# vac.dP_design = (self.external_P - self.headspace_P) * 1e5
# vac.simulate()
gas = self.outs[0]
self.vacuum_pump = vac = VacuumSystem(
self, F_mass=_F_mass(gas), F_vol=_F_vol(gas),
P_suction=self.headspace_P * 1e5,
vessel_volume=self.V_gas
)
qvac = _construct_vacuum_pump(vac)
# pvac = getfield(creg, f'{flowsheet_ID}_{vac.ID}_surrogate')
pvac = getfield(creg, f'{flowsheet_ID}_{self.ID}_VacPump_surrogate')
pvac.quantity = qvac
h_lift = self._static_lift_equivalent()
for i, ws, in enumerate(self.ins):
ID = pipe_IDs[i]
hf = pipe_friction_head(ws.F_vol*(1+rQ)*_cmph_2_gpm, L_inlets, ID) # friction head loss
TDH = hf + h # in m, assume suction head = 0, discharge head = reactor height
hf = pipe_friction_head(ws.F_vol*_cmph_2_gpm, L_inlets, ID) # friction head loss
TDH = hf + h_lift # in m, assume suction head = 0, discharge head = reactor height
field = f'Pump_ins{i}'
pump = getfield(self, field)
pump.ins[0].copy_flow(ws)
pump.ins[0].set_total_flow(ws.F_vol*(1+rQ), 'm3/h')
# pump.ins[0].set_total_flow(ws.F_vol*(1+rQ), 'm3/h')
pump.dP_design = TDH * 9804.14 # in Pa
pump.simulate()
if self.include_construction:
q22, q40 = _construct_water_pump(pump)
p22 = getfield(creg, f'{flowsheet_ID}_{pump.ID}_22kW')
p40 = getfield(creg, f'{flowsheet_ID}_{pump.ID}_40W')
p22.quantity = q22
p40.quantity = q40
if rQ > 0:
field = 'Pump_recirculation'
if not hasattr(self, field):
pump = Pump(self.ID+'recir_Pump', ins=Stream(f'{self.ID}_recir'))
setattr(self, field, pump)
self.construction += [
Construction(ID='22kW', linked_unit=pump, item='pump_22kW'),
Construction(ID='40W', linked_unit=pump, item='pump_40W')
]
self.auxiliary_unit_names = tuple({*self.auxiliary_unit_names, field})
ID = pipe_IDs[len(self.ins)]
hf = pipe_friction_head(mixed.F_vol*rQ*_cmph_2_gpm, L_inlets+L_outlets, ID) # friction head loss
TDH = hf # in m, assume suction head = 0, static lift = 0
pump = self.Pump_recirculation
pump.ins[0].copy_flow(self.outs[1])
pump.ins[0].set_total_flow(mixed.F_vol*rQ, 'm3/h')
pump.dP_design = TDH * 9804.14 # in Pa
pump.simulate()
if self.include_construction:
Expand Down Expand Up @@ -1623,6 +1645,21 @@ def biomass_tss(self, biomass_IDs):
outs = np.asarray(outs)
return np.mean(outs, axis=0)

def _static_lift_equivalent(self):
dia = (self.V_bed*4/self.reactor_height_to_diameter/pi) ** (1/3)
L = dia * self.reactor_height_to_diameter
void = self.voidage
mixed = self._mixed
rho = mixed.rho
mu = mixed.mu
d = self.bead_diameter * 1e-3
A_bed = (pi*self.V_bed**2/4/self.reactor_height_to_diameter**2)**(1/3)
A_liq = A_bed * void
u = mixed.F_vol/A_liq/3600 # m/s
dP = L * (150 * mu * (1-void)**2 * u / (void**3 * d**2) \
+ 1.75 * (1-void) * rho * u**2 / (void**3 * d))
return dP / (9.81 * rho) + L # Pa to m

#%% Batch experiment

class METAB_BatchExp(METAB_FluidizedBed):
Expand Down
15 changes: 8 additions & 7 deletions tests/test_metab.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,24 +27,25 @@ def test_metab():
UASB_M.simulate(state_reset_hook='reset_cache', method='BDF', t_span=(0, 400))
fs = UASB_M.flowsheet.stream
assert np.isclose(1 - fs.eff_dg.COD / fs.inf.COD, 0.9067142448236405, rtol)
assert np.isclose(UASB_M.TEA.annualized_NPV, -123994.44966853957, rtol)
#!!! add back test after biosteam pump design gets updated.
# assert np.isclose(UASB_M.TEA.annualized_NPV, -19128.91741988097, rtol)
assert np.isclose(UASB_M.LCA.total_impacts['GWP100'], 613761.7802056486, rtol)

FB_H = create_system(n_stages=2, reactor_type='FB', gas_extraction='H', tot_HRT=4)
FB_H.simulate(state_reset_hook='reset_cache', method='BDF', t_span=(0, 400))
fs = FB_H.flowsheet.stream
assert np.isclose(1 - fs.eff_dg.COD / fs.inf.COD, 0.8254341848530495, rtol)
assert np.isclose(FB_H.TEA.annualized_NPV, -34968.07019357201, rtol)
assert np.isclose(FB_H.LCA.total_impacts['GWP100'], 1004100.126558344, rtol)
assert np.isclose(1 - fs.eff_dg.COD / fs.inf.COD, 0.8254350623696006, rtol)
# assert np.isclose(FB_H.TEA.annualized_NPV, -26069.226184087474, rtol)
assert np.isclose(FB_H.LCA.total_impacts['GWP100'], 631855.2829115734, rtol)

PB_P = create_system(n_stages=2, reactor_type='PB', gas_extraction='P', tot_HRT=4)
# Might fail the first time it runs, re-running will usually fix the problem
try: PB_P.simulate(state_reset_hook='reset_cache', method='BDF', t_span=(0, 400))
except: PB_P.simulate(state_reset_hook='reset_cache', method='BDF', t_span=(0, 400))
fs = PB_P.flowsheet.stream
assert np.isclose(1 - fs.eff_dg.COD / fs.inf.COD, 0.8261047568019022, rtol)
assert np.isclose(PB_P.TEA.annualized_NPV, -50509.157739631584, rtol)
assert np.isclose(PB_P.LCA.total_impacts['GWP100'], 178566.02455877443, rtol)
assert np.isclose(1 - fs.eff_dg.COD / fs.inf.COD, 0.8261060899768736, rtol)
# assert np.isclose(PB_P.TEA.annualized_NPV, -39132.00024529493, rtol)
assert np.isclose(PB_P.LCA.total_impacts['GWP100'], 178567.97333664406, rtol)

if __name__ == '__main__':
test_metab()

0 comments on commit 0d11614

Please sign in to comment.