diff --git a/exposan/htl/README.rst b/exposan/htl/README.rst new file mode 100644 index 00000000..2ce70213 --- /dev/null +++ b/exposan/htl/README.rst @@ -0,0 +1,200 @@ +============================================================================================= +htl: Hydrothermal Liquefaction-based Wet Organic Waste Management / Biofuel Production System +============================================================================================= + +Summary +------- +This module includes a hydrothermal liquefaction (HTL)-based system for wet organic waste management and biofuel production, as described in +Feng et al. [1]_ + +Key sanunits included in this module include: + + - hydrothermal liquefaction (``HydrothermalLiquefaction``) + - catalytic hydrothermal gasification (``CatalyticHydrothermalGasification``) + - hydrotreating (``Hydrotreaing``) + - hydrocracking (``Hydrocracking``) + - combined heat and power unit (``CombinedHeatPower``) + +Feedstocks for the htl system are characterized by their moisture content and biochemical compositions (ash, lipid, protein, carbohydrate). +Tested feedstocks include: + + - wastewater sludge/biosolid + - food waste + - fats, oils, greases (FOG) + - green waste (a.k.a. yard waste) + - animal manure + +The products of this system include: + + - struvite + - ammonium sulfate + - naphtha + - renewable diesel + - electricity + +The system has three different configurations: + + - baseline (``configuration='baseline'``) + - remove phosphorus extraction from hydrochar (``configuration='no_P'``) + - add a pressure swing adsorption unit for hydrogen recycle (``configuration='PSA'``) + +An example htl system layout (``configuration='baseline'``): + +.. figure:: ./readme_figures/example_system.png + +A full list of the packages in the environment used to generate the results is included in `htl.yml `_. + +Getting Started +--------------- +.. code-block:: python + + >>> # create an htl system by specifying system configuration, capacity, and feedstock's compositions: + >>> from exposan.htl import create_system + >>> sys = create_system( + ... configuration='baseline', # system configurations ('baseline','no_P','PSA') + ... capacity=100, # capacity in metric ton per day + ... sludge_moisture_content=0.8, + ... sludge_dw_ash_content=0.257, + ... sludge_afdw_lipid_content=0.204, + ... sludge_afdw_protein_content=0.463, + ... ) + + >>> sys.show() + System: sys_baseline + ins... + [0] feedstock_assumed_in_wastewater + phase: 'l', T: 298.15 K, P: 101325 Pa + flow (kmol/hr): H2O 8.73e+05 + [1] makeup_water + phase: 'l', T: 298.15 K, P: 101325 Pa + flow: 0 + [2] recycle + phase: 'l', T: 298.15 K, P: 101325 Pa + flow: 0 + [3] H2SO4 + phase: 'l', T: 298.15 K, P: 101325 Pa + flow (kmol/hr): H2O 677 + H2SO4 6.09 + [4] MgCl2 + phase: 's', T: 298.15 K, P: 101325 Pa + flow (kmol/hr): MgCl2 0.933 + [5] NH4Cl + phase: 's', T: 298.15 K, P: 101325 Pa + flow: 0 + [6] MgO + phase: 's', T: 298.15 K, P: 101325 Pa + flow (kmol/hr): MgO 1.46 + [7] 7.8%_Ru/C + phase: 's', T: 298.15 K, P: 101325 Pa + flow (kmol/hr): CHG_catalyst 0.745 + [8] NaOH + phase: 'l', T: 298.15 K, P: 101325 Pa + flow (kmol/hr): NaOH 0.00203 + [9] Membrane_in + phase: 'l', T: 298.15 K, P: 101325 Pa + flow (kmol/hr): Membrane 0.0966 + [10] H2 + phase: 'l', T: 298.15 K, P: 101325 Pa + flow (kmol/hr): H2 98.8 + [11] CoMo_alumina_HT + phase: 's', T: 298.15 K, P: 101325 Pa + flow (kmol/hr): HT_catalyst 0.149 + [12] CoMo_alumina_HC + phase: 's', T: 298.15 K, P: 101325 Pa + flow (kmol/hr): HC_catalyst 0.00532 + [13] s3 + phase: 'l', T: 298.15 K, P: 101325 Pa + flow: 0 + [14] natural_gas + phase: 'g', T: 298.15 K, P: 101325 Pa + flow: 0 + [15] air + phase: 'g', T: 298.15 K, P: 101325 Pa + flow (kmol/hr): O2 83.4 + N2 314 + outs... + [0] treated_water + phase: 'l', T: 298.15 K, P: 101325 Pa + flow (kmol/hr): H2O 8.72e+05 + [1] residual + phase: 's', T: 333.15 K, P: 2.08891e+07 Pa + flow (kmol/hr): Residual 327 + [2] struvite + phase: 's', T: 333.15 K, P: 101325 Pa + flow (kmol/hr): Struvite 1.98 + [3] ammonium_sulfate + phase: 'l', T: 298.15 K, P: 101325 Pa + flow (kmol/hr): NH42SO4 4.64 + [4] Membrane_out + phase: 'l', T: 298.15 K, P: 101325 Pa + flow (kmol/hr): Membrane 0.0966 + [5] solution + phase: 'l', T: 298.15 K, P: 101325 Pa + flow (kmol/hr): H2O 526 + H2SO4 0.0898 + [6] gasoline + phase: 'l', T: 333.15 K, P: 137895 Pa + flow (kmol/hr): C4H10 0.00747 + TWOMBUTAN 0.0154 + NPENTAN 0.11 + TWOMPENTA 0.0579 + CYCHEX 0.0277 + HEXANE 0.0638 + TWOMHEXAN 0.0509 + ... 1.96 + [7] diesel + phase: 'l', T: 333.15 K, P: 128932 Pa + flow (kmol/hr): C9H20 0.000619 + C10H22 0.0833 + C4BENZ 0.115 + C11H24 0.307 + C10H12 0.195 + C12H26 0.254 + C13H28 0.0671 + ... 3.11 + [8] CHG_catalyst_out + phase: 's', T: 298.15 K, P: 101325 Pa + flow (kmol/hr): CHG_catalyst 0.745 + [9] HT_catalyst_out + phase: 's', T: 298.15 K, P: 101325 Pa + flow (kmol/hr): HT_catalyst 0.149 + [10] HC_catalyst_out + phase: 's', T: 298.15 K, P: 101325 Pa + flow (kmol/hr): HC_catalyst 0.00532 + [11] wastewater + phase: 'l', T: 332.93 K, P: 344738 Pa + flow (kmol/hr): H2O 1.14e+03 + C 9.47 + N 3.84 + P 0.708 + [12] emission + phase: 'g', T: 298.15 K, P: 101325 Pa + flow (kmol/hr): H2O 115 + N2 314 + CO2 42 + [13] solid_ash + phase: 's', T: 298.15 K, P: 101325 Pa + flow: 0 + + >>> # You can look at TEA and LCA results by: + >>> sys.TEA + HTL_TEA: sys_baseline + NPV: -27,298,818 USD at 3.0% IRR + + >>> sys.LCA + LCA: sys_baseline (lifetime 30 yr) + Impacts: + Construction Transportation Stream Others Total + Ecotoxicity (kg 2,4-D-eq) 6.94e+06 0 2.69e+09 1.63e+08 2.86e+09 + OzoneDepletion (kg CFC-11-eq) 0.205 0 -104 33.9 -69.5 + Carcinogenics (kg benzene-eq) 1.22e+05 0 2.23e+05 2.95e+06 3.3e+06 + PhotochemicalOxidation (kg NOx-eq) 1.23e+04 0 5.33e+05 3.87e+05 9.33e+05 + Eutrophication (kg N) 1e+03 0 -4.62e+05 3.48e+04 -4.26e+05 + RespiratoryEffects (kg PM2.5-eq) 1.49e+04 0 4.1e+05 2.43e+05 6.67e+05 + Acidification (moles of H+-eq) 1.27e+06 0 1.17e+08 4.54e+07 1.63e+08 + NonCarcinogenics (kg toluene-eq) 1.49e+08 0 7.26e+09 3.18e+09 1.06e+10 + GlobalWarming (kg CO2-eq) 4.87e+06 0 -1e+08 4.19e+08 3.24e+08 + +References +---------- +.. [1] Feng et al., Characterizing the Opportunity Space for Sustainable Hydrothermal Valorization of Wet Organic Wastes. *Environmental Science and Technology*, *In revision* diff --git a/exposan/htl/__init__.py b/exposan/htl/__init__.py index 9b280df0..1d55720d 100644 --- a/exposan/htl/__init__.py +++ b/exposan/htl/__init__.py @@ -65,7 +65,6 @@ def load(configuration='baseline'): dct = globals() dct.update(sys.flowsheet.to_dict()) - def __getattr__(name): if not _components_loaded or not _system_loaded: raise AttributeError( @@ -77,7 +76,7 @@ def __getattr__(name): from .models import * def simulate_and_save(model, - resample=True, samples_kwargs={'N':1000, 'rule':'L', 'seed':None}, + resample=True, samples_kwargs={'N':1000, 'rule':'L', 'seed':3221}, include_spearman=True, spearman_kwargs={'nan_policy': 'omit'}, export_results=True, path='',notes=''): if resample: @@ -98,7 +97,7 @@ def simulate_and_save(model, if export_results: ID = model.system.flowsheet.ID N = model.table.shape[0] - path = path or os.path.join(results_path, f'{date.today()}__{ID}_{N}_{notes}.xlsx') + path = path or os.path.join(results_path, f'{date.today()}_{ID}_{N}_{notes}.xlsx') with pd.ExcelWriter(path) as writer: parameters.to_excel(writer, sheet_name='Parameters') results.to_excel(writer, sheet_name='Results') @@ -107,7 +106,6 @@ def simulate_and_save(model, r_df.to_excel(writer, sheet_name='Spearman_r') p_df.to_excel(writer, sheet_name='Spearman_p') - __all__ = ( 'htl_path', 'data_path', diff --git a/exposan/htl/_components.py b/exposan/htl/_components.py index a05b261e..9cee9758 100644 --- a/exposan/htl/_components.py +++ b/exposan/htl/_components.py @@ -100,10 +100,10 @@ def create_components(set_thermo=True): # made up value, so that HTL.ins[0].nu = 0.03 m2/s ~30000 cSt # (NREL 2013 appendix B) - Biochar = Component('Biochar', phase='s', particle_size='Particulate', + Hydrochar = Component('Hydrochar', phase='s', particle_size='Particulate', degradability='Undegradable', organic=False) - add_V_from_rho(Biochar, 1500) # assume 1500kg/m3 - Biochar.copy_models_from(Chemical('CaCO3'),('Cn',)) + add_V_from_rho(Hydrochar, 1500) # assume 1500kg/m3 + Hydrochar.copy_models_from(Chemical('CaCO3'),('Cn',)) Biocrude = Component('Biocrude', search_ID='629-54-9', particle_size='Soluble', degradability='Slowly', @@ -384,7 +384,7 @@ def create_components(set_thermo=True): Membrane.copy_models_from(Chemical('CaCO3'),('Cn',)) cmps = Components([Sludge_lipid, Sludge_protein, Sludge_carbo, Sludge_ash, - Struvite, Biochar, Residual, + Struvite, Hydrochar, Residual, Biocrude, HTLaqueous, H2O, C, N, P, O2, N2, CH4, C2H6, C3H8, CO2, CO, H2, NH3, H2SO4, H3PO4, MgCl2, MgO, NaOH, NH42SO4, NH4Cl, diff --git a/exposan/htl/_process_settings.py b/exposan/htl/_process_settings.py index 12df74d3..f26f3868 100644 --- a/exposan/htl/_process_settings.py +++ b/exposan/htl/_process_settings.py @@ -64,7 +64,7 @@ def _load_process_settings(): # ============================================================================= # set utility prices # ============================================================================= - bst.PowerUtility.price = 0.06879 + bst.PowerUtility.price = 0.06879 # !!! the electricity price can be adjusted here # # These utilities are provided by CHP thus cost already considered # # setting the regeneration price to 0 or not will not affect the final results diff --git a/exposan/htl/_sanunits.py b/exposan/htl/_sanunits.py index 3b55c81c..259465a6 100644 --- a/exposan/htl/_sanunits.py +++ b/exposan/htl/_sanunits.py @@ -25,6 +25,7 @@ 'AcidExtraction', 'FuelMixer', 'HTLmixer', + 'Humidifier', 'StruvitePrecipitation', 'WWmixer', 'WWTP', @@ -34,215 +35,180 @@ _m3perh_to_MGD = auom('m3/h').conversion_factor('MGD') # ============================================================================= -# Sludge Lab +# Acid Extraction # ============================================================================= -class WWTP(SanUnit): +class AcidExtraction(Reactor): ''' - WWTP is a fake unit that can set up sludge biochemical compositions - and calculate sludge elemental compositions. + H2SO4 is added to hydrochar from HTL to extract phosphorus. Parameters ---------- ins : Iterable(stream) - ww. + hydrochar, acid. outs : Iterable(stream) - sludge, treated. - ww_2_dry_sludge: float - Wastewater-to-dry-sludge conversion factor, [metric ton/day/MGD]. - sludge_moisture: float - Sludge moisture content. - sludge_dw_ash: float - Sludge dry weight ash content. - sludge_afdw_lipid: float - Sludge ash free dry weight lipid content. - sludge_afdw_protein: float - Sludge ash free dry weight protein content. - lipid_2_C: float - Lipid to carbon factor. - protein_2_C: float - Protein to carbon factor. - carbo_2_C: float - Carbohydrate to carbon factor. - C_2_H: float - Carbon to hydrogen factor. - protein_2_N: float - Protein to nitrogen factor. - N_2_P: float - Nitrogen to phosphorus factor. - operation_hour: float - Plant yearly operation hour, [hr/yr]. + residual, extracted. + acid_vol: float + 0.5 M H2SO4 to hydrochar ratio: mL/g. + P_acid_recovery_ratio: float + The ratio of phosphorus that can be extracted. References ---------- - .. [1] Metcalf and Eddy, Incorporated. 1991. Wastewater Engineering: - Treatment Disposal and Reuse. New York: McGraw-Hill. - .. [2] Li, Y.; Leow, S.; Fedders, A. C.; Sharma, B. K.; Guest, J. S.; - Strathmann, T. J. Quantitative Multiphase Model for Hydrothermal - Liquefaction of Algal Biomass. Green Chem. 2017, 19 (4), 1163–1174. - https://doi.org/10.1039/C6GC03294J. + .. [1] Zhu, Y.; Schmidt, A.; Valdez, P.; Snowden-Swan, L.; Edmundson, S. + Hydrothermal Liquefaction and Upgrading of Wastewater-Grown Microalgae: + 2021 State of Technology; PNNL-32695, 1855835; 2022; p PNNL-32695, 1855835. + https://doi.org/10.2172/1855835. ''' - _N_ins = 1 + _N_ins = 2 _N_outs = 2 - + _F_BM_default = {**Reactor._F_BM_default} + def __init__(self, ID='', ins=None, outs=(), thermo=None, - init_with='WasteStream', - ww_2_dry_sludge=0.94, # [1] - sludge_moisture=0.99, sludge_dw_ash=0.257, - sludge_afdw_lipid=0.204, sludge_afdw_protein=0.463, - lipid_2_C=0.750, protein_2_C=0.545, - carbo_2_C=0.400, C_2_H=0.1427, - protein_2_N=0.159, N_2_P=0.3927, - operation_hours=yearly_operation_hour): + init_with='WasteStream', acid_vol=7, P_acid_recovery_ratio=0.8, + P=None, tau=2, V_wf=0.8, # tau: [1] + length_to_diameter=2, N=1, V=10, auxiliary=False, + mixing_intensity=None, kW_per_m3=0, # use MixTank default value + wall_thickness_factor=1, + vessel_material='Stainless steel 304', # acid condition + vessel_type='Vertical'): SanUnit.__init__(self, ID, ins, outs, thermo, init_with) - self.ww_2_dry_sludge = ww_2_dry_sludge - self.sludge_moisture = sludge_moisture - self.sludge_dw_ash = sludge_dw_ash - self.sludge_afdw_lipid = sludge_afdw_lipid - self.sludge_afdw_protein = sludge_afdw_protein - self.lipid_2_C = lipid_2_C - self.protein_2_C = protein_2_C - self.carbo_2_C = carbo_2_C - self.C_2_H = C_2_H - self.protein_2_N = protein_2_N - self.N_2_P = N_2_P - self.operation_hours = operation_hours - + self.acid_vol = acid_vol + self.P_acid_recovery_ratio = P_acid_recovery_ratio + self.P = P + self.tau = tau + self.V_wf = V_wf + self.length_to_diameter = length_to_diameter + self.N = N + self.V = V + self.auxiliary = auxiliary + self.mixing_intensity = mixing_intensity + self.kW_per_m3 = kW_per_m3 + self.wall_thickness_factor = wall_thickness_factor + self.vessel_material = vessel_material + self.vessel_type = vessel_type + def _run(self): - ww = self.ins[0] - sludge, treated = self.outs - - self.sludge_afdw_carbo = round(1 - self.sludge_afdw_protein - self.sludge_afdw_lipid, 5) + hydrochar, acid = self.ins + residual, extracted = self.outs - if self.sludge_dw_ash >= 1: - raise Exception ('ash can not be larger than or equal to 1') + self.HTL = self.ins[0]._source - if self.sludge_afdw_protein + self.sludge_afdw_lipid > 1: - raise Exception ('protein and lipid exceed 1') + if hydrochar.F_mass <= 0: + pass + else: + if self.HTL.hydrochar_P <= 0: + residual.copy_like(hydrochar) + else: + acid.imass['H2SO4'] = hydrochar.F_mass*self.acid_vol*0.5*98.079/1000 + # 0.5 M H2SO4 acid_vol (10 mL/1 g) hydrochar + acid.imass['H2O'] = hydrochar.F_mass*self.acid_vol*1.05 -\ + acid.imass['H2SO4'] + # 0.5 M H2SO4 density: 1.05 kg/L + # https://www.fishersci.com/shop/products/sulfuric-acid-1n-0-5m- + # standard-solution-thermo-scientific/AC124240010 (accessed 10-6-2022) + + residual.imass['Residual'] = hydrochar.F_mass - self.ins[0]._source.\ + hydrochar_P*self.P_acid_recovery_ratio + + extracted.copy_like(acid) + extracted.imass['P'] = hydrochar.F_mass - residual.F_mass + # assume just P can be extracted + + residual.phase = 's' + + residual.T = extracted.T = hydrochar.T + residual.P = hydrochar.P + # H2SO4 reacts with hydrochar to release heat and temperature will be + # increased mixture's temperature - self.sludge_dw = ww.F_vol*_m3perh_to_MGD*self.ww_2_dry_sludge*1000/24 - - sludge.imass['H2O'] = self.sludge_dw/(1-self.sludge_moisture)*self.sludge_moisture - sludge.imass['Sludge_ash'] = self.sludge_dw*self.sludge_dw_ash - - sludge_afdw = self.sludge_dw*(1 - self.sludge_dw_ash) - sludge.imass['Sludge_lipid'] = sludge_afdw*self.sludge_afdw_lipid - sludge.imass['Sludge_protein'] = sludge_afdw*self.sludge_afdw_protein - sludge.imass['Sludge_carbo'] = sludge_afdw*self.sludge_afdw_carbo - - treated.imass['H2O'] = ww.F_mass - sludge.F_mass - @property - def sludge_dw_protein(self): - return self.sludge_afdw_protein*(1-self.sludge_dw_ash) - - @property - def sludge_dw_lipid(self): - return self.sludge_afdw_lipid*(1-self.sludge_dw_ash) - - @property - def sludge_dw_carbo(self): - return self.sludge_afdw_carbo*(1-self.sludge_dw_ash) - - @property - def sludge_C_ratio(self): - return self.sludge_dw_protein*self.protein_2_C + self.sludge_dw_lipid*self.lipid_2_C +\ - self.sludge_dw_carbo*self.carbo_2_C - - @property - def sludge_H_ratio(self): - return self.sludge_C_ratio*self.C_2_H - - @property - def sludge_N_ratio(self): - return self.sludge_dw_protein*self.protein_2_N - - @property - def sludge_P_ratio(self): - return self.sludge_N_ratio*self.N_2_P - - @property - def sludge_O_ratio(self): - return 1 - self.sludge_C_ratio - self.sludge_H_ratio -\ - self.sludge_N_ratio - self.sludge_dw_ash - - @property - def sludge_C(self): - return self.sludge_C_ratio*self.sludge_dw - - @property - def sludge_H(self): - return self.sludge_H_ratio*self.sludge_dw - - @property - def sludge_N(self): - return self.sludge_N_ratio*self.sludge_dw - - @property - def sludge_P(self): - return self.sludge_P_ratio*self.sludge_dw + def residual_C(self): + return self.ins[0]._source.hydrochar_C @property - def sludge_O(self): - return self.sludge_O_ratio*self.sludge_dw - - @property - def AOSc(self): - return (3*self.sludge_N_ratio/14.0067 + 2*self.sludge_O_ratio/15.999 -\ - self.sludge_H_ratio/1.00784)/(self.sludge_C_ratio/12.011) - - @property - def sludge_HHV(self): - return 100*(0.338*self.sludge_C_ratio + 1.428*(self.sludge_H_ratio -\ - self.sludge_O_ratio/8)) # [2] - - @property - def H_C_eff(self): - return (self.sludge_H/1.00784-2*self.sludge_O/15.999)/self.sludge_C*12.011 - + def residual_P(self): + return self.ins[0]._source.hydrochar_P - self.outs[1].imass['P'] + + def _design(self): + self.N = ceil(self.HTL.WWTP.ins[0].F_vol/788.627455/self.V) + # 1/788.627455 m3 reactor/m3 wastewater/h (50 MGD ~ 10 m3) + self.P = self.ins[1].P + Reactor._design(self) # ============================================================================= -# WWmixer +# FuelMixer # ============================================================================= -class WWmixer(SanUnit): +class FuelMixer(SanUnit): ''' - A fake unit that mix all wastewater streams and calculates C, N, P, and H2O - amount. + Convert gasoline to diesel or diesel to gasoline based on LHV. + Parameters ---------- - ins : Iterable(stream) - supernatant_1, supernatant_2, memdis_ww, ht_ww - outs : Iterable(stream) - mixture + ins: Iterable(stream) + gasoline, diesel + outs: Iterable(stream) + fuel + target: str + The target can only be 'gasoline' or 'diesel'. + gasoline_price: float + Gasoline price, [$/kg]. + diesel_price: float + Diesel price, [$/kg]. ''' - _N_ins = 3 + _N_ins = 2 _N_outs = 1 - def __init__(self, ID='', ins=None, outs=(), thermo=None, init_with='Stream'): + def __init__(self, ID='', ins=None, outs=(), thermo=None, + init_with='WasteStream', target='diesel', + gasoline_gal_2_kg=2.834894885, + diesel_gal_2_kg=3.220628346, + gasoline_price=0.9388, + diesel_price=0.9722): SanUnit.__init__(self, ID, ins, outs, thermo, init_with) - + self.target = target + self.gasoline_gal_2_kg = gasoline_gal_2_kg + self.diesel_gal_2_kg = diesel_gal_2_kg + self.gasoline_price = gasoline_price + self.diesel_price = diesel_price + def _run(self): - supernatant, memdis_ww, ht_ww = self.ins - mixture = self.outs[0] - - mixture.mix_from(self.ins) + gasoline, diesel = self.ins + fuel = self.outs[0] + target = self.target - HT = self.ins[2]._source.ins[0]._source.ins[0]._source.ins[0]._source.\ - ins[0]._source.ins[0]._source + gasoline_LHV_2_diesel_LHV = (gasoline.LHV/gasoline.F_mass)/(diesel.LHV/diesel.F_mass) + # KJ/kg gasoline:KJ/kg diesel - # only account for C and N from HT if they are not less than 0 - if HT.HTaqueous_C >= 0: - mixture.imass['C'] += HT.HTaqueous_C - mixture.imass['H2O'] -= HT.HTaqueous_C - if HT.HTaqueous_N >=0: - mixture.imass['N'] += HT.HTaqueous_N - mixture.imass['H2O'] -= HT.HTaqueous_N - + if target == 'gasoline': + fuel.imass['Gasoline'] = gasoline.F_mass + diesel.F_mass/gasoline_LHV_2_diesel_LHV + fuel.T = gasoline.T + fuel.P = gasoline.P + elif target == 'diesel': + fuel.imass['Diesel'] = diesel.F_mass + gasoline.F_mass*gasoline_LHV_2_diesel_LHV + fuel.T = diesel.T + fuel.P = diesel.P + + def _cost(self): + if self.target == 'gasoline': + self.outs[0].price = self.gasoline_price + elif self.target == 'diesel': + self.outs[0].price = self.diesel_price + + @property + def target(self): + return self._target + @target.setter + def target(self, i): + if i not in ('gasoline', 'diesel'): + raise ValueError('`target` must be either "gasoline" or "diesel" ', + f'the provided "{i}" is not valid.') + self._target = i # ============================================================================= # HTL mixer @@ -307,111 +273,44 @@ def pH(self): hydrogen_ion_conc = 10**0/dilution_factor return -log(hydrogen_ion_conc, 10) - # ============================================================================= -# Acid Extraction +# Humidifier # ============================================================================= -class AcidExtraction(Reactor): - ''' - H2SO4 is added to biochar from HTL to extract phosphorus. - - Parameters - ---------- - ins : Iterable(stream) - biochar, acid. - outs : Iterable(stream) - residual, extracted. - acid_vol: float - 0.5 M H2SO4 to biochar ratio: mL/g. - P_acid_recovery_ratio: float - The ratio of phosphorus that can be extracted. - - References - ---------- - .. [1] Zhu, Y.; Schmidt, A.; Valdez, P.; Snowden-Swan, L.; Edmundson, S. - Hydrothermal Liquefaction and Upgrading of Wastewater-Grown Microalgae: - 2021 State of Technology; PNNL-32695, 1855835; 2022; p PNNL-32695, 1855835. - https://doi.org/10.2172/1855835. +class Humidifier(SanUnit): ''' - _N_ins = 2 - _N_outs = 2 - _F_BM_default = {**Reactor._F_BM_default} - - def __init__(self, ID='', ins=None, outs=(), thermo=None, - init_with='WasteStream', acid_vol=7, P_acid_recovery_ratio=0.8, - P=None, tau=2, V_wf=0.8, # tau: [1] - length_to_diameter=2, N=1, V=10, auxiliary=False, - mixing_intensity=None, kW_per_m3=0, # use MixTank default value - wall_thickness_factor=1, - vessel_material='Stainless steel 304', # acid condition - vessel_type='Vertical'): - - SanUnit.__init__(self, ID, ins, outs, thermo, init_with) - self.acid_vol = acid_vol - self.P_acid_recovery_ratio = P_acid_recovery_ratio - self.P = P - self.tau = tau - self.V_wf = V_wf - self.length_to_diameter = length_to_diameter - self.N = N - self.V = V - self.auxiliary = auxiliary - self.mixing_intensity = mixing_intensity - self.kW_per_m3 = kW_per_m3 - self.wall_thickness_factor = wall_thickness_factor - self.vessel_material = vessel_material - self.vessel_type = vessel_type - - def _run(self): - - biochar, acid = self.ins - residual, extracted = self.outs - - self.HTL = self.ins[0]._source - - if biochar.F_mass <= 0: - pass - else: - if self.HTL.biochar_P <= 0: - residual.copy_like(biochar) - else: - acid.imass['H2SO4'] = biochar.F_mass*self.acid_vol*0.5*98.079/1000 - #0.5 M H2SO4 acid_vol (10 mL/1 g) Biochar - acid.imass['H2O'] = biochar.F_mass*self.acid_vol*1.05 -\ - acid.imass['H2SO4'] - # 0.5 M H2SO4 density: 1.05 kg/L - # https://www.fishersci.com/shop/products/sulfuric-acid-1n-0-5m- - # standard-solution-thermo-scientific/AC124240010 (accessed 10-6-2022) - - residual.imass['Residual'] = biochar.F_mass - self.ins[0]._source.\ - biochar_P*self.P_acid_recovery_ratio - - extracted.copy_like(acid) - extracted.imass['P'] = biochar.F_mass - residual.F_mass - # assume just P can be extracted - - residual.phase = 's' - - residual.T = extracted.T = biochar.T - residual.P = biochar.P - # H2SO4 reacts with biochar to release heat and temperature will be - # increased mixture's temperature - - @property - def residual_C(self): - return self.ins[0]._source.biochar_C + A fake unit increases the moisture content of HTL feedstocks to 80%. + Assume 80% of the water can be recovered from the end. Calcalute makeup water. + feedstock: X H2O + Y dry matter + target: 4Y H2O + Y dry matter + water recovery = 0.8 = (4Y - X - makeup water)/4Y + makeup water = 0.8Y - X - @property - def residual_P(self): - return self.ins[0]._source.biochar_P - self.outs[1].imass['P'] + Parameters + ---------- + ins : Iterable(stream) + feedstock, makeup, recycle + outs : Iterable(stream) + mixture + ''' + + def __init__(self, ID='', ins=None, outs=(), thermo=None, init_with='WasteStream'): - def _design(self): - self.N = ceil(self.HTL.WWTP.ins[0].F_vol/788.627455/self.V) - # 1/788.627455 m3 reactor/m3 wastewater/h (50 MGD ~ 10 m3) - self.P = self.ins[1].P - Reactor._design(self) + SanUnit.__init__(self, ID, ins, outs, thermo, init_with) + + _N_ins = 3 + _N_outs = 1 + + def _run(self): + + feedstock, makeup, recycle = self.ins + mixture = self.outs[0] + + makeup.imass['H2O'] = max(0, 0.8*(feedstock.F_mass - feedstock.imass['H2O']) - feedstock.imass['H2O']) + + recycle.imass['H2O'] = (feedstock.F_mass - feedstock.imass['H2O'])/0.2 - feedstock.F_mass - makeup.imass['H2O'] + mixture.mix_from(self.ins) # ============================================================================= # Struvite Precipitation @@ -541,76 +440,219 @@ def _design(self): self.P = self.ins[0].P Reactor._design(self) - # ============================================================================= -# FuelMixer +# WWmixer # ============================================================================= -class FuelMixer(SanUnit): +class WWmixer(SanUnit): ''' - Convert gasoline to diesel or diesel to gasoline based on LHV. - + A fake unit that mix all wastewater streams and calculates C, N, P, and H2O + amount. Parameters ---------- ins : Iterable(stream) - gasoline, diesel + supernatant_1, supernatant_2, memdis_ww, ht_ww outs : Iterable(stream) - fuel - target: str - The target can only be 'gasoline' or 'diesel'. - gasoline_price: float - Gasoline price, [$/kg]. - diesel_price: float - Diesel price, [$/kg]. + mixture ''' - _N_ins = 2 + _N_ins = 3 _N_outs = 1 - def __init__(self, ID='', ins=None, outs=(), thermo=None, - init_with='WasteStream', target='diesel', - gasoline_gal_2_kg=2.834894885, - diesel_gal_2_kg=3.220628346, - gasoline_price=0.9388, - diesel_price=0.9722): + def __init__(self, ID='', ins=None, outs=(), thermo=None, init_with='Stream'): SanUnit.__init__(self, ID, ins, outs, thermo, init_with) - self.target = target - self.gasoline_gal_2_kg = gasoline_gal_2_kg - self.diesel_gal_2_kg = diesel_gal_2_kg - self.gasoline_price = gasoline_price - self.diesel_price = diesel_price + + def _run(self): + + supernatant, memdis_ww, ht_ww = self.ins + mixture = self.outs[0] + + mixture.mix_from(self.ins) + + HT = self.ins[2]._source.ins[0]._source.ins[0]._source.ins[0]._source.\ + ins[0]._source.ins[0]._source + + # only account for C and N from HT if they are not less than 0 + if HT.HTaqueous_C >= 0: + mixture.imass['C'] += HT.HTaqueous_C + mixture.imass['H2O'] -= HT.HTaqueous_C + if HT.HTaqueous_N >=0: + mixture.imass['N'] += HT.HTaqueous_N + mixture.imass['H2O'] -= HT.HTaqueous_N +# ============================================================================= +# WWTP +# ============================================================================= + +class WWTP(SanUnit): + ''' + WWTP is a fake unit that can set up sludge biochemical compositions + and calculate sludge elemental compositions. + + Parameters + ---------- + ins : Iterable(stream) + ww. + outs : Iterable(stream) + sludge, treated. + ww_2_dry_sludge: float + Wastewater-to-dry-sludge conversion factor, [metric ton/day/MGD]. + sludge_moisture: float + Sludge moisture content. + sludge_dw_ash: float + Sludge dry weight ash content. + sludge_afdw_lipid: float + Sludge ash free dry weight lipid content. + sludge_afdw_protein: float + Sludge ash free dry weight protein content. + lipid_2_C: float + Lipid to carbon factor. + protein_2_C: float + Protein to carbon factor. + carbo_2_C: float + Carbohydrate to carbon factor. + C_2_H: float + Carbon to hydrogen factor. + protein_2_N: float + Protein to nitrogen factor. + N_2_P: float + Nitrogen to phosphorus factor. + operation_hour: float + Plant yearly operation hour, [hr/yr]. + + References + ---------- + .. [1] Metcalf and Eddy, Incorporated. 1991. Wastewater Engineering: + Treatment Disposal and Reuse. New York: McGraw-Hill. + .. [2] Li, Y.; Leow, S.; Fedders, A. C.; Sharma, B. K.; Guest, J. S.; + Strathmann, T. J. Quantitative Multiphase Model for Hydrothermal + Liquefaction of Algal Biomass. Green Chem. 2017, 19 (4), 1163–1174. + https://doi.org/10.1039/C6GC03294J. + ''' + _N_ins = 1 + _N_outs = 2 + def __init__(self, ID='', ins=None, outs=(), thermo=None, + init_with='WasteStream', + ww_2_dry_sludge=0.94, # [1] + sludge_moisture=0.99, sludge_dw_ash=0.257, + sludge_afdw_lipid=0.204, sludge_afdw_protein=0.463, + lipid_2_C=0.750, + protein_2_C=0.545, + carbo_2_C=0.400, + lipid_2_H=0.125, + protein_2_H=0.068, + carbo_2_H=0.067, + protein_2_N=0.159, + N_2_P=0.3927, + operation_hours=yearly_operation_hour): + + SanUnit.__init__(self, ID, ins, outs, thermo, init_with) + self.ww_2_dry_sludge = ww_2_dry_sludge + self.sludge_moisture = sludge_moisture + self.sludge_dw_ash = sludge_dw_ash + self.sludge_afdw_lipid = sludge_afdw_lipid + self.sludge_afdw_protein = sludge_afdw_protein + self.lipid_2_C = lipid_2_C + self.protein_2_C = protein_2_C + self.carbo_2_C = carbo_2_C + self.lipid_2_H = lipid_2_H + self.protein_2_H = protein_2_H + self.carbo_2_H = carbo_2_H + self.protein_2_N = protein_2_N + self.N_2_P = N_2_P + self.operation_hours = operation_hours + def _run(self): - gasoline, diesel = self.ins - fuel = self.outs[0] - target = self.target + ww = self.ins[0] + sludge, treated = self.outs + + self.sludge_afdw_carbo = round(1 - self.sludge_afdw_protein - self.sludge_afdw_lipid, 5) - gasoline_LHV_2_diesel_LHV = (gasoline.LHV/gasoline.F_mass)/(diesel.LHV/diesel.F_mass) - # KJ/kg gasoline:KJ/kg diesel + if self.sludge_dw_ash >= 1: + raise Exception ('ash can not be larger than or equal to 1') - if target == 'gasoline': - fuel.imass['Gasoline'] = gasoline.F_mass + diesel.F_mass/gasoline_LHV_2_diesel_LHV - fuel.T = gasoline.T - fuel.P = gasoline.P - elif target == 'diesel': - fuel.imass['Diesel'] = diesel.F_mass + gasoline.F_mass*gasoline_LHV_2_diesel_LHV - fuel.T = diesel.T - fuel.P = diesel.P - - def _cost(self): - if self.target == 'gasoline': - self.outs[0].price = self.gasoline_price - elif self.target == 'diesel': - self.outs[0].price = self.diesel_price + if self.sludge_afdw_protein + self.sludge_afdw_lipid > 1: + raise Exception ('protein and lipid exceed 1') + self.sludge_dw = ww.F_vol*_m3perh_to_MGD*self.ww_2_dry_sludge*1000/24 + + sludge.imass['H2O'] = self.sludge_dw/(1-self.sludge_moisture)*self.sludge_moisture + sludge.imass['Sludge_ash'] = self.sludge_dw*self.sludge_dw_ash + + sludge_afdw = self.sludge_dw*(1 - self.sludge_dw_ash) + sludge.imass['Sludge_lipid'] = sludge_afdw*self.sludge_afdw_lipid + sludge.imass['Sludge_protein'] = sludge_afdw*self.sludge_afdw_protein + sludge.imass['Sludge_carbo'] = sludge_afdw*self.sludge_afdw_carbo + + treated.imass['H2O'] = ww.F_mass - sludge.F_mass + @property - def target(self): - return self._target - @target.setter - def target(self, i): - if i not in ('gasoline', 'diesel'): - raise ValueError('`target` must be either "gasoline" or "diesel" ', - f'the provided "{i}" is not valid.') - self._target = i \ No newline at end of file + def sludge_dw_protein(self): + return self.sludge_afdw_protein*(1-self.sludge_dw_ash) + + @property + def sludge_dw_lipid(self): + return self.sludge_afdw_lipid*(1-self.sludge_dw_ash) + + @property + def sludge_dw_carbo(self): + return self.sludge_afdw_carbo*(1-self.sludge_dw_ash) + + @property + def sludge_C_ratio(self): + return self.sludge_dw_protein*self.protein_2_C + self.sludge_dw_lipid*self.lipid_2_C +\ + self.sludge_dw_carbo*self.carbo_2_C + + @property + def sludge_H_ratio(self): + return self.sludge_dw_protein*self.protein_2_H + self.sludge_dw_lipid*self.lipid_2_H +\ + self.sludge_dw_carbo*self.carbo_2_H + + @property + def sludge_N_ratio(self): + return self.sludge_dw_protein*self.protein_2_N + + @property + def sludge_P_ratio(self): + return self.sludge_N_ratio*self.N_2_P + + @property + def sludge_O_ratio(self): + return 1 - self.sludge_C_ratio - self.sludge_H_ratio -\ + self.sludge_N_ratio - self.sludge_dw_ash + + @property + def sludge_C(self): + return self.sludge_C_ratio*self.sludge_dw + + @property + def sludge_H(self): + return self.sludge_H_ratio*self.sludge_dw + + @property + def sludge_N(self): + return self.sludge_N_ratio*self.sludge_dw + + @property + def sludge_P(self): + return self.sludge_P_ratio*self.sludge_dw + + @property + def sludge_O(self): + return self.sludge_O_ratio*self.sludge_dw + + @property + def AOSc(self): + return (3*self.sludge_N_ratio/14.0067 + 2*self.sludge_O_ratio/15.999 -\ + self.sludge_H_ratio/1.00784)/(self.sludge_C_ratio/12.011) + + @property + def sludge_HHV(self): + return 100*(0.338*self.sludge_C_ratio + 1.428*(self.sludge_H_ratio -\ + self.sludge_O_ratio/8)) # [2] + + @property + def H_C_eff(self): + return (self.sludge_H/1.00784-2*self.sludge_O/15.999)/self.sludge_C*12.011 \ No newline at end of file diff --git a/exposan/htl/_tea.py b/exposan/htl/_tea.py index d1f7ea3d..6f23ab96 100644 --- a/exposan/htl/_tea.py +++ b/exposan/htl/_tea.py @@ -166,7 +166,7 @@ def _FOC(self, FCI): + self.labor_cost * (1 + self.labor_burden)) -def create_tea(sys, OSBL_units=None, cls=None): +def create_tea(sys, OSBL_units=None, cls=None, IRR_value=0.1, finance_interest_value=0.08): if OSBL_units is None: OSBL_units = bst.get_OSBL(sys.cost_units) try: BT = tmo.utils.get_instance(OSBL_units, (bst.BoilerTurbogenerator, bst.Boiler)) @@ -175,11 +175,11 @@ def create_tea(sys, OSBL_units=None, cls=None): if cls is None: cls = HTL_TEA tea = cls( system=sys, - IRR=0.10, # Jones + IRR=IRR_value, # use 0%-3%-5% triangular distribution for waste management, and 5%-10%-15% triangular distribution for biofuel production duration=(2022, 2052), # Jones depreciation='MACRS7', # Jones income_tax=0.35, # Jones - operating_days=330, # Jones + operating_days=sys.operating_hours/24, # Jones lang_factor=None, # related to expansion, not needed here construction_schedule=(0.08, 0.60, 0.32), # Jones startup_months=6, # Jones @@ -187,7 +187,7 @@ def create_tea(sys, OSBL_units=None, cls=None): startup_salesfrac=0.5, # Davis NREL 2018 startup_VOCfrac=0.75, # Davis NREL 2018 WC_over_FCI=0.05, # Jones - finance_interest=0.08, # Jones + finance_interest=finance_interest_value, # use 3% for waste management, use 8% for biofuel finance_years=10, # Jones finance_fraction=0.6, # debt: Jones OSBL_units=OSBL_units, diff --git a/exposan/htl/analyses/MCF.py b/exposan/htl/analyses/MCF.py new file mode 100644 index 00000000..ae656576 --- /dev/null +++ b/exposan/htl/analyses/MCF.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Mon Nov 13 14:27:26 2023 + +@author: jiananfeng +""" + +import pandas as pd +from scipy import stats + +data = pd.read_csv('XXX.csv') + +for i in ['heat','CHGCAT','CHGYIELD','HTCAT','HTYIELD','HCCAT','HCYIELD','HTLCAP','CHGCAP', + 'HTCAP','OIL','AQUEOUS','BIOCHAR','GAS']: + if stats.kstest(data[i].dropna(), data[f'{i}_H'].dropna()).pvalue < 0.05: + print(i) + print(stats.kstest(data[i].dropna(), data[f'{i}_H'].dropna())) \ No newline at end of file diff --git a/exposan/htl/analyses/geospatial_analysis.py b/exposan/htl/analyses/geospatial_analysis.py new file mode 100644 index 00000000..b4cc5305 --- /dev/null +++ b/exposan/htl/analyses/geospatial_analysis.py @@ -0,0 +1,671 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Sun Jun 11 08:12:41 2023 + +@author: jiananfeng +""" + +from exposan.htl.geospatial_HTL_systems import create_spatial_system +from exposan.htl import _m3perh_to_MGD +from qsdsan.utils import palettes +import pandas as pd, geopandas as gpd, numpy as np, matplotlib.pyplot as plt +import matplotlib.ticker as mtick +import geopy.distance, googlemaps +from datetime import date +from warnings import filterwarnings + +folder = '/Users/jiananfeng/Desktop/PhD_CEE/NSF_PFAS/HTL_geospatial/' + +# color palette +Guest = palettes['Guest'] +b = Guest.blue.HEX +g = Guest.green.HEX +r = Guest.red.HEX +o = Guest.orange.HEX +y = Guest.yellow.HEX +a =Guest.gray.HEX + +def set_plot(figure_size=(30,30)): + global fig, ax + fig, ax = plt.subplots(figsize=(30, 30)) + ax.tick_params(top=False, bottom=False, left=False, right=False, + labelleft=False, labelbottom=False) + ax.set_frame_on(False) + +#%% + +# read data +sludge = pd.read_excel(folder + 'WWTP/sludge_catagorized_Seiple.xlsx', 'datasheet') + +sludge['solid_fate'] = sludge['BASELINE:Process Code'].str[-1].astype(int) + +# from Seiple et al. 2020 +# 1: AD, dewatering +# 2: Aerobic digestion, dewatering +# 3: Dewatering, lime stabilization +# 4: AD, dewatering, thermal drying +# 5: Dewatering, multiple-hearth incineration (MHI) +# 6: Dewatering, fluidized bed incineration (FBI) +# 7: Dewatering only +# 8: Lagoon/constructed wetland + +sludge = gpd.GeoDataFrame(sludge, crs='EPSG:4269', + geometry=gpd.points_from_xy(x=sludge.Longitude, + y=sludge.Latitude)) + +refinery = pd.read_excel(folder + 'refinery/petroleum_refineries_EIA.xlsx') +refinery = gpd.GeoDataFrame(refinery, crs='EPSG:4269', + geometry=gpd.points_from_xy(x=refinery.Longitude, + y=refinery.Latitude)) + +US = gpd.read_file(folder + 'US/cb_2018_us_state_500k.shp') +# confirmed from the wesbite (next line): this is no change in US map since June 1, 1995. So, using 2018 US map data is OK. +# https://en.wikipedia.org/wiki/Territorial_evolution_of_the_United_States#1946%E2%80%93present_(Decolonization) +US = US[['NAME', 'geometry']] + +for excluded in ('Alaska', + 'Hawaii', + 'Puerto Rico', + 'American Samoa', + 'Commonwealth of the Northern Mariana Islands', + 'Guam', + 'United States Virgin Islands'): + US = US.loc[US['NAME'] != excluded] + +sludge = sludge.to_crs(crs='EPSG:3857') +refinery = refinery.to_crs(crs='EPSG:3857') +US = US.to_crs(crs='EPSG:3857') + +# if only select states, uncomment the next line of code +# US = US.loc[US['NAME'].isin(('Pennsylvania',))] + +sludge = gpd.sjoin(sludge, US) +sludge = sludge.drop(['index_right'], axis=1) +refinery = gpd.sjoin(refinery, US) +refinery = refinery.drop(['index_right'], axis=1) + +elec = pd.read_excel(folder + 'state_elec_price_GHG.xlsx', 'summary') +solid_fate = pd.read_excel(folder + 'WWTP/solid_fate.xlsx') +solid_amount = pd.read_excel(folder + 'WWTP/sludge_uncategorized_Seiple.xlsx') + +electricity = pd.merge(elec, US, left_on='name', right_on='NAME') +electricity = gpd.GeoDataFrame(electricity) + +#%% + +# WRRFs visualization + +set_plot() + +US.plot(ax=ax, color='w', edgecolor='k', linewidth=5) # for figures in SI, use default linewidth + +less_than_10 = sludge['Influent Flow (MMGal/d)'] <= 10 +sludge[less_than_10].plot(ax=ax, color=Guest.gray.HEX, markersize=5, alpha=0.5) + +between_10_and_100 = (sludge['Influent Flow (MMGal/d)'] > 10) & (sludge['Influent Flow (MMGal/d)'] <= 100) +sludge[between_10_and_100].plot(ax=ax, color=Guest.red.HEX, markersize=100, edgecolor='k', linewidth=1.5, alpha=0.8) + +more_than_100 = sludge['Influent Flow (MMGal/d)'] > 100 +sludge[more_than_100].plot(ax=ax, color=Guest.green.HEX, markersize=500, edgecolor='k', linewidth=2) + +# if you want to show all WRRFs together with the same symbols, comment the codes above and uncomment the next line of code +# sludge.plot(ax=ax, color=Guest.gray.HEX, markersize=10) + +#%% + +# oil refinery visualization + +set_plot() + +US.plot(ax=ax, + color=[r, b, g, b, b, r, g, b, o, b, g, y, r, g, r, y, r, + b, b, g, o, o, g, o, b, g, y, g, b, o, g, b, b, y, + b, b, b, b, b, b, g, g, g, y, g, r, g, g, b], + alpha=0.4, + edgecolor='k', + linewidth=0) + +US.plot(ax=ax, color='none', edgecolor='k', linewidth=5) # for figures in SI, use default linewidth + +refinery.plot(ax=ax, color=Guest.orange.HEX, markersize=1000, edgecolor='k', linewidth=5) + +#%% + +# WRRFs+oil refineries visualization (just select states) + +set_plot() + +US.plot(ax=ax, color=Guest.blue.HEX, alpha=0.4, edgecolor='k', linewidth=0) +US.plot(ax=ax, color='none', edgecolor='k', linewidth=10) + +sludge.plot(ax=ax, color=Guest.gray.HEX, markersize=200) + +refinery.plot(ax=ax, color=Guest.orange.HEX, markersize=5000, edgecolor='k', linewidth=10) + +#%% + +# WRRFs+oil refineries visualization + +set_plot() + +US.plot(ax=ax, color='w', edgecolor='k', linewidth=3.5) +sludge.plot(ax=ax, color=Guest.gray.HEX, markersize=15) +refinery.plot(ax=ax, color=Guest.orange.HEX, markersize=500, edgecolor='k', linewidth=3.5) + +#%% + +# electricity price and carbon intensity visualization + +set_plot() + +# electricity.plot('price (10-year median)', ax=ax, cmap='Oranges', edgecolor='k', legend=True, legend_kwds={'shrink': 0.35}) + +electricity.plot('GHG (10-year median)', ax=ax, cmap='Blues', edgecolor='k', legend=True, legend_kwds={'shrink': 0.35}) + +#%% + +# transporation distance calculation + +# if want select WWTPs that are within certain ranges of refineries, set max_distance in sjoin_nearest. +# here we do not set a max distance + +WWTP_within = sludge.sjoin_nearest(refinery, max_distance=None, distance_col='distance') + +# if set max_distance = 100000, we have a problem here, that is CRS 3857 is not accurate at all, +# especially more far away from the equator, therefore can use a larger distance here, +# for example, 150000 (tested, when use 150000, all actual distances are smaller than 100000) +# as the max_distance and then use geopy.distance.geodesic to recalculate the distance and +# filter out those that are actually longer than 100000 + +WWTP_within['WWTP_location'] = list(zip(WWTP_within.Latitude_left, WWTP_within.Longitude_left)) +WWTP_within['refinery_location'] = list(zip(WWTP_within.Latitude_right, WWTP_within.Longitude_right)) + +linear_distance = [] +for i in range(len(WWTP_within)): + linear_distance.append(geopy.distance.geodesic(WWTP_within['WWTP_location'].iloc[i], WWTP_within['refinery_location'].iloc[i]).km) + +WWTP_within['linear_distance_km'] = linear_distance + +# if we set the max_distance, then uncomment the next line (replace 100 km when necessary) +# WWTP_within = WWTP_within[WWTP_within['linear_distance'] <= 100] + +gmaps = googlemaps.Client(key='XXX') # !!! get a google API key, and do not upload to GitHub +real_distance = [] +for i in range(len(WWTP_within)): + try: + print(i) + real_distance.append(gmaps.distance_matrix(WWTP_within['WWTP_location'].iloc[i], + WWTP_within['refinery_location'].iloc[i], + mode='driving')['rows'][0]['elements'][0]['distance']['value']/1000) + except KeyError: + print('--------------------------------') + real_distance.append('nan') + +WWTP_within['real_distance_km'] = real_distance + +# based on state of the WWTPs, determine electricity price and GHG + +WWTP_within = WWTP_within.merge(elec, left_on='ST', right_on='state') + +# based on solid fate, determine sludge amount and biochemical composition + +WWTP_within = WWTP_within.merge(solid_fate, left_on='solid_fate', right_on='category') + +WWTP_within = WWTP_within.merge(solid_amount, left_on=('FACILITY','ST','CITY'), right_on=('Facility Name','STATE','CITY')) + +WWTP_within.to_excel(folder + f'HTL_geospatial_model_input_{date.today()}.xlsx') # this will be the input for the future analysis + +#%% + +# travel distance box plot + +WWTP_within = pd.read_excel(folder + 'HTL_geospatial_model_input_final.xlsx') + +fig, ax = plt.subplots(figsize = (5, 8)) + +plt.rcParams['axes.linewidth'] = 3 +plt.rcParams['xtick.labelsize'] = 30 +plt.rcParams['ytick.labelsize'] = 30 +plt.yticks(fontname = 'Arial') + +ax = plt.gca() +ax.set_ylim([-50, 850]) +ax.tick_params(direction='inout', length=20, width=3, labelbottom=False, bottom=False, top=False, left=True, right=False) + +ax_right = ax.twinx() +ax_right.set_ylim(ax.get_ylim()) +ax_right.tick_params(direction='in', length=10, width=3, bottom=False, top=True, left=False, right=True, labelcolor='none') + +bp = plt.boxplot(WWTP_within.real_distance_km, showfliers=False, widths=0.5) + +for box in bp['boxes']: + box.set(color='k', linewidth=3) + +for whisker in bp['whiskers']: + whisker.set(color='k', linewidth=3) + +for median in bp['medians']: + median.set(color='k', linewidth=3) + +for cap in bp['caps']: + cap.set(color='k', linewidth=3) + +#%% + +# WRRFs GHG map (after filter out some WRRFs) + +WWTP_within = pd.read_excel(folder + 'HTL_geospatial_model_input_final.xlsx') + +WWTP_within = gpd.GeoDataFrame(WWTP_within, crs='EPSG:4269', + geometry=gpd.points_from_xy(x=WWTP_within.Longitude_left, + y=WWTP_within.Latitude_left)) + +WWTP_within = WWTP_within.to_crs(crs='EPSG:3857') + +set_plot() + +US.plot(ax=ax, color='w', edgecolor='k') + +# use k-ton/yr + +less_than_10 = WWTP_within['30_years_emission_ton_CO2']/30/1000 <= 10 +WWTP_within[less_than_10].plot(ax=ax, color=Guest.gray.HEX, markersize=5, alpha=0.5) + +between_10_and_100 = (WWTP_within['30_years_emission_ton_CO2']/30/1000 > 10) & (WWTP_within['30_years_emission_ton_CO2']/30/1000 <= 100) +WWTP_within[between_10_and_100].plot(ax=ax, color=Guest.red.HEX, markersize=100, edgecolor='k', linewidth=1.5, alpha=0.8) + +more_than_100 = WWTP_within['30_years_emission_ton_CO2']/30/1000 > 100 +WWTP_within[more_than_100].plot(ax=ax, color=Guest.green.HEX, markersize=500, edgecolor='k', linewidth=2) + +#%% + +# WRRFs sludge management GHG map (after filter out some WRRFs) + +WWTP_within = pd.read_excel(folder + 'HTL_geospatial_model_input_final.xlsx') + +WWTP_within = gpd.GeoDataFrame(WWTP_within, crs='EPSG:4269', + geometry=gpd.points_from_xy(x=WWTP_within.Longitude_left, + y=WWTP_within.Latitude_left)) + +WWTP_within = WWTP_within.to_crs(crs='EPSG:3857') + +set_plot() + +US.plot(ax=ax, color='w', edgecolor='k') + +# use k-ton/yr + +less_than_10 = WWTP_within['sludge_management_kg_CO2_per_day_AD_included']*365/1000000 <= 5 +WWTP_within[less_than_10].plot(ax=ax, color=Guest.gray.HEX, markersize=5, alpha=0.5) + +between_10_and_100 = (WWTP_within['sludge_management_kg_CO2_per_day_AD_included']*365/1000000 > 5) & (WWTP_within['sludge_management_kg_CO2_per_day_AD_included']*365/1000000 <= 20) +WWTP_within[between_10_and_100].plot(ax=ax, color=Guest.red.HEX, markersize=100, edgecolor='k', linewidth=1.5, alpha=0.8) + +more_than_100 = WWTP_within['sludge_management_kg_CO2_per_day_AD_included']*365/1000000 > 20 +WWTP_within[more_than_100].plot(ax=ax, color=Guest.green.HEX, markersize=500, edgecolor='k', linewidth=2) + +#%% + +# cumulative WRRFs capacity vs distances + +# remember to use the correct file +WWTP_within = pd.read_excel(folder + 'HTL_geospatial_model_input_final.xlsx') + +result = WWTP_within[['site_id']].drop_duplicates() + +for distance in np.linspace(0, 100, 1001): + WWTP_within_distance = WWTP_within[WWTP_within['linear_distance'] <= distance] + WWTP_within_distance = WWTP_within_distance.groupby('site_id').sum('Influent Flow (MMGal/d)') + WWTP_within_distance = WWTP_within_distance[['Influent Flow (MMGal/d)']] + WWTP_within_distance = WWTP_within_distance.rename(columns={'Influent Flow (MMGal/d)': int(distance)}) + WWTP_within_distance.reset_index(inplace=True) + if len(WWTP_within_distance) > 0: + result = result.merge(WWTP_within_distance, how='left', on='site_id') + +result = result.fillna(0) + +if len(result) < len(refinery): + result_index = pd.Index(result.site_id) + refinery_index = pd.Index(refinery.site_id) + refineries_left_id = refinery_index.difference(result_index).values + +result = result.set_index('site_id') +for item in refineries_left_id: + result.loc[item] = [0]*len(result.columns) + +result = result.merge(refinery, how='left', on='site_id') + +result.to_excel(folder + f'results/MGD_vs_distance_{date.today()}.xlsx') + +#%% + +# CO2 abatement cost analysis +# run analysis for every row + +filterwarnings('ignore') + +# remember to use the correct file +final_WWTPs = pd.read_excel(folder + 'HTL_geospatial_model_input_final.xlsx') +print(len(final_WWTPs)) + +facility = [] +city = [] +state = [] +CO2_reduction = [] +sludge_CO2_reduction_ratio = [] +WRRF_CO2_reduction_ratio = [] +USD_decarbonization = [] +oil_BPD = [] + +for i in range(14100, len(final_WWTPs)): +# for i in [0, 76, 718]: + + sys, barrel = create_spatial_system(waste_cost=400, + size=final_WWTPs.iloc[i]['Influent Flow (MMGal/d)'], + distance=final_WWTPs.iloc[i]['real_distance_km'], + solid_fate=final_WWTPs.iloc[i]['solid_fate'], + ww_2_dry_sludge_ratio=final_WWTPs.iloc[i]['BASELINE:SOLIDS (dry kg/y):Disposed (Biosolids)']/1000/365/final_WWTPs.iloc[i]['Influent Flow (MMGal/d)'], + # ww_2_dry_sludge_ratio: how much metric ton/day sludge can be produced by 1 MGD of ww + state=final_WWTPs.iloc[i]['state'], + elec_GHG=final_WWTPs.iloc[i]['GHG (10-year median)']) + + lca = sys.LCA + + flowsheet = sys.flowsheet + unit = flowsheet.unit + stream = flowsheet.stream + WWTP = unit.WWTP + raw_wastewater = stream.raw_wastewater + + kg_CO2_per_ton_dry_sludge = lca.get_total_impacts(exclude=(raw_wastewater,))['GlobalWarming']/raw_wastewater.F_vol/_m3perh_to_MGD/WWTP.ww_2_dry_sludge/(sys.operating_hours/24)/lca.lifetime + + CO2_reduction_result = final_WWTPs.iloc[i]['BASELINE:SOLIDS (dry kg/y):Disposed (Biosolids)']/1000*30*(final_WWTPs.iloc[i]['sludge_management_kg_CO2_per_ton']-kg_CO2_per_ton_dry_sludge) # kg CO2 + + sludge_CO2_reduction_ratio_result = CO2_reduction_result/final_WWTPs.iloc[i]['sludge_management_kg_CO2_per_day_AD_included']/365/30 + + WRRF_CO2_reduction_ratio_result = CO2_reduction_result/final_WWTPs.iloc[i]['30_years_emission_ton_CO2']/1000 + + # make sure CO2_reduction_result is positive if you want to calculate USD_per_ton_CO2_reduction + + if CO2_reduction_result > 0: + USD_per_ton_CO2_reduction = -sys.TEA.NPV/CO2_reduction_result*1000 # save money: the results are negative; spend money: the results are positive + else: + USD_per_ton_CO2_reduction = np.nan + + facility.append(final_WWTPs.iloc[i]['FACILITY']) + city.append(final_WWTPs.iloc[i]['CITY_x']) + state.append(final_WWTPs.iloc[i]['ST']) + CO2_reduction.append(CO2_reduction_result) + sludge_CO2_reduction_ratio.append(sludge_CO2_reduction_ratio_result) + WRRF_CO2_reduction_ratio.append(WRRF_CO2_reduction_ratio_result) + USD_decarbonization.append(USD_per_ton_CO2_reduction) + oil_BPD.append(barrel) + + if USD_per_ton_CO2_reduction < 0: + print('HERE WE GO!') + + if i%5 == 0: + print(i) # check progress + +result = {'facility': facility, + 'city': city, + 'state': state, + 'CO2_reduction': CO2_reduction, + 'sludge_CO2_reduction_ratio': sludge_CO2_reduction_ratio, + 'WRRF_CO2_reduction_ratio': WRRF_CO2_reduction_ratio, + 'USD_decarbonization': USD_decarbonization, + 'oil_BPD': oil_BPD} + +result = pd.DataFrame(result) + +result.to_excel(folder + f'results/decarbonization_{date.today()}_{i}.xlsx') + +#%% + +# plant-level decarbonization cost vs decarbonization ratio figure + +decarbonization_result = pd.read_excel(folder + 'results/decarbonization_results_10_7_2023/carbonization_summary.xlsx') + +WWTP_within = pd.read_excel(folder + 'HTL_geospatial_model_input_final.xlsx') + +decarbonization_result = decarbonization_result.merge(WWTP_within[['FACILITY','CITY_x','Influent Flow (MMGal/d)','category']], how='left', left_on=['facility','city'], right_on=['FACILITY','CITY_x']) + +decarbonization_result.loc[decarbonization_result['category'].isin((1, 2, 4)) ,'AD'] = b # blue + +decarbonization_result.loc[~decarbonization_result['category'].isin((1, 2, 4)) ,'AD'] = r # red + +decarbonization_result = decarbonization_result[decarbonization_result['USD_decarbonization'].notna()] + +decarbonization_result = decarbonization_result[decarbonization_result['USD_decarbonization'] <= 500] + +decarbonization_result_more = decarbonization_result[decarbonization_result['USD_decarbonization'] > -2000] + +decarbonization_result_less = decarbonization_result[decarbonization_result['USD_decarbonization'] < -2000] + +fig, ax_more = plt.subplots(figsize = (15, 10)) + +plt.rcParams['axes.linewidth'] = 2 +plt.rcParams['xtick.labelsize'] = 30 +plt.rcParams['ytick.labelsize'] = 30 +plt.xticks(fontname = 'Arial') +plt.yticks(fontname = 'Arial') + +ax_more = plt.gca() +ax_more.set_xlim([-3, 53]) +ax_more.set_ylim([-1625, 625]) +ax_more.xaxis.set_major_formatter(mtick.PercentFormatter(decimals=0)) +ax_more.tick_params(direction='inout', length=15, width=2, bottom=True, top=False, left=True, right=False) + +plt.xticks(np.arange(0, 55, 5)) + +ax_more_right = ax_more.twinx() +ax_more_right.set_ylim(ax_more.get_ylim()) +ax_more_right.tick_params(direction='in', length=7.5, width=2, bottom=False, top=True, left=False, right=True, labelcolor='none') + +ax_more_top = ax_more.twiny() +ax_more_top.set_xlim(ax_more.get_xlim()) +plt.xticks(np.arange(0, 55, 5)) +ax_more_top.tick_params(direction='in', length=7.5, width=2, bottom=False, top=True, left=False, right=True, labelcolor='none') + +ax_more.scatter(x = decarbonization_result_more['WRRF_CO2_reduction_ratio']*100, + y = decarbonization_result_more['USD_decarbonization'], + s = decarbonization_result_more['Influent Flow (MMGal/d)']*4, + c = decarbonization_result_more['AD'], + linewidths = 0, + alpha = 0.7) + +ax_more.scatter(x = decarbonization_result_more['WRRF_CO2_reduction_ratio']*100, + y = decarbonization_result_more['USD_decarbonization'], + s = decarbonization_result_more['Influent Flow (MMGal/d)']*4, + color = 'none', + linewidths = 2, + edgecolors = 'k') + +ax_less = fig.add_axes([0.5, 0.2, 0.36, 0.32]) + +plt.rcParams['axes.linewidth'] = 2 +plt.rcParams['xtick.labelsize'] = 30 +plt.rcParams['ytick.labelsize'] = 30 +plt.xticks(fontname = 'Arial') +plt.yticks(fontname = 'Arial') + +ax_less.set_xlim([-0.5, 3.5]) +ax_less.set_ylim([-40000, 0]) +ax_less.xaxis.set_major_formatter(mtick.PercentFormatter(decimals=0)) +ax_less.tick_params(direction='inout', length=15, width=2, bottom=True, top=False, left=True, right=False) + +plt.yticks(np.arange(-35000, 5000, 10000)) + +ax_less_right = ax_less.twinx() +ax_less_right.set_ylim(ax_less.get_ylim()) +plt.yticks(np.arange(-35000, 5000, 10000)) +ax_less_right.tick_params(direction='in', length=7.5, width=2, bottom=False, top=True, left=False, right=True, labelcolor='none') + +ax_less_top = ax_less.twiny() +ax_less_top.set_xlim(ax_less.get_xlim()) +ax_less_top.tick_params(direction='in', length=7.5, width=2, bottom=False, top=True, left=False, right=True, labelcolor='none') + +ax_less.scatter(x = decarbonization_result_less['WRRF_CO2_reduction_ratio']*100, + y = decarbonization_result_less['USD_decarbonization'], + s = decarbonization_result_less['Influent Flow (MMGal/d)']*4, + c = decarbonization_result_less['AD'], + linewidths = 0, + alpha = 0.7) + +ax_less.scatter(x = decarbonization_result_less['WRRF_CO2_reduction_ratio']*100, + y = decarbonization_result_less['USD_decarbonization'], + s = decarbonization_result_less['Influent Flow (MMGal/d)']*4, + color = 'none', + linewidths = 2, + edgecolors = 'k') + +#%% + +# regional level analysis + +decarbonization_result = pd.read_excel(folder + 'results/decarbonization_results_10_7_2023/carbonization_summary.xlsx') + +WWTP_within = pd.read_excel(folder + 'HTL_geospatial_model_input_final.xlsx') + +decarbonization_result = decarbonization_result.merge(WWTP_within[['FACILITY','CITY_x','PADD']], how='left', left_on=['facility','city'], right_on=['FACILITY','CITY_x']) + +decarbonization_result = decarbonization_result[decarbonization_result['USD_decarbonization'].notna()] + +decarbonization_result = decarbonization_result[decarbonization_result['USD_decarbonization'] <= 1000] + +PADD_1 = decarbonization_result[decarbonization_result['PADD'] == 1] +PADD_1.sort_values(by='USD_decarbonization', inplace=True) +PADD_1['cummulative_oil_BPD'] = PADD_1['oil_BPD'].cumsum() + +PADD_2 = decarbonization_result[decarbonization_result['PADD'] == 2] +PADD_2.sort_values(by='USD_decarbonization', inplace=True) +PADD_2['cummulative_oil_BPD'] = PADD_2['oil_BPD'].cumsum() + +PADD_3 = decarbonization_result[decarbonization_result['PADD'] == 3] +PADD_3.sort_values(by='USD_decarbonization', inplace=True) +PADD_3['cummulative_oil_BPD'] = PADD_3['oil_BPD'].cumsum() + +PADD_4 = decarbonization_result[decarbonization_result['PADD'] == 4] +PADD_4.sort_values(by='USD_decarbonization', inplace=True) +PADD_4['cummulative_oil_BPD'] = PADD_4['oil_BPD'].cumsum() + +PADD_5 = decarbonization_result[decarbonization_result['PADD'] == 5] +PADD_5.sort_values(by='USD_decarbonization', inplace=True) +PADD_5['cummulative_oil_BPD'] = PADD_5['oil_BPD'].cumsum() + +fig, ax_more = plt.subplots(figsize = (15, 10)) + +plt.rcParams['axes.linewidth'] = 2 +plt.rcParams['xtick.labelsize'] = 30 +plt.rcParams['ytick.labelsize'] = 30 +plt.xticks(fontname = 'Arial') +plt.yticks(fontname = 'Arial') + +ax_more.set_xlim([0, 5000]) +ax_more.set_ylim([-1500, 1000]) + +plt.axvspan(xmin=0, xmax=5000, ymin=0, ymax=0.6, facecolor=a, alpha=0.3) +plt.axvspan(xmin=0, xmax=5000, ymin=0, ymax=0.6, facecolor='none', linewidth=2, edgecolor='k') + +def plot_line(ax, data, color): + ax.plot((0, *data['cummulative_oil_BPD']), (0, *data['USD_decarbonization']), + color=color, marker='x', markersize=10, markeredgewidth=2, linewidth=2) + +plot_line(ax_more, PADD_1, b) +plot_line(ax_more, PADD_2, g) +plot_line(ax_more, PADD_3, r) +plot_line(ax_more, PADD_4, o) +plot_line(ax_more, PADD_5, y) + +ax_more.tick_params(direction='inout', length=15, width=2, bottom=True, top=False, left=True, right=False) + +ax_more_right = ax_more.twinx() +ax_more_right.set_ylim(ax_more.get_ylim()) +ax_more_right.tick_params(direction='in', length=7.5, width=2, bottom=False, top=True, left=False, right=True, labelcolor='none') + +ax_more_top = ax_more.twiny() +ax_more_top.set_xlim(ax_more.get_xlim()) +ax_more_top.tick_params(direction='in', length=7.5, width=2, bottom=False, top=True, left=False, right=True, labelcolor='none') + +ax_less = fig.add_axes([0.6, 0.2, 0.25, 0.3]) + +plt.rcParams['axes.linewidth'] = 2 +plt.rcParams['xtick.labelsize'] = 30 +plt.rcParams['ytick.labelsize'] = 30 +plt.xticks(fontname = 'Arial') +plt.yticks(fontname = 'Arial') + +ax_less.set_xlim([0, 1500]) +ax_less.set_ylim([-33500, -1500]) + +plt.axvspan(xmin=0, xmax=1500, facecolor=a, alpha=0.3) + +plot_line(ax_less, PADD_1, b) +plot_line(ax_less, PADD_2, g) +plot_line(ax_less, PADD_3, r) +plot_line(ax_less, PADD_4, o) +plot_line(ax_less, PADD_5, y) + +plt.xticks(np.arange(0, 2000, 500)) +plt.yticks(np.arange(-30000, 0, 5000)) + +ax_less.tick_params(direction='inout', length=15, width=2, bottom=True, top=False, left=True, right=False) + +ax_less_right = ax_less.twinx() +ax_less_right.set_ylim(ax_less.get_ylim()) +plt.yticks(np.arange(-30000, 0, 5000)) +ax_less_right.tick_params(direction='in', length=7.5, width=2, bottom=False, top=True, left=False, right=True, labelcolor='none') + +ax_less_top = ax_less.twiny() +ax_less_top.set_xlim(ax_less.get_xlim()) +plt.xticks(np.arange(0, 2000, 500)) +ax_less_top.tick_params(direction='in', length=7.5, width=2, bottom=False, top=True, left=False, right=True, labelcolor='none') + +#%% + +# national level analysis + +decarbonization_result = pd.read_excel(folder + 'results/decarbonization_results_10_7_2023/carbonization_summary.xlsx') + +WWTP_within = pd.read_excel(folder + 'HTL_geospatial_model_input_final.xlsx') + +decarbonization_result = decarbonization_result.merge(WWTP_within[['FACILITY','CITY_x','site_id', + 'AD_Mbpd','Vdist_Mbpd','CaDis_Mbpd', + 'HyCrk_Mbpd','VRedu_Mbpd','CaRef_Mbpd', + 'Isal_Mbpd','HDS_Mbpd','Cokin_Mbpd', + 'Asph_Mbpd','30_years_emission_ton_CO2', + 'sludge_management_kg_CO2_per_day_AD_included']], + how='left', left_on=['facility','city'], right_on=['FACILITY','CITY_x']) + +total_CO2_emission = decarbonization_result.sum(axis=0)['30_years_emission_ton_CO2'] + +total_sludge_CO2_emission = decarbonization_result.sum(axis=0)['sludge_management_kg_CO2_per_day_AD_included']*365*30/1000 + +oil = decarbonization_result[['site_id','AD_Mbpd','Vdist_Mbpd','CaDis_Mbpd','HyCrk_Mbpd','VRedu_Mbpd','CaRef_Mbpd','Isal_Mbpd','HDS_Mbpd','Cokin_Mbpd','Asph_Mbpd']] + +oil = oil.drop_duplicates(subset='site_id') + +oil = oil.drop(labels='site_id', axis=1) + +total_oil = oil.sum().sum() + +decarbonization_result = decarbonization_result[decarbonization_result['USD_decarbonization'].notna()] + +decarbonization_result = decarbonization_result[decarbonization_result['USD_decarbonization'] <= 500] + +reduced_CO2_emission = decarbonization_result.sum(axis=0)['CO2_reduction']/1000 + +added_oil = decarbonization_result.sum(axis=0)['oil_BPD']/1000000 + +national_CO2_reduction_ratio = reduced_CO2_emission/total_CO2_emission + +national_CO2_recuction_ratio_sludge_management = reduced_CO2_emission/total_sludge_CO2_emission + +national_oil_production_ratio = added_oil/total_oil + +print(f'National decarbonization ratio of wastewater treatment sector is {national_CO2_reduction_ratio*100:.2f}%') + +print(f'National decarbonization ratio of sludge management is {national_CO2_recuction_ratio_sludge_management*100:.2f}%') + +print(f'National increase ratio of crude oil production is {national_oil_production_ratio*100:.5f}%') \ No newline at end of file diff --git a/exposan/htl/analyses/high_IRR.py b/exposan/htl/analyses/high_IRR.py new file mode 100644 index 00000000..9abd46ad --- /dev/null +++ b/exposan/htl/analyses/high_IRR.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Fri Oct 27 08:45:48 2023 + +@author: jiananfeng +""" + +from exposan.htl import create_system, create_model, simulate_and_save + +for feedstock in ['sludge','food','fogs','green','manure']: + waste_cost_value=0 + waste_GWP_value=0 + print('\n\nfeedstock: ' + feedstock + '\ncost: ' + str(waste_cost_value) +\ + '\nGWP: ' + str(waste_GWP_value) + '\n\n') + sys = create_system(waste_cost=waste_cost_value, waste_GWP=waste_GWP_value) + model = create_model(sys, feedstock=feedstock) + + # if the independent variable is cost, uncomment the next three lines + # simulate_and_save(model, + # samples_kwargs={'N':1000, 'rule':'L', 'seed':3221}, + # notes=f'{feedstock}_high_IRR_cost_{waste_cost_value}') + + # if the independent variable is GWP, uncomment the next three lines + # simulate_and_save(model, + # samples_kwargs={'N':1000, 'rule':'L', 'seed':3221}, + # notes=f'{feedstock}_high_IRR_GWP_{waste_GWP_value}') \ No newline at end of file diff --git a/exposan/htl/analyses/system_PSA.py b/exposan/htl/analyses/system_PSA.py index 8d6aed84..6b384adb 100644 --- a/exposan/htl/analyses/system_PSA.py +++ b/exposan/htl/analyses/system_PSA.py @@ -35,7 +35,6 @@ df.to_csv(os.path.join(results_path, 'PSA.csv')) # simulate_and_save(model, samples_kwargs={'N':100}) - #%% # import qsdsan as qs # fig, ax = qs.stats.plot_uncertainties(model) diff --git a/exposan/htl/analyses/system_baseline.py b/exposan/htl/analyses/system_baseline.py index 0c1816e1..1176a963 100644 --- a/exposan/htl/analyses/system_baseline.py +++ b/exposan/htl/analyses/system_baseline.py @@ -35,7 +35,6 @@ df.to_csv(os.path.join(results_path, 'baseline.csv')) # simulate_and_save(model, samples_kwargs={'N':100}) - #%% # import qsdsan as qs # fig, ax = qs.stats.plot_uncertainties(model) diff --git a/exposan/htl/analyses/system_no_P.py b/exposan/htl/analyses/system_no_P.py index ae8a8564..9890f08a 100644 --- a/exposan/htl/analyses/system_no_P.py +++ b/exposan/htl/analyses/system_no_P.py @@ -35,7 +35,6 @@ df.to_csv(os.path.join(results_path, 'no_P.csv')) # simulate_and_save(model, samples_kwargs={'N':100}) - #%% # import qsdsan as qs # fig, ax = qs.stats.plot_uncertainties(model) diff --git a/exposan/htl/analyses/ternary.py b/exposan/htl/analyses/ternary.py index 9c9c112a..e499fb83 100644 --- a/exposan/htl/analyses/ternary.py +++ b/exposan/htl/analyses/ternary.py @@ -39,8 +39,8 @@ 'GWP_sludge_5th':[], 'GWP_sludge_50th':[], 'GWP_sludge_95th':[]} ternary_results = pd.DataFrame(ternary_results_dict) -lipids = (0,0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1) -proteins = (0,0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1) +lipids = (0, ) +proteins = (0, ) get_quantiles = lambda data, quantiles=(0.05, 0.5, 0.95): [data.quantile(q) for q in quantiles] @@ -53,19 +53,22 @@ WWTP.sludge_afdw_protein = protein sys.simulate() print('\n\n', f'Lipid: {WWTP.sludge_afdw_lipid}\n', f'Protein: {WWTP.sludge_afdw_protein}\n', f'Carbohydrate: {WWTP.sludge_afdw_carbo}', '\n\n') - model = create_model(sys, exclude_sludge_compositions=True, + model = create_model(sys, + plant_size=False, + ternary=True, + exclude_sludge_compositions=True, include_HTL_yield_as_metrics=False, include_other_metrics=False, include_other_CFs_as_metrics=False, include_check=False) - N = 200 + N = 1000 samples = model.sample(N=N, rule='L', seed=3221) model.load_samples(samples) model.evaluate() MDSP = model.table['TEA']['MDSP [$/gal diesel]'].dropna() - sludge_price = model.table['TEA']['sludge_management_price [$/ton dry sludge]'].dropna() - LCA_diesel = model.table['LCA']['GWP_diesel [kg CO2/MMBTU diesel]'].dropna() - LCA_sludge = model.table['LCA']['GWP_sludge [kg CO2/ton dry sludge]'].dropna() + sludge_price = model.table['TEA']['Sludge management price [$/ton dry sludge]'].dropna() + LCA_diesel = model.table['LCA']['GWP diesel [kg CO2/MMBTU diesel]'].dropna() + LCA_sludge = model.table['LCA']['GWP sludge [kg CO2/ton dry sludge]'].dropna() ternary_results.loc[len(ternary_results.index)] = ( [100*lipid, 100*protein, round(100-100*lipid-100*protein),] + diff --git a/exposan/htl/data/impact_items.xlsx b/exposan/htl/data/impact_items.xlsx index 1bbd2c4e..1b85fe32 100644 Binary files a/exposan/htl/data/impact_items.xlsx and b/exposan/htl/data/impact_items.xlsx differ diff --git a/exposan/htl/geospatial_HTL_systems.py b/exposan/htl/geospatial_HTL_systems.py new file mode 100644 index 00000000..89af4014 --- /dev/null +++ b/exposan/htl/geospatial_HTL_systems.py @@ -0,0 +1,430 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Mon Jun 5 08:46:28 2023 + +@author: jiananfeng +""" + +import os +import qsdsan as qs, biosteam as bst +import pandas as pd +from qsdsan import sanunits as qsu +from qsdsan.utils import clear_lca_registries +from exposan.htl import (_load_components, create_tea,) +from exposan.htl import _sanunits as su +from biosteam.units import IsenthalpicValve +from biosteam import settings + +__all__ = ('create_spatial_system',) + +density_biocrude = 980 # kg/m3 Snowden-Swan et al. 2022 SOT, PNNL + +def _load_process_settings(location='IL'): +# ============================================================================= +# add a heating agent +# ============================================================================= + # use DOWTHERM(TM) A Heat Transfer Fluid (HTF) as the heating agent + # DOWTHERM(TM) A HTF = 73.5% diphenyl oxide (DPO) + 26.5% Biphenyl (BIP) + # critical temperature for HTF: 497 C + # critical pressure for HTF: 313.4 kPa + # https://www.dow.com/en-us/pdp.dowtherm-a-heat-transfer-fluid.238000z.\ + # html#tech-content (accessed 11-16-2022) + + DPO_chem = qs.Chemical('DPO_chem', search_ID='101-84-8') + BIP_chem = qs.Chemical('BIP_chem', search_ID='92-52-4') + + DPO = qs.Component.from_chemical('DPO', chemical=DPO_chem, particle_size='Soluble', + degradability='Slowly', organic=True) + + BIP = qs.Component.from_chemical('BIP', chemical=BIP_chem, particle_size='Soluble', + degradability='Slowly', organic=True) + + HTF_thermo = bst.Thermo((DPO, BIP,)) + + HTF = bst.UtilityAgent('HTF', DPO=0.735, BIP=0.265, T=673.15, P=951477, phase='g', + # 400 C (673.15 K) and 138 psig (951477 pa) are max temp and pressure for HTF + thermo=HTF_thermo, + # T_limit = 495 F (530.372 K) is the highest temp that vapor can exist + regeneration_price=1) # Lang + # use default heat transfer efficiency (1) + # Temperature and pressure: https://www.dow.com/content/dam/dcc/documents/\ + # en-us/app-tech-guide/176/176-01334-01-dowtherm-heat-transfer-fluids-\ + # engineering-manual.pdf?iframe=true (accessed on 11-16-2022) + bst.HeatUtility.heating_agents.append(HTF) + + bst.CE = qs.CEPCI_by_year[2020] # use 2020$ to match up with latest PNNL report + +# ============================================================================= +# set utility prices +# ============================================================================= + + folder = '/Users/jiananfeng/Desktop/PhD_CEE/NSF_PFAS/HTL_geospatial/' + + elec_input = pd.read_excel(folder + 'HTL_geospatial_model_input.xlsx') + + bst.PowerUtility.price = elec_input[elec_input['state']==location]['price (10-year median)'].iloc[0]/100 + +# ============================================================================= +# create the HTL spatial system +# ============================================================================= + +def create_spatial_system(waste_cost=400, # assumed to be 400 for all WRRFs + size=100, # in MGD + distance=30, # in km, using Google Maps API + solid_fate=1, # from Seiple et al. 2020 + ww_2_dry_sludge_ratio=1, # use real solid amount/WRRFs influent + state='IL', + elec_GHG=0.365593393303875): + + flowsheet_ID = 'htl_geospatial' + + # Clear flowsheet and registry for reloading + if hasattr(qs.main_flowsheet.flowsheet, flowsheet_ID): + getattr(qs.main_flowsheet.flowsheet, flowsheet_ID).clear() + clear_lca_registries() + flowsheet = qs.Flowsheet(flowsheet_ID) + stream = flowsheet.stream + qs.main_flowsheet.set_flowsheet(flowsheet) + + _load_components() + _load_process_settings(location=state) + + # folder = os.path.dirname(__file__) + folder = '/Users/jiananfeng/Desktop/PhD_CEE/NSF_PFAS/HTL_geospatial/' + qs.ImpactIndicator.load_from_file(os.path.join(folder, 'data/impact_indicators.csv')) + qs.ImpactItem.load_from_file(os.path.join(folder, 'data/impact_items.xlsx')) + + raw_wastewater = qs.WasteStream('raw_wastewater', H2O=size, units='MGD', T=25+273.15) + # set H2O equal to the total raw wastewater into the WWTP + + # ============================================================================= + # pretreatment (Area 000) + # ============================================================================= + + if solid_fate == 8: # lagoon and constructed wetland + WWTP = su.WWTP('S000', ins=raw_wastewater, outs=('sludge','treated_water'), + ww_2_dry_sludge=ww_2_dry_sludge_ratio, + # how much metric ton/day sludge can be produced by 1 MGD of ww + sludge_moisture=0.99, # dewatering needed + sludge_dw_ash=0.257, # w/o digestion + sludge_afdw_lipid=0.204, # w/o digestion + sludge_afdw_protein=0.463, # w/o digestion + operation_hours=8760) + + SluC = qsu.SludgeCentrifuge('A000', ins=WWTP-0, + outs=('supernatant','compressed_sludge'), + init_with='Stream', + solids=('Sludge_lipid','Sludge_protein', + 'Sludge_carbo','Sludge_ash'), + sludge_moisture=0.8) + SluC.register_alias('SluC') + + P1 = qsu.SludgePump('A100', ins=SluC-1, outs='pressed_sludge', P=3049.7*6894.76, + init_with='Stream') + + elif solid_fate in (1, 2, 4): # anaerobic/aerobic digestion + WWTP = su.WWTP('S000', ins=raw_wastewater, outs=('sludge','treated_water'), + ww_2_dry_sludge=ww_2_dry_sludge_ratio, + # how much metric ton/day sludge can be produced by 1 MGD of ww + sludge_moisture=0.8, # dewatering not needed + sludge_dw_ash=0.454166666666667, # w/ digestion + sludge_afdw_lipid=0.1693, # w/ digestion + sludge_afdw_protein=0.5185, # w/ digestion + operation_hours=8760) + + P1 = qsu.SludgePump('A100', ins=WWTP-0, outs='pressed_sludge', P=3049.7*6894.76, + init_with='Stream') + + else: + WWTP = su.WWTP('S000', ins=raw_wastewater, outs=('sludge','treated_water'), + ww_2_dry_sludge=ww_2_dry_sludge_ratio, + # how much metric ton/day sludge can be produced by 1 MGD of ww + sludge_moisture=0.8, # dewatering not needed + sludge_dw_ash=0.257, # w/o digestion + sludge_afdw_lipid=0.204, # w/o digestion + sludge_afdw_protein=0.463, # w/o digestion + operation_hours=8760) + + P1 = qsu.SludgePump('A100', ins=WWTP-0, outs='pressed_sludge', P=3049.7*6894.76, + init_with='Stream') + + WWTP.register_alias('WWTP') + + P1.register_alias('P1') + # Jones 2014: 3049.7 psia + + raw_wastewater.price = -WWTP.ww_2_dry_sludge*waste_cost/3.79/(10**6) # 1 gal water = 3.79 kg water + + # ============================================================================= + # HTL (Area 100) + # ============================================================================= + + H1 = qsu.HXutility('A110', include_construction=True, + ins=P1-0, outs='heated_sludge', T=351+273.15, + U=0.0795, init_with='Stream', rigorous=True) + # feed T is low, thus high viscosity and low U (case B in Knorr 2013) + # U: 3, 14, 15 BTU/hr/ft2/F as minimum, baseline, and maximum + # U: 0.0170348, 0.0794957, 0.085174 kW/m2/K + # H1: SS PNNL 2020: 50 (17-76) Btu/hr/ft2/F ~ U = 0.284 (0.096-0.4313) kW/m2/K + # but not in other pumps (low viscosity, don't need U to enforce total heat transfer efficiency) + # unit conversion: https://www.unitsconverters.com/en/Btu(It)/Hmft2mdegf-To-W/M2mk/Utu-4404-4398 + H1.register_alias('H1') + + if solid_fate == 8: + HTL = qsu.HydrothermalLiquefaction('A120', ins=H1-0, + outs=('hydrochar','HTL_aqueous','biocrude','offgas_HTL'), + mositure_adjustment_exist_in_the_system=True) + else: + HTL = qsu.HydrothermalLiquefaction('A120', ins=H1-0, + outs=('hydrochar','HTL_aqueous','biocrude','offgas_HTL'), + mositure_adjustment_exist_in_the_system=False) + HTL.register_alias('HTL') + + # ============================================================================= + # CHG (Area 200) + # ============================================================================= + + H2SO4_Tank = qsu.StorageTank('T200', ins='H2SO4', outs=('H2SO4_out'), + init_with='WasteStream', tau=24, vessel_material='Stainless steel') + H2SO4_Tank.ins[0].price = 0.00658 # based on 93% H2SO4 and fresh water (dilute to 5%) price found in Davis 2020$/kg + H2SO4_Tank.register_alias('H2SO4_Tank') + + SP1 = qsu.ReversedSplitter('S200',ins=H2SO4_Tank-0, outs=('H2SO4_P','H2SO4_N'), + init_with='Stream') + SP1.register_alias('SP1') + + AcidEx = su.AcidExtraction('A200', ins=(HTL-0, SP1-0), + outs=('residual','extracted')) + AcidEx.register_alias('AcidEx') + # AcidEx.outs[0].price = -0.055 # SS 2021 SOT PNNL report page 24 Table 9 + # not include residual for TEA and LCA for now + + M1_outs1 = AcidEx.outs[1] + M1 = su.HTLmixer('A210', ins=(HTL-1, M1_outs1), outs=('mixture',)) + M1.register_alias('M1') + + StruPre = su.StruvitePrecipitation('A220', ins=(M1-0,'MgCl2','NH4Cl','MgO'), + outs=('struvite','CHG_feed')) + StruPre.ins[1].price = 0.5452 + StruPre.ins[2].price = 0.13 + StruPre.ins[3].price = 0.2 + StruPre.outs[0].price = 0.661 + StruPre.register_alias('StruPre') + + CHG = qsu.CatalyticHydrothermalGasification('A230', + ins=(StruPre-1, '7.8%_Ru/C'), + outs=('CHG_out', '7.8%_Ru/C_out')) + CHG.ins[1].price = 134.53 + CHG.register_alias('CHG') + + V1 = IsenthalpicValve('A240', ins=CHG-0, outs='depressed_cooled_CHG', P=50*6894.76, vle=True) + V1.register_alias('V1') + + F1 = qsu.Flash('A250', ins=V1-0, outs=('CHG_fuel_gas','N_riched_aqueous'), + T=60+273.15, P=50*6894.76, thermo=settings.thermo.ideal()) + F1.register_alias('F1') + + MemDis = qsu.MembraneDistillation('A260', ins=(F1-1, SP1-1, 'NaOH', 'Membrane_in'), + outs=('ammonium_sulfate','MemDis_ww', 'Membrane_out','solution'), + init_with='WasteStream') + MemDis.ins[2].price = 0.5256 + MemDis.outs[0].price = 0.3236 + MemDis.register_alias('MemDis') + + # ============================================================================= + # Storage, and disposal (Area 300) + # ============================================================================= + + CrudeOilTank = qsu.StorageTank('T300', ins=HTL-2, outs=('crude_oil'), + tau=3*24, init_with='WasteStream', vessel_material='Carbon steel') + # store for 3 days based on Jones 2014 + CrudeOilTank.register_alias('CrudeOilTank') + + CrudeOilTank.outs[0].price = -5.67/density_biocrude - 0.07/density_biocrude*distance + 0.3847 + # 5.67: fixed cost, 0.07: variable cost (Pootakham et al. Bio-oil transport by pipeline: A techno-economic assessment. Bioresource Technology, 2010) + + PC1 = qsu.PhaseChanger('S300', ins=CHG-1, outs='CHG_catalyst_out', phase='s') + PC1.register_alias('PC1') + + GasMixer = qsu.Mixer('S310', ins=(HTL-3, F1-0,), + outs=('fuel_gas'), init_with='Stream') + GasMixer.register_alias('GasMixer') + + # ============================================================================= + # facilities + # ============================================================================= + + qsu.HeatExchangerNetwork('HXN', T_min_app=86, force_ideal_thermo=True) + # 86 K: Jones et al. PNNL, 2014 + + CHP = qsu.CombinedHeatPower('CHP', include_construction=True, + ins=(GasMixer-0, 'natural_gas', 'air'), + outs=('emission','solid_ash'), init_with='WasteStream', + supplement_power_utility=False) + CHP.ins[1].price = 0.1685 + + sys = qs.System.from_units('sys_geospatial', + units=list(flowsheet.unit), + operating_hours=WWTP.operation_hours) + sys.register_alias('sys') + +# ============================================================================= +# add stream impact items +# ============================================================================= + + # transportation - crude oil + qs.StreamImpactItem(ID='transportation_item', + linked_stream=stream.crude_oil, + Acidification=89/1000/density_biocrude/(0.12917/1000)*0.12698/1000*distance-0.1617, + Ecotoxicity=89/1000/density_biocrude/(0.12917/1000)*0.25445/1000*distance-0.10666, + Eutrophication=89/1000/density_biocrude/(0.12917/1000)*0.00024901/1000*distance-0.00096886, + GlobalWarming=89/1000/density_biocrude*distance-0.22304, + OzoneDepletion=89/1000/density_biocrude/(0.12917/1000)*0.000000016986/1000*distance-0.00000060605, + PhotochemicalOxidation=89/1000/density_biocrude/(0.12917/1000)*0.001655/1000*distance-0.0013914, + Carcinogenics=89/1000/density_biocrude/(0.12917/1000)*0.00046431/1000*distance-0.00030447, + NonCarcinogenics=89/1000/density_biocrude/(0.12917/1000)*1.9859/1000*distance-1.0441, + RespiratoryEffects=89/1000/density_biocrude/(0.12917/1000)*0.00022076/1000*distance-0.00068606) + # 89 g CO2/m3/km: carbon intensity of truck transportation (Pootakham et al. A comparison of pipeline versus truck transport of bio-oil. Bioresource Technology, 2010) + # others are scaled based on transportation data from EcoInvent and GWP from the paper above + + # CHG catalyst + qs.StreamImpactItem(ID='CHG_catalyst_item', + linked_stream=stream.CHG_catalyst_out, + Acidification=991.6544196, + Ecotoxicity=15371.08292, + Eutrophication=0.45019348, + GlobalWarming=484.7862509, + OzoneDepletion=2.23437E-05, + PhotochemicalOxidation=6.735405072, + Carcinogenics=1.616793132, + NonCarcinogenics=27306.37232, + RespiratoryEffects=3.517184526) + + # Membrane distillation and acid extraction + qs.StreamImpactItem(ID='H2SO4_item', + linked_stream=stream.H2SO4, + Acidification=0.019678922, + Ecotoxicity=0.069909345, + Eutrophication=4.05E-06, + GlobalWarming=0.008205666, + OzoneDepletion=8.94E-10, + PhotochemicalOxidation=5.04E-05, + Carcinogenics=1.74E-03, + NonCarcinogenics=1.68237815, + RespiratoryEffects=9.41E-05) + + # Membrane distillation + qs.StreamImpactItem(ID='NaOH_item', + linked_stream=stream.NaOH, + Acidification=0.33656, + Ecotoxicity=0.77272, + Eutrophication=0.00032908, + GlobalWarming=1.2514, + OzoneDepletion=7.89E-07, + PhotochemicalOxidation=0.0033971, + Carcinogenics=0.0070044, + NonCarcinogenics=13.228, + RespiratoryEffects=0.0024543) + + qs.StreamImpactItem(ID='RO_item', + linked_stream=stream.Membrane_in, + Acidification=0.53533, + Ecotoxicity=0.90848, + Eutrophication=0.0028322, + GlobalWarming=2.2663, + OzoneDepletion=0.00000025541, + PhotochemicalOxidation=0.0089068, + Carcinogenics=0.034791, + NonCarcinogenics=31.8, + RespiratoryEffects=0.0028778) + + # Struvite precipitation + qs.StreamImpactItem(ID='MgCl2_item', + linked_stream=stream.MgCl2, + Acidification=0.77016, + Ecotoxicity=0.97878, + Eutrophication=0.00039767, + GlobalWarming=2.8779, + OzoneDepletion=4.94E-08, + PhotochemicalOxidation=0.0072306, + Carcinogenics=0.0050938, + NonCarcinogenics=8.6916, + RespiratoryEffects=0.004385) + + qs.StreamImpactItem(ID='NH4Cl_item', + linked_stream=stream.NH4Cl, + Acidification=0.34682, + Ecotoxicity=0.90305, + Eutrophication=0.0047381, + GlobalWarming=1.525, + OzoneDepletion=9.22E-08, + PhotochemicalOxidation=0.0030017, + Carcinogenics=0.010029, + NonCarcinogenics=14.85, + RespiratoryEffects=0.0018387) + + qs.StreamImpactItem(ID='MgO_item', + linked_stream=stream.MgO, + Acidification=0.12584, + Ecotoxicity=2.7949, + Eutrophication=0.00063607, + GlobalWarming=1.1606, + OzoneDepletion=1.54E-08, + PhotochemicalOxidation=0.0017137, + Carcinogenics=0.018607, + NonCarcinogenics=461.54, + RespiratoryEffects=0.0008755) + + # Heating and power utilities + qs.StreamImpactItem(ID='natural_gas_item', + linked_stream=stream.natural_gas, + Acidification=0.083822558, + Ecotoxicity=0.063446198, + Eutrophication=7.25E-05, + GlobalWarming=1.584234288, + OzoneDepletion=1.23383E-07, + PhotochemicalOxidation=0.000973731, + Carcinogenics=0.000666424, + NonCarcinogenics=3.63204, + RespiratoryEffects=0.000350917) + + # Struvite + qs.StreamImpactItem(ID='struvite_item', + linked_stream=stream.struvite, + Acidification=-0.122829597, + Ecotoxicity=-0.269606396, + Eutrophication=-0.000174952, + GlobalWarming=-0.420850152, + OzoneDepletion=-2.29549E-08, + PhotochemicalOxidation=-0.001044087, + Carcinogenics=-0.002983018, + NonCarcinogenics=-4.496533528, + RespiratoryEffects=-0.00061764) + + # Ammonium sulfate + qs.StreamImpactItem(ID='NH42SO4_item', + linked_stream=stream.ammonium_sulfate, + Acidification=-0.72917, + Ecotoxicity=-3.4746, + Eutrophication=-0.0024633, + GlobalWarming=-1.2499, + OzoneDepletion=-6.12E-08, + PhotochemicalOxidation=-0.0044519, + Carcinogenics=-0.036742, + NonCarcinogenics=-62.932, + RespiratoryEffects=-0.0031315) + + create_tea(sys, IRR_value=0.03, finance_interest_value=0.03) + + qs.LCA(system=sys, lifetime=30, lifetime_unit='yr', + Electricity=lambda:(sys.get_electricity_consumption()-sys.get_electricity_production())*30/0.67848*elec_GHG, + # 0.67848 is the GHG level with the Electricity item, + # we can adjust the electricity amount to reflect different GHG of electricity at different locations + Cooling=lambda:sys.get_cooling_duty()/1000*30) + + sys.simulate() + + biocrude_barrel = CrudeOilTank.outs[0].F_mass/0.98/3.78541/42*24 # in BPD (barrel per day) + + return sys, biocrude_barrel \ No newline at end of file diff --git a/exposan/htl/htl.yml b/exposan/htl/htl.yml new file mode 100644 index 00000000..6c56a4a2 --- /dev/null +++ b/exposan/htl/htl.yml @@ -0,0 +1,489 @@ +name: htl +channels: + - conda-forge + - defaults +dependencies: + - _anaconda_depends=2023.03=py39_0 + - _ipyw_jlab_nb_ext_conf=0.1.0=py39hecd8cb5_1 + - alabaster=0.7.12=pyhd3eb1b0_0 + - anaconda=custom=py39_1 + - anaconda-client=1.11.2=py39hecd8cb5_0 + - anyio=3.5.0=py39hecd8cb5_0 + - appdirs=1.4.4=pyhd3eb1b0_0 + - applaunchservices=0.3.0=py39hecd8cb5_0 + - appnope=0.1.2=py39hecd8cb5_1001 + - appscript=1.1.2=py39h9ed2024_0 + - argon2-cffi=21.3.0=pyhd3eb1b0_0 + - argon2-cffi-bindings=21.2.0=py39hca72f7f_0 + - arrow=1.2.3=py39hecd8cb5_1 + - astroid=2.14.2=py39hecd8cb5_0 + - astropy=5.1=py39h67323c0_0 + - asttokens=2.0.5=pyhd3eb1b0_0 + - atomicwrites=1.4.0=py_0 + - attrs=22.1.0=py39hecd8cb5_0 + - automat=20.2.0=py_0 + - autopep8=1.6.0=pyhd3eb1b0_1 + - babel=2.11.0=py39hecd8cb5_0 + - backcall=0.2.0=pyhd3eb1b0_0 + - backports=1.1=pyhd3eb1b0_0 + - backports.functools_lru_cache=1.6.4=pyhd3eb1b0_0 + - backports.tempfile=1.0=pyhd3eb1b0_1 + - backports.weakref=1.0.post1=py_1 + - bcrypt=3.2.0=py39hca72f7f_1 + - beautifulsoup4=4.12.2=py39hecd8cb5_0 + - binaryornot=0.4.4=pyhd3eb1b0_1 + - black=23.3.0=py39hecd8cb5_0 + - blas=1.0=openblas + - bleach=4.1.0=pyhd3eb1b0_0 + - blosc=1.21.3=hcec6c5f_0 + - bokeh=2.4.3=py39hecd8cb5_0 + - bottleneck=1.3.5=py39h67323c0_0 + - brotli=1.0.9=hca72f7f_7 + - brotli-bin=1.0.9=hca72f7f_7 + - brotlipy=0.7.0=py39h9ed2024_1003 + - brunsli=0.1=h23ab428_0 + - bzip2=1.0.8=h1de35cc_0 + - c-ares=1.19.0=h6c40b1e_0 + - ca-certificates=2022.12.7=h033912b_0 + - cctools=949.0.1=h9abeeb2_25 + - cctools_osx-64=949.0.1=hc7db93f_25 + - certifi=2022.12.7=pyhd8ed1ab_0 + - cffi=1.15.1=py39h6c40b1e_3 + - cfitsio=3.470=hbd21bf8_7 + - chardet=4.0.0=py39hecd8cb5_1003 + - charls=2.2.0=h23ab428_0 + - charset-normalizer=2.0.4=pyhd3eb1b0_0 + - click=8.0.4=py39hecd8cb5_0 + - cloudpickle=2.0.0=pyhd3eb1b0_0 + - clyent=1.2.2=py39hecd8cb5_1 + - colorama=0.4.6=py39hecd8cb5_0 + - colorcet=3.0.1=py39hecd8cb5_0 + - comm=0.1.2=py39hecd8cb5_0 + - conda-content-trust=0.1.3=py39hecd8cb5_0 + - conda-package-handling=2.0.2=py39hecd8cb5_0 + - conda-package-streaming=0.7.0=py39hecd8cb5_0 + - conda-repo-cli=1.0.41=py39hecd8cb5_0 + - conda-verify=3.4.2=py_1 + - constantly=15.1.0=pyh2b92418_0 + - contourpy=1.0.5=py39haf03e11_0 + - cookiecutter=1.7.3=pyhd3eb1b0_0 + - cryptography=39.0.1=py39hf6deb26_0 + - cssselect=1.1.0=pyhd3eb1b0_0 + - curl=7.88.1=h6c40b1e_0 + - cycler=0.11.0=pyhd3eb1b0_0 + - cytoolz=0.12.0=py39hca72f7f_0 + - dask=2023.3.2=py39hecd8cb5_0 + - dask-core=2023.3.2=py39hecd8cb5_0 + - datashader=0.14.4=py39hecd8cb5_0 + - datashape=0.5.4=py39hecd8cb5_1 + - debugpy=1.5.1=py39he9d5cce_0 + - decorator=5.1.1=pyhd3eb1b0_0 + - defusedxml=0.7.1=pyhd3eb1b0_0 + - diff-match-patch=20200713=pyhd3eb1b0_0 + - dill=0.3.6=py39hecd8cb5_0 + - distributed=2023.3.2=py39hecd8cb5_0 + - docstring-to-markdown=0.11=py39hecd8cb5_0 + - docutils=0.18.1=py39hecd8cb5_3 + - entrypoints=0.4=py39hecd8cb5_0 + - et_xmlfile=1.1.0=py39hecd8cb5_0 + - executing=0.8.3=pyhd3eb1b0_0 + - filelock=3.9.0=py39hecd8cb5_0 + - flake8=6.0.0=py39hecd8cb5_0 + - flask=2.2.2=py39hecd8cb5_0 + - flit-core=3.8.0=py39hecd8cb5_0 + - fonttools=4.25.0=pyhd3eb1b0_0 + - freetype=2.12.1=hd8bbffd_0 + - fsspec=2023.3.0=py39hecd8cb5_0 + - future=0.18.3=py39hecd8cb5_0 + - gensim=4.3.0=py39hcec6c5f_0 + - gettext=0.21.0=he85b6c0_1 + - giflib=5.2.1=h6c40b1e_3 + - glib=2.69.1=hfff2838_2 + - glob2=0.7=pyhd3eb1b0_0 + - gmp=6.2.1=he9d5cce_3 + - gmpy2=2.1.2=py39hd5de756_0 + - greenlet=2.0.1=py39hcec6c5f_0 + - gst-plugins-base=1.14.1=hcec6c5f_1 + - gstreamer=1.14.1=h6c40b1e_1 + - h5py=3.7.0=py39h4a1dd59_0 + - hdf5=1.10.6=h10fe05b_1 + - heapdict=1.0.1=pyhd3eb1b0_0 + - holoviews=1.15.4=py39hecd8cb5_0 + - huggingface_hub=0.10.1=py39hecd8cb5_0 + - hvplot=0.8.2=py39hecd8cb5_0 + - hyperlink=21.0.0=pyhd3eb1b0_0 + - icu=58.2=h0a44026_3 + - idna=3.4=py39hecd8cb5_0 + - imagecodecs=2021.8.26=py39h8a96914_2 + - imageio=2.26.0=py39hecd8cb5_0 + - imagesize=1.4.1=py39hecd8cb5_0 + - imbalanced-learn=0.10.1=py39hecd8cb5_0 + - importlib-metadata=6.0.0=py39hecd8cb5_0 + - importlib_metadata=6.0.0=hd3eb1b0_0 + - importlib_resources=5.2.0=pyhd3eb1b0_1 + - incremental=21.3.0=pyhd3eb1b0_0 + - inflection=0.5.1=py39hecd8cb5_0 + - iniconfig=1.1.1=pyhd3eb1b0_0 + - intake=0.6.8=py39hecd8cb5_0 + - intervaltree=3.1.0=pyhd3eb1b0_0 + - ipykernel=6.19.2=py39h01d92e1_0 + - ipython=8.12.0=py39hecd8cb5_0 + - ipython_genutils=0.2.0=pyhd3eb1b0_1 + - ipywidgets=8.0.4=py39hecd8cb5_0 + - isort=5.9.3=pyhd3eb1b0_0 + - itemadapter=0.3.0=pyhd3eb1b0_0 + - itemloaders=1.0.4=pyhd3eb1b0_1 + - itsdangerous=2.0.1=pyhd3eb1b0_0 + - jaraco.classes=3.2.1=pyhd3eb1b0_0 + - jedi=0.18.1=py39hecd8cb5_1 + - jellyfish=0.9.0=py39hca72f7f_0 + - jinja2=3.1.2=py39hecd8cb5_0 + - jinja2-time=0.2.0=pyhd3eb1b0_3 + - jmespath=0.10.0=pyhd3eb1b0_0 + - joblib=1.1.1=py39hecd8cb5_0 + - jpeg=9e=h6c40b1e_1 + - jq=1.6=h9ed2024_1000 + - json5=0.9.6=pyhd3eb1b0_0 + - jsonschema=4.17.3=py39hecd8cb5_0 + - jupyter=1.0.0=py39hecd8cb5_8 + - jupyter_client=8.1.0=py39hecd8cb5_0 + - jupyter_console=6.6.3=py39hecd8cb5_0 + - jupyter_core=5.3.0=py39hecd8cb5_0 + - jupyter_server=1.23.4=py39hecd8cb5_0 + - jupyterlab=3.5.3=py39hecd8cb5_0 + - jupyterlab_pygments=0.1.2=py_0 + - jupyterlab_server=2.22.0=py39hecd8cb5_0 + - jupyterlab_widgets=3.0.5=py39hecd8cb5_0 + - jxrlib=1.1=haf1e3a3_2 + - keyring=23.13.1=py39hecd8cb5_0 + - kiwisolver=1.4.4=py39hcec6c5f_0 + - krb5=1.19.4=hdba6334_0 + - lazy-object-proxy=1.6.0=py39h9ed2024_0 + - lcms2=2.12=hf1fd2bf_0 + - ld64=530=h20443b4_25 + - ld64_osx-64=530=h70f3046_25 + - ldid=2.1.2=h2d21305_2 + - lerc=3.0=he9d5cce_0 + - libaec=1.0.4=hb1e8313_1 + - libarchive=3.6.2=had00e1f_1 + - libbrotlicommon=1.0.9=hca72f7f_7 + - libbrotlidec=1.0.9=hca72f7f_7 + - libbrotlienc=1.0.9=hca72f7f_7 + - libclang13=14.0.6=default_habbcc1a_1 + - libcurl=7.88.1=ha585b31_0 + - libcxx=14.0.6=h9765a3e_0 + - libdeflate=1.17=hb664fd8_0 + - libedit=3.1.20221030=h6c40b1e_0 + - libev=4.33=h9ed2024_1 + - libffi=3.4.2=hecd8cb5_6 + - libgfortran=5.0.0=11_3_0_hecd8cb5_28 + - libgfortran5=11.3.0=h9dfd629_28 + - libiconv=1.16=hca72f7f_2 + - liblief=0.12.3=hcec6c5f_0 + - libllvm11=11.1.0=h46f1229_6 + - libllvm12=12.0.0=h9b2ccf5_3 + - libllvm14=14.0.6=h91fad77_2 + - libnghttp2=1.46.0=ha29bfda_0 + - libopenblas=0.3.21=h54e7dc3_0 + - libpng=1.6.39=h6c40b1e_0 + - libpq=12.9=h1c9f633_3 + - libprotobuf=3.20.3=hfff2838_0 + - libsodium=1.0.18=h1de35cc_0 + - libspatialindex=1.9.3=h23ab428_0 + - libssh2=1.10.0=h0a4fc7d_0 + - libtiff=4.5.0=hcec6c5f_2 + - libuv=1.44.2=h6c40b1e_0 + - libwebp=1.2.4=hf6ce154_1 + - libwebp-base=1.2.4=h6c40b1e_1 + - libxml2=2.10.3=h930c0e2_0 + - libxslt=1.1.37=h6d1eb0e_0 + - libzopfli=1.0.3=hb1e8313_0 + - llvm-openmp=14.0.6=h0dcd299_0 + - locket=1.0.0=py39hecd8cb5_0 + - lxml=4.9.2=py39h6c40b1e_0 + - lz4=3.1.3=py39h9ed2024_0 + - lz4-c=1.9.4=hcec6c5f_0 + - lzo=2.10=haf1e3a3_2 + - markdown=3.4.1=py39hecd8cb5_0 + - markupsafe=2.1.1=py39hca72f7f_0 + - matplotlib=3.7.1=py39hecd8cb5_1 + - matplotlib-base=3.7.1=py39hda11e5a_1 + - matplotlib-inline=0.1.6=py39hecd8cb5_0 + - mccabe=0.7.0=pyhd3eb1b0_0 + - mistune=0.8.4=py39h9ed2024_1000 + - mock=4.0.3=pyhd3eb1b0_0 + - more-itertools=8.12.0=pyhd3eb1b0_0 + - mpc=1.1.0=h6ef4df4_1 + - mpfr=4.0.2=h9066e36_1 + - mpmath=1.2.1=py39hecd8cb5_0 + - msgpack-python=1.0.3=py39haf03e11_0 + - multipledispatch=0.6.0=py39hecd8cb5_0 + - munkres=1.1.4=py_0 + - mypy_extensions=0.4.3=py39hecd8cb5_1 + - nbclassic=0.5.5=py39hecd8cb5_0 + - nbclient=0.5.13=py39hecd8cb5_0 + - nbconvert=6.5.4=py39hecd8cb5_0 + - nbformat=5.7.0=py39hecd8cb5_0 + - ncurses=6.4=hcec6c5f_0 + - nest-asyncio=1.5.6=py39hecd8cb5_0 + - networkx=2.8.4=py39hecd8cb5_1 + - ninja=1.10.2=hecd8cb5_5 + - ninja-base=1.10.2=haf03e11_5 + - nltk=3.7=pyhd3eb1b0_0 + - notebook=6.5.4=py39hecd8cb5_0 + - notebook-shim=0.2.2=py39hecd8cb5_0 + - nspr=4.33=he9d5cce_0 + - nss=3.74=h47edf6a_0 + - numexpr=2.8.4=py39h57a7bef_0 + - numpydoc=1.5.0=py39hecd8cb5_0 + - oniguruma=6.9.7.1=h9ed2024_0 + - openjpeg=2.4.0=h66ea3da_0 + - openpyxl=3.0.10=py39hca72f7f_0 + - openssl=1.1.1t=hfd90126_0 + - packaging=23.0=py39hecd8cb5_0 + - pandas=1.5.3=py39h07fba90_0 + - pandocfilters=1.5.0=pyhd3eb1b0_0 + - panel=0.14.3=py39hecd8cb5_0 + - param=1.12.3=py39hecd8cb5_0 + - parsel=1.6.0=py39hecd8cb5_0 + - parso=0.8.3=pyhd3eb1b0_0 + - partd=1.2.0=pyhd3eb1b0_1 + - patch=2.7.6=h1de35cc_1001 + - pathlib=1.0.1=pyhd3eb1b0_1 + - pathspec=0.10.3=py39hecd8cb5_0 + - patsy=0.5.3=py39hecd8cb5_0 + - pcre=8.45=h23ab428_0 + - pep8=1.7.1=py39hecd8cb5_1 + - pexpect=4.8.0=pyhd3eb1b0_3 + - pickleshare=0.7.5=pyhd3eb1b0_1003 + - pillow=9.4.0=py39hcec6c5f_0 + - pip=23.0.1=py39hecd8cb5_0 + - pkginfo=1.9.6=py39hecd8cb5_0 + - platformdirs=2.5.2=py39hecd8cb5_0 + - plotly=5.9.0=py39hecd8cb5_0 + - pluggy=1.0.0=py39hecd8cb5_1 + - ply=3.11=py39hecd8cb5_0 + - pooch=1.4.0=pyhd3eb1b0_0 + - poyo=0.5.0=pyhd3eb1b0_0 + - prometheus_client=0.14.1=py39hecd8cb5_0 + - prompt-toolkit=3.0.36=py39hecd8cb5_0 + - prompt_toolkit=3.0.36=hd3eb1b0_0 + - protego=0.1.16=py_0 + - psutil=5.9.0=py39hca72f7f_0 + - ptyprocess=0.7.0=pyhd3eb1b0_2 + - pure_eval=0.2.2=pyhd3eb1b0_0 + - py=1.11.0=pyhd3eb1b0_0 + - py-lief=0.12.3=py39hcec6c5f_0 + - pyasn1=0.4.8=pyhd3eb1b0_0 + - pyasn1-modules=0.2.8=py_0 + - pycodestyle=2.10.0=py39hecd8cb5_0 + - pycosat=0.6.4=py39hca72f7f_0 + - pycparser=2.21=pyhd3eb1b0_0 + - pyct=0.5.0=py39hecd8cb5_0 + - pycurl=7.45.2=py39hdb2fb19_0 + - pydispatcher=2.0.5=py39hecd8cb5_2 + - pydocstyle=6.3.0=py39hecd8cb5_0 + - pyerfa=2.0.0=py39h9ed2024_0 + - pyflakes=3.0.1=py39hecd8cb5_0 + - pygments=2.11.2=pyhd3eb1b0_0 + - pyhamcrest=2.0.2=pyhd3eb1b0_2 + - pyjwt=2.4.0=py39hecd8cb5_0 + - pylint=2.16.2=py39hecd8cb5_0 + - pylint-venv=2.3.0=py39hecd8cb5_0 + - pyls-spyder=0.4.0=pyhd3eb1b0_0 + - pyobjc-core=9.0=py39h9205ec4_1 + - pyobjc-framework-cocoa=9.0=py39h9205ec4_0 + - pyobjc-framework-coreservices=9.0=py39h46256e1_0 + - pyobjc-framework-fsevents=9.0=py39hecd8cb5_0 + - pyodbc=4.0.34=py39he9d5cce_0 + - pyopenssl=23.0.0=py39hecd8cb5_0 + - pyparsing=3.0.9=py39hecd8cb5_0 + - pyqt=5.15.7=py39he9d5cce_0 + - pyqt5-sip=12.11.0=py39he9d5cce_0 + - pyqtwebengine=5.15.7=py39he9d5cce_0 + - pyrsistent=0.18.0=py39hca72f7f_0 + - pysocks=1.7.1=py39hecd8cb5_0 + - pytables=3.7.0=py39h59775c6_1 + - pytest=7.1.2=py39hecd8cb5_0 + - python=3.9.16=h218abb5_2 + - python-dateutil=2.8.2=pyhd3eb1b0_0 + - python-fastjsonschema=2.16.2=py39hecd8cb5_0 + - python-libarchive-c=2.9=pyhd3eb1b0_1 + - python-lsp-black=1.2.1=py39hecd8cb5_0 + - python-lsp-jsonrpc=1.0.0=pyhd3eb1b0_0 + - python-lsp-server=1.7.2=pyhd8ed1ab_0 + - python-lsp-server-base=1.7.2=pyhd8ed1ab_0 + - python-slugify=5.0.2=pyhd3eb1b0_0 + - python-snappy=0.6.1=py39hcec6c5f_0 + - python.app=3=py39hca72f7f_0 + - pytoolconfig=1.2.5=py39hecd8cb5_1 + - pytorch=1.12.1=cpu_py39h64f2f56_1 + - pytz=2022.7=py39hecd8cb5_0 + - pyviz_comms=2.0.2=pyhd3eb1b0_0 + - pywavelets=1.4.1=py39h6c40b1e_0 + - pyyaml=6.0=py39h6c40b1e_1 + - pyzmq=23.2.0=py39he9d5cce_0 + - qdarkstyle=3.0.2=pyhd3eb1b0_0 + - qstylizer=0.2.2=py39hecd8cb5_0 + - qt-main=5.15.2=h51e0635_8 + - qt-webengine=5.15.9=h90a370e_4 + - qtawesome=1.2.2=py39hecd8cb5_0 + - qtconsole=5.4.2=py39hecd8cb5_0 + - qtpy=2.2.0=py39hecd8cb5_0 + - qtwebkit=5.212=hbfab81c_5 + - queuelib=1.5.0=py39hecd8cb5_0 + - readline=8.2=hca72f7f_0 + - regex=2022.7.9=py39hca72f7f_0 + - requests=2.28.1=py39hecd8cb5_1 + - requests-file=1.5.1=pyhd3eb1b0_0 + - requests-toolbelt=0.9.1=pyhd3eb1b0_0 + - ripgrep=13.0.0=hc2228c6_0 + - rope=1.7.0=py39hecd8cb5_0 + - rtree=1.0.1=py39hecd8cb5_0 + - ruamel.yaml=0.17.21=py39hca72f7f_0 + - ruamel.yaml.clib=0.2.6=py39hca72f7f_1 + - scikit-image=0.19.3=py39hcec6c5f_1 + - scikit-learn=1.2.2=py39hcec6c5f_0 + - scipy=1.10.1=py39h9034365_0 + - scrapy=2.8.0=py39hecd8cb5_0 + - seaborn=0.12.2=py39hecd8cb5_0 + - send2trash=1.8.0=pyhd3eb1b0_1 + - service_identity=18.1.0=pyhd3eb1b0_1 + - setuptools=66.0.0=py39hecd8cb5_0 + - sip=6.6.2=py39he9d5cce_0 + - six=1.16.0=pyhd3eb1b0_1 + - smart_open=5.2.1=py39hecd8cb5_0 + - snappy=1.1.9=he9d5cce_0 + - sniffio=1.2.0=py39hecd8cb5_1 + - snowballstemmer=2.2.0=pyhd3eb1b0_0 + - sortedcontainers=2.4.0=pyhd3eb1b0_0 + - soupsieve=2.4=py39hecd8cb5_0 + - sphinx=5.0.2=py39hecd8cb5_0 + - sphinxcontrib-applehelp=1.0.2=pyhd3eb1b0_0 + - sphinxcontrib-devhelp=1.0.2=pyhd3eb1b0_0 + - sphinxcontrib-htmlhelp=2.0.0=pyhd3eb1b0_0 + - sphinxcontrib-jsmath=1.0.1=pyhd3eb1b0_0 + - sphinxcontrib-qthelp=1.0.3=pyhd3eb1b0_0 + - sphinxcontrib-serializinghtml=1.1.5=pyhd3eb1b0_0 + - spyder=5.4.3=py39hecd8cb5_0 + - spyder-kernels=2.4.3=py39hecd8cb5_0 + - sqlalchemy=1.4.39=py39hca72f7f_0 + - sqlite=3.41.2=h6c40b1e_0 + - stack_data=0.2.0=pyhd3eb1b0_0 + - statsmodels=0.13.5=py39hacda100_1 + - sympy=1.11.1=py39hecd8cb5_0 + - tabulate=0.8.10=py39hecd8cb5_0 + - tapi=1000.10.8=ha1b3eb9_0 + - tbb=2021.8.0=ha357a0b_0 + - tbb4py=2021.8.0=py39ha357a0b_0 + - tblib=1.7.0=pyhd3eb1b0_0 + - tenacity=8.0.1=py39hecd8cb5_1 + - terminado=0.17.1=py39hecd8cb5_0 + - text-unidecode=1.3=pyhd3eb1b0_0 + - textdistance=4.2.1=pyhd3eb1b0_0 + - threadpoolctl=2.2.0=pyh0d69192_0 + - three-merge=0.1.1=pyhd3eb1b0_0 + - tifffile=2021.7.2=pyhd3eb1b0_2 + - tinycss2=1.2.1=py39hecd8cb5_0 + - tk=8.6.12=h5d9f67b_0 + - tldextract=3.2.0=pyhd3eb1b0_0 + - tokenizers=0.11.4=py39h8776b5c_1 + - toml=0.10.2=pyhd3eb1b0_0 + - tomli=2.0.1=py39hecd8cb5_0 + - tomlkit=0.11.1=py39hecd8cb5_0 + - toolz=0.12.0=py39hecd8cb5_0 + - tornado=6.2=py39hca72f7f_0 + - tqdm=4.65.0=py39h01d92e1_0 + - traitlets=5.7.1=py39hecd8cb5_0 + - transformers=4.24.0=py39hecd8cb5_0 + - twisted=22.2.0=py39hca72f7f_1 + - typing-extensions=4.4.0=py39hecd8cb5_0 + - typing_extensions=4.4.0=py39hecd8cb5_0 + - tzdata=2023c=h04d1e81_0 + - ujson=5.4.0=py39he9d5cce_0 + - unidecode=1.2.0=pyhd3eb1b0_0 + - unixodbc=2.3.11=hb456775_0 + - urllib3=1.26.15=py39hecd8cb5_0 + - w3lib=1.21.0=pyhd3eb1b0_0 + - watchdog=2.1.6=py39h999c104_0 + - wcwidth=0.2.5=pyhd3eb1b0_0 + - webencodings=0.5.1=py39hecd8cb5_1 + - websocket-client=0.58.0=py39hecd8cb5_4 + - werkzeug=2.2.3=py39hecd8cb5_0 + - whatthepatch=1.0.2=py39hecd8cb5_0 + - wheel=0.38.4=py39hecd8cb5_0 + - widgetsnbextension=4.0.5=py39hecd8cb5_0 + - wrapt=1.14.1=py39hca72f7f_0 + - wurlitzer=3.0.2=py39hecd8cb5_0 + - xarray=2022.11.0=py39hecd8cb5_0 + - xlwings=0.29.1=py39hecd8cb5_0 + - xz=5.2.10=h6c40b1e_1 + - yaml=0.2.5=haf1e3a3_0 + - yapf=0.31.0=pyhd3eb1b0_0 + - zeromq=4.3.4=h23ab428_0 + - zfp=0.5.5=he9d5cce_6 + - zict=2.1.0=py39hecd8cb5_0 + - zipp=3.11.0=py39hecd8cb5_0 + - zlib=1.2.13=h4dc903c_0 + - zope=1.0=py39hecd8cb5_1 + - zope.interface=5.4.0=py39h9ed2024_0 + - zstandard=0.19.0=py39h6c40b1e_0 + - zstd=1.5.5=hc035e20_0 + - pip: + - absl-py==1.4.0 + - ansicolors==1.1.8 + - array-record==0.4.0 + - astunparse==1.6.3 + - cachetools==5.3.1 + - chaospy==4.3.10 + - chemicals==1.1.5 + - click-plugins==1.1.1 + - cligj==0.7.2 + - colorpalette==0.3.3 + - dm-tree==0.1.8 + - etils==1.3.0 + - fiona==1.9.4.post1 + - flatbuffers==23.5.26 + - flexsolve==0.5.4 + - fluids==1.0.25 + - free-properties==0.3.6 + - gast==0.4.0 + - geographiclib==2.0 + - geopandas==0.13.0 + - geopy==2.3.0 + - git-filter-repo==2.38.0 + - google-auth==2.22.0 + - google-auth-oauthlib==1.0.0 + - google-pasta==0.2.0 + - googleapis-common-protos==1.59.1 + - googlemaps==4.10.0 + - grpcio==1.56.0 + - keras==2.13.1 + - libclang==16.0.0 + - llvmlite==0.41.0 + - multiprocess==0.70.14 + - numba==0.58.0 + - numpoly==1.2.5 + - numpy==1.25.2 + - oauthlib==3.2.2 + - opt-einsum==3.3.0 + - pint==0.22 + - promise==2.3 + - protobuf==4.23.4 + - pyglet==2.0.9 + - pyproj==3.5.0 + - python-graphviz==0.20.1 + - requests-oauthlib==1.3.1 + - rsa==4.9 + - salib==1.4.7 + - shapely==2.0.1 + - tensorboard==2.13.0 + - tensorboard-data-server==0.7.1 + - tensorflow==2.13.0 + - tensorflow-estimator==2.13.0 + - tensorflow-io-gcs-filesystem==0.32.0 + - tensorflow-metadata==1.13.1 + - termcolor==2.3.0 + - thermo==0.2.27 + - xlrd==2.0.1 +prefix: /Users/jiananfeng/opt/anaconda3/envs/htl diff --git a/exposan/htl/models.py b/exposan/htl/models.py index 3d68a14c..debb6187 100644 --- a/exposan/htl/models.py +++ b/exposan/htl/models.py @@ -27,9 +27,16 @@ __all__ = ('create_model',) -def create_model(system=None, exclude_sludge_compositions=False, - include_HTL_yield_as_metrics=True, include_other_metrics=True, - include_other_CFs_as_metrics=True, include_check=True): +def create_model(system=None, + feedstock='sludge', + plant_size=False, + ternary=False, + high_IRR=False, + exclude_sludge_compositions=False, + include_HTL_yield_as_metrics=True, + include_other_metrics=True, + include_other_CFs_as_metrics=True, + include_check=True): ''' Create a model based on the given system (or create the system based on the given configuration). @@ -46,62 +53,320 @@ def create_model(system=None, exclude_sludge_compositions=False, stream = flowsheet.stream model = qs.Model(sys) param = model.parameter + if feedstock not in ['sludge','food','fogs','green','manure']: + raise ValueError("invalid feedstock, select from 'sludge', 'food', 'fogs', 'green', and 'manure'") # ========================================================================= # WWTP # ========================================================================= - WWTP = unit.WWTP - dist = shape.Uniform(0.846,1.034) - @param(name='ww_2_dry_sludge', - element=WWTP, - kind='coupled', - units='ton/d/MGD', - baseline=0.94, - distribution=dist) - def set_ww_2_dry_sludge(i): - WWTP.ww_2_dry_sludge=i - dist = shape.Uniform(0.97,0.995) - @param(name='sludge_moisture', - element=WWTP, - kind='coupled', - units='-', - baseline=0.99, - distribution=dist) - def set_WWTP_sludge_moisture(i): - WWTP.sludge_moisture=i + # add plant size for Spearman's - dist = shape.Triangle(0.174,0.257,0.414) - @param(name='sludge_dw_ash', - element=WWTP, - kind='coupled', - units='-', - baseline=0.257, - distribution=dist) - def set_sludge_dw_ash(i): - WWTP.sludge_dw_ash=i + # raw_wastewater = stream.raw_wastewater + # dist = shape.Uniform(12618039,18927059) + # @param(name='plant_size', + # element=raw_wastewater, + # kind='coupled', + # units='MGD', + # baseline=15772549, + # distribution=dist) + # def set_plant_size(i): + # raw_wastewater.F_mass=i - if not exclude_sludge_compositions: - dist = shape.Triangle(0.08,0.204,0.308) - @param(name='sludge_afdw_lipid', + WWTP = unit.WWTP + if plant_size and feedstock == 'sludge': + dist = shape.Uniform(0.846,1.034) # only needed for sludge and when the independent variable is plant-size + @param(name='ww_2_dry_sludge', + element=WWTP, + kind='coupled', + units='ton/d/MGD', + baseline=0.94, + distribution=dist) + def set_ww_2_dry_sludge(i): + WWTP.ww_2_dry_sludge=i + + if ternary: + dist = shape.Triangle(0.052,0.5,0.8) # only needed for ternary + @param(name='sludge_moisture', element=WWTP, kind='coupled', units='-', - baseline=0.204, + baseline=0.5, distribution=dist) - def set_sludge_afdw_lipid(i): - WWTP.sludge_afdw_lipid=i + def set_WWTP_sludge_moisture(i): + WWTP.sludge_moisture=i - dist = shape.Triangle(0.38,0.463,0.51) - @param(name='sludge_afdw_protein', + dist = shape.Triangle(0.012,0.15,0.483) # only needed for ternary + @param(name='sludge_dw_ash', element=WWTP, kind='coupled', units='-', - baseline=0.463, + baseline=0.15, distribution=dist) - def set_sludge_afdw_protein(i): - WWTP.sludge_afdw_protein=i + def set_sludge_dw_ash(i): + WWTP.sludge_dw_ash=i + if not exclude_sludge_compositions: + if feedstock == 'sludge': + + dist = shape.Uniform(0.6,0.8) + @param(name='sludge_moisture', + element=WWTP, + kind='coupled', + units='-', + baseline=0.7, + distribution=dist) + def set_WWTP_sludge_moisture(i): + WWTP.sludge_moisture=i + + dist = shape.Triangle(0.174,0.257,0.414) + @param(name='sludge_dw_ash', + element=WWTP, + kind='coupled', + units='-', + baseline=0.257, + distribution=dist) + def set_sludge_dw_ash(i): + WWTP.sludge_dw_ash=i + + dist = shape.Triangle(0.08,0.204,0.308) + @param(name='sludge_afdw_lipid', + element=WWTP, + kind='coupled', + units='-', + baseline=0.204, + distribution=dist) + def set_sludge_afdw_lipid(i): + WWTP.sludge_afdw_lipid=i + + dist = shape.Triangle(0.38,0.463,0.51) + @param(name='sludge_afdw_protein', + element=WWTP, + kind='coupled', + units='-', + baseline=0.463, + distribution=dist) + def set_sludge_afdw_protein(i): + WWTP.sludge_afdw_protein=i + + dist = shape.Triangle(0.1944,0.3927,0.5556) + @param(name='N_2_P', + element=WWTP, + kind='coupled', + units='-', + baseline=0.3927, + distribution=dist) + def set_N_2_P(i): + WWTP.N_2_P=i + + if feedstock == 'food': + + dist = shape.Uniform(0.68,0.8) + @param(name='sludge_moisture', + element=WWTP, + kind='coupled', + units='-', + baseline=0.74, + distribution=dist) + def set_WWTP_sludge_moisture(i): + WWTP.sludge_moisture=i + + dist = shape.Triangle(0.025,0.0679,0.126) + @param(name='sludge_dw_ash', + element=WWTP, + kind='coupled', + units='-', + baseline=0.0679, + distribution=dist) + def set_sludge_dw_ash(i): + WWTP.sludge_dw_ash=i + + dist = shape.Uniform(0.13,0.30) + @param(name='sludge_afdw_lipid', # I will keep using 'sludge' in the name + element=WWTP, + kind='coupled', + units='-', + baseline=0.22, + distribution=dist) + def set_sludge_afdw_lipid(i): + WWTP.sludge_afdw_lipid=i + + dist = shape.Uniform(0.15,0.25) + @param(name='sludge_afdw_protein', + element=WWTP, + kind='coupled', + units='-', + baseline=0.2, + distribution=dist) + def set_sludge_afdw_protein(i): + WWTP.sludge_afdw_protein=i + + dist = shape.Triangle(0.1146,0.1502,0.2857) + @param(name='N_2_P', + element=WWTP, + kind='coupled', + units='-', + baseline=0.1502, + distribution=dist) + def set_N_2_P(i): + WWTP.N_2_P=i + + if feedstock == 'fogs': + + dist = shape.Uniform(0.1,0.6) + @param(name='sludge_moisture', + element=WWTP, + kind='coupled', + units='-', + baseline=0.35, + distribution=dist) + def set_WWTP_sludge_moisture(i): + WWTP.sludge_moisture=i + + dist = shape.Uniform(0.01492,0.02238) + @param(name='sludge_dw_ash', + element=WWTP, + kind='coupled', + units='-', + baseline=0.01865, + distribution=dist) + def set_sludge_dw_ash(i): + WWTP.sludge_dw_ash=i + + dist = shape.Triangle(0.912,0.987,1) + @param(name='sludge_afdw_lipid', # I will keep using 'sludge' in the name + element=WWTP, + kind='coupled', + units='-', + baseline=0.987, + distribution=dist) + def set_sludge_afdw_lipid(i): + WWTP.sludge_afdw_lipid=i + + dist = shape.Triangle(0,0.002,0.0123) + @param(name='sludge_afdw_protein', + element=WWTP, + kind='coupled', + units='-', + baseline=0.002, + distribution=dist) + def set_sludge_afdw_protein(i): + WWTP.sludge_afdw_protein=i + + dist = shape.Triangle(0,0.01055,0.03472) + @param(name='N_2_P', + element=WWTP, + kind='coupled', + units='-', + baseline=0.01055, + distribution=dist) + def set_N_2_P(i): + WWTP.N_2_P=i + + if feedstock == 'green': + + dist = shape.Triangle(0.052,0.342,0.69) + @param(name='sludge_moisture', + element=WWTP, + kind='coupled', + units='-', + baseline=0.342, + distribution=dist) + def set_WWTP_sludge_moisture(i): + WWTP.sludge_moisture=i + + dist = shape.Triangle(0.012,0.134,0.483) + @param(name='sludge_dw_ash', + element=WWTP, + kind='coupled', + units='-', + baseline=0.134, + distribution=dist) + def set_sludge_dw_ash(i): + WWTP.sludge_dw_ash=i + + dist = shape.Uniform(0.0104,0.0259) # two points found in literature, but consider this as a range, since one point is for leaves and one is for branches + @param(name='sludge_afdw_lipid', # I will keep using 'sludge' in the name + element=WWTP, + kind='coupled', + units='-', + baseline=0.018, + distribution=dist) + def set_sludge_afdw_lipid(i): + WWTP.sludge_afdw_lipid=i + + dist = shape.Uniform(0.016,0.082) + @param(name='sludge_afdw_protein', + element=WWTP, + kind='coupled', + units='-', + baseline=0.049, + distribution=dist) + def set_sludge_afdw_protein(i): + WWTP.sludge_afdw_protein=i + + dist = shape.Uniform(0.1871,0.2287) + @param(name='N_2_P', + element=WWTP, + kind='coupled', + units='-', + baseline=0.2079, + distribution=dist) + def set_N_2_P(i): + WWTP.N_2_P=i + + if feedstock == 'manure': + + dist = shape.Triangle(0.1735,0.6634,0.7975) + @param(name='sludge_moisture', + element=WWTP, + kind='coupled', + units='-', + baseline=0.6634, + distribution=dist) + def set_WWTP_sludge_moisture(i): + WWTP.sludge_moisture=i + + dist = shape.Triangle(0.138,0.3056,0.4295) + @param(name='sludge_dw_ash', + element=WWTP, + kind='coupled', + units='-', + baseline=0.3056, + distribution=dist) + def set_sludge_dw_ash(i): + WWTP.sludge_dw_ash=i + + dist = shape.Triangle(0.0377,0.092325,0.247) + @param(name='sludge_afdw_lipid', # I will keep using 'sludge' in the name + element=WWTP, + kind='coupled', + units='-', + baseline=0.092325, + distribution=dist) + def set_sludge_afdw_lipid(i): + WWTP.sludge_afdw_lipid=i + + dist = shape.Triangle(0.143,0.216375,0.264) + @param(name='sludge_afdw_protein', + element=WWTP, + kind='coupled', + units='-', + baseline=0.216375, + distribution=dist) + def set_sludge_afdw_protein(i): + WWTP.sludge_afdw_protein=i + + dist = shape.Uniform(0.2534,0.3801) + @param(name='N_2_P', + element=WWTP, + kind='coupled', + units='-', + baseline=0.3167, + distribution=dist) + def set_N_2_P(i): + WWTP.N_2_P=i + dist = shape.Uniform(0.675,0.825) @param(name='lipid_2_C', element=WWTP, @@ -132,36 +397,46 @@ def set_protein_2_C(i): def set_carbo_2_C(i): WWTP.carbo_2_C=i - dist = shape.Triangle(0.1348,0.1427,0.1647) - @param(name='C_2_H', + dist = shape.Uniform(0.1125,0.1375) + @param(name='lipid_2_H', element=WWTP, kind='coupled', units='-', - baseline=0.1427, + baseline=0.125, distribution=dist) - def set_C_2_H(i): - WWTP.C_2_H=i + def set_lipid_2_H(i): + WWTP.lipid_2_H=i - dist = shape.Uniform(0.1431,0.1749) - @param(name='protein_2_N', + dist = shape.Uniform(0.0614,0.075) + @param(name='protein_2_H', element=WWTP, kind='coupled', units='-', - baseline=0.159, + baseline=0.0682, distribution=dist) - def set_protein_2_N(i): - WWTP.protein_2_N=i - - dist = shape.Triangle(0.1944,0.3927,0.5556) - @param(name='N_2_P', + def set_protein_2_H(i): + WWTP.protein_2_H=i + + dist = shape.Uniform(0.06,0.0733) + @param(name='carbo_2_H', element=WWTP, kind='coupled', units='-', - baseline=0.3927, + baseline=0.0667, distribution=dist) - def set_N_2_P(i): - WWTP.N_2_P=i - + def set_carbo_2_H(i): + WWTP.carbo_2_H=i + + dist = shape.Uniform(0.1432,0.1750) + @param(name='protein_2_N', + element=WWTP, + kind='coupled', + units='-', + baseline=0.159, + distribution=dist) + def set_protein_2_N(i): + WWTP.protein_2_N=i + dist = shape.Triangle(7392,7920,8448) @param(name='operation_hour', element=WWTP, @@ -176,12 +451,12 @@ def set_operation_hour(i): # HTL # ========================================================================= H1 = unit.H1 - dist = shape.Triangle(0.017035,0.0795,0.085174) + dist = shape.Uniform(0.0170348,0.0227131) @param(name='enforced heating transfer coefficient', element=H1, kind='coupled', units='kW/m2/K', - baseline=0.0795, + baseline=0.0198739, distribution=dist) def set_U(i): H1.U=i @@ -308,14 +583,14 @@ def set_TOC_TC(i): HTL.TOC_TC=i dist = shape.Normal(1.750,0.122) - @param(name='biochar_C_slope', + @param(name='hydrochar_C_slope', element=HTL, kind='coupled', units='-', baseline=1.750, distribution=dist) - def set_biochar_C_slope(i): - HTL.biochar_C_slope=i + def set_hydrochar_C_slope(i): + HTL.hydrochar_C_slope=i dist = shape.Triangle(0.035,0.063,0.102) @param(name='biocrude_moisture_content', @@ -328,14 +603,14 @@ def set_biocrude_moisture_content(i): HTL.biocrude_moisture_content=i dist = shape.Uniform(0.84,0.88) - @param(name='biochar_P_recovery_ratio', + @param(name='hydrochar_P_recovery_ratio', element=HTL, kind='coupled', units='-', baseline=0.86, distribution=dist) - def set_biochar_P_recovery_ratio(i): - HTL.biochar_P_recovery_ratio=i + def set_hydrochar_P_recovery_ratio(i): + HTL.hydrochar_P_recovery_ratio=i # ========================================================================= # AcidEx @@ -636,56 +911,80 @@ def set_HC_hydrocarbon_ratio(i): # TEA # ========================================================================= dist = shape.Triangle(0.6,1,1.4) - @param(name='HTL_CAPEX_factor', + @param(name='HTL_TIC_factor', element='TEA', kind='isolated', units='-', baseline=1, distribution=dist) - def set_HTL_CAPEX_factor(i): - HTL.CAPEX_factor=i + def set_HTL_TIC_factor(i): + HTL.TIC_factor=i dist = shape.Triangle(0.6,1,1.4) - @param(name='CHG_CAPEX_factor', + @param(name='CHG_TIC_factor', element='TEA', kind='isolated', units='-', baseline=1, distribution=dist) - def set_CHG_CAPEX_factor(i): - CHG.CAPEX_factor=i + def set_CHG_TIC_factor(i): + CHG.TIC_factor=i dist = shape.Triangle(0.6,1,1.4) - @param(name='HT_CAPEX_factor', + @param(name='HT_TIC_factor', element='TEA', kind='isolated', units='-', baseline=1, distribution=dist) - def set_HT_CAPEX_factor(i): - HT.CAPEX_factor=i + def set_HT_TIC_factor(i): + HT.TIC_factor=i CHP = unit.CHP dist = shape.Uniform(980,1470) - @param(name='unit_CAPEX', + @param(name='unit_TIC', element='TEA', kind='isolated', units='-', baseline=1225, distribution=dist) - def set_unit_CAPEX(i): - CHP.unit_CAPEX=i + def set_unit_TIC(i): + CHP.unit_TIC=i tea = sys.TEA - dist = shape.Triangle(0,0.1,0.2) - @param(name='IRR', + + if high_IRR == False: + dist = shape.Triangle(0,0.03,0.05) + @param(name='IRR', + element='TEA', + kind='isolated', + units='-', + baseline=0.03, + distribution=dist) + def set_IRR(i): + tea.IRR=i + + else: + dist = shape.Triangle(0.05,0.1,0.15) + @param(name='IRR', + element='TEA', + kind='isolated', + units='-', + baseline=0.1, + distribution=dist) + def set_IRR(i): + tea.IRR=i + + makeup_water = stream.makeup_water + dist = shape.Uniform(0.000475,0.000581) + @param(name='makeup water price', element='TEA', kind='isolated', - units='-', - baseline=0.1, + units='$/kg', + baseline=0.000528, distribution=dist) - def set_IRR(i): - tea.IRR=i + def set_makeup_water_price(i): + makeup_water.price=i H2SO4 = stream.H2SO4 dist = shape.Triangle(0.005994,0.00658,0.014497) @@ -865,7 +1164,7 @@ def set_electrivity_price(i): # LCA (unifrom ± 10%) # ========================================================================= # don't get joint distribution for multiple times, since the baselines for LCA will change. - qs.ImpactItem.get_all_items().pop('waste_sludge_item') + qs.ImpactItem.get_all_items().pop('feedstock_item') for item in qs.ImpactItem.get_all_items().keys(): for CF in qs.ImpactIndicator.get_all_indicators().keys(): abs_small = 0.9*qs.ImpactItem.get_item(item).CFs[CF] @@ -889,6 +1188,19 @@ def set_LCA(i): if include_other_metrics: # all metrics # Element metrics + + @metric(name='C_afdw',units='%',element='Sankey') + def get_C_afdw(): + return WWTP.sludge_C*24/1000/(sys.flowsheet.stream.raw_wastewater.F_mass/157262.48454459725)/(1-WWTP.sludge_dw_ash) + + @metric(name='N_afdw',units='%',element='Sankey') + def get_N_afdw(): + return WWTP.sludge_N*24/1000/(sys.flowsheet.stream.raw_wastewater.F_mass/157262.48454459725)/(1-WWTP.sludge_dw_ash) + + @metric(name='P_afdw',units='%',element='Sankey') + def get_P_afdw(): + return WWTP.sludge_P*24/1000/(sys.flowsheet.stream.raw_wastewater.F_mass/157262.48454459725)/(1-WWTP.sludge_dw_ash) + @metric(name='sludge_C',units='kg/hr',element='Sankey') def get_sludge_C(): return WWTP.sludge_C @@ -925,13 +1237,13 @@ def get_biocrude_N(): def get_offgas_C(): return HTL.offgas_C - @metric(name='biochar_C',units='kg/hr',element='Sankey') - def get_biochar_C(): - return HTL.biochar_C + @metric(name='hydrochar_C',units='kg/hr',element='Sankey') + def get_hydrochar_C(): + return HTL.hydrochar_C - @metric(name='biochar_P',units='kg/hr',element='Sankey') - def get_biochar_P(): - return HTL.biochar_P + @metric(name='hydrochar_P',units='kg/hr',element='Sankey') + def get_hydrochar_P(): + return HTL.hydrochar_P cmps = qs.get_components() D2 = unit.D2 @@ -994,11 +1306,11 @@ def get_extracted_P(): @metric(name='residual_P',units='kg/hr',element='Sankey') def get_residual_P(): - return HTL.biochar_P-AcidEx.outs[1].imass['P'] + return HTL.hydrochar_P-AcidEx.outs[1].imass['P'] @metric(name='residual_C',units='kg/hr',element='Sankey') def get_residual_C(): - return HTL.biochar_C + return HTL.hydrochar_C @metric(name='struvite_N',units='kg/hr',element='Sankey') def get_struvite_N(): @@ -1054,6 +1366,11 @@ def get_MemDis_ww_P(): return MemDis.outs[1].imass['P'] # Energy metrics + + @metric(name='sludge_HHV',units='MJ/kg',element='Sankey') + def get_sludge_HHV(): + return WWTP.sludge_HHV + @metric(name='sludge_E',units='GJ/hr',element='Sankey') def get_sludge_E(): return (WWTP.outs[0].F_mass-WWTP.outs[0].imass['H2O'])*WWTP.sludge_HHV/1000 @@ -1106,62 +1423,69 @@ def get_HC_H2_E(): def get_CHG_gas_E(): return F1.outs[0].HHV/1000000 - # CAPEX metrics - @metric(name='CAPEX',units='$',element='TEA') - def get_CAPEX(): + # CAPEX metrics (as total installed cost, TIC) + @metric(name='TIC',units='$',element='TEA') + def get_TIC(): return sys.installed_equipment_cost - SluC, P1 = unit.SluC, unit.P1 - @metric(name='HTL_CAPEX',units='$',element='TEA') - def get_HTL_CAPEX(): - return sum(i.installed_cost for i in (SluC, P1, H1, HTL)) + P1 = unit.P1 + + try: + SluC = unit.SluC + @metric(name='HTL_TIC',units='$',element='TEA') + def get_HTL_TIC(): + return sum(i.installed_cost for i in (SluC, P1, H1, HTL)) + except AttributeError: + @metric(name='HTL_TIC',units='$',element='TEA') + def get_HTL_TIC(): + return sum(i.installed_cost for i in (P1, H1, HTL)) SP1, H2SO4_Tank = unit.SP1, unit.H2SO4_Tank if AcidEx: if SP1.F_mass_out != 0: - def get_Phosphorus_CAPEX(): + def get_Phosphorus_TIC(): return (AcidEx.installed_cost + StruPre.installed_cost + H2SO4_Tank.installed_cost*SP1.outs[0].F_mass/SP1.F_mass_out) else: - def get_Phosphorus_CAPEX(): + def get_Phosphorus_TIC(): return 0 else: if SP1.F_mass_out != 0: - def get_Phosphorus_CAPEX(): + def get_Phosphorus_TIC(): return (StruPre.installed_cost + H2SO4_Tank.installed_cost*SP1.outs[0].F_mass/SP1.F_mass_out) else: - def get_Phosphorus_CAPEX(): + def get_Phosphorus_TIC(): return 0 - model.metric(getter=get_Phosphorus_CAPEX, name='Phosphorus_CAPEX',units='$',element='TEA') + model.metric(getter=get_Phosphorus_TIC, name='Phosphorus_TIC',units='$',element='TEA') - @metric(name='CHG_CAPEX',units='$',element='TEA') - def get_CHG_CAPEX(): + @metric(name='CHG_TIC',units='$',element='TEA') + def get_CHG_TIC(): return CHG.installed_cost + F1.installed_cost if SP1.F_mass_out != 0: - def get_Nitrogen_CAPEX(): + def get_Nitrogen_TIC(): return MemDis.installed_cost+H2SO4_Tank.installed_cost*SP1.outs[1].F_mass/SP1.F_mass_out else: - def get_Nitrogen_CAPEX(): + def get_Nitrogen_TIC(): return 0 - model.metric(getter=get_Nitrogen_CAPEX, name='Nitrogen_CAPEX',units='$',element='TEA') + model.metric(getter=get_Nitrogen_TIC, name='Nitrogen_TIC',units='$',element='TEA') HT_HC_units = (unit.P2, HT, unit.H2, F2, unit.H3, D1, D2, D3, unit.P3, HC, unit.H4, F3, D4, unit.H5, unit.H6, unit.GasolineTank, unit.DieselTank) - @metric(name='HT_HC_CAPEX',units='$',element='TEA') - def get_HT_HC_CAPEX(): + @metric(name='HT_HC_TIC',units='$',element='TEA') + def get_HT_HC_TIC(): return sum(i.installed_cost for i in HT_HC_units) HXN = unit.HXN - @metric(name='HXN_CAPEX',units='$',element='TEA') - def get_HXN_CAPEX(): + @metric(name='HXN_TIC',units='$',element='TEA') + def get_HXN_TIC(): return HXN.installed_cost - @metric(name='CHP_CAPEX',units='$',element='TEA') - def get_CHP_CAPEX(): + @metric(name='CHP_TIC',units='$',element='TEA') + def get_CHP_TIC(): return CHP.installed_cost @metric(name='AOC',units='$/yr',element='TEA') @@ -1221,9 +1545,15 @@ def get_membrane_VOC(): def get_utility_VOC(): return sys.utility_cost - @metric(name='HTL_utility_VOC',units='$/yr',element='TEA') - def get_HTL_utility_VOC(): - return (SluC.utility_cost+P1.utility_cost+H1.utility_cost+HTL.utility_cost)*sys.operating_hours + try: + SluC = unit.SluC + @metric(name='HTL_utility_VOC',units='$/yr',element='TEA') + def get_HTL_utility_VOC(): + return (SluC.utility_cost+P1.utility_cost+H1.utility_cost+HTL.utility_cost)*sys.operating_hours + except AttributeError: + @metric(name='HTL_utility_VOC',units='$/yr',element='TEA') + def get_HTL_utility_VOC(): + return (P1.utility_cost+H1.utility_cost+HTL.utility_cost)*sys.operating_hours @metric(name='CHG_utility_VOC',units='$/yr',element='TEA') def get_CHG_utility_VOC(): @@ -1256,11 +1586,20 @@ def get_stream_GWP(): def get_other_GWP(): return lca.get_other_impacts()['GlobalWarming'] - @metric(name='HTL_constrution_GWP',units='kg CO2 eq',element='LCA') - def get_HTL_constrution_GWP(): - table_construction = lca.get_impact_table('Construction')['GlobalWarming [kg CO2-eq]'] - return table_construction['Stainless_steel [kg]']['A000']+table_construction['Stainless_steel [kg]']['A100']+\ - table_construction['Stainless_steel [kg]']['A110']+table_construction['Stainless_steel [kg]']['A120'] + try: + SluC = unit.SluC + @metric(name='HTL_constrution_GWP',units='kg CO2 eq',element='LCA') + def get_HTL_constrution_GWP(): + table_construction = lca.get_impact_table('Construction')['GlobalWarming [kg CO2-eq]'] + return table_construction['Stainless_steel [kg]']['A000']+table_construction['Stainless_steel [kg]']['A100']+\ + table_construction['Stainless_steel [kg]']['A110']+table_construction['Stainless_steel [kg]']['A120'] + except AttributeError: + @metric(name='HTL_constrution_GWP',units='kg CO2 eq',element='LCA') + def get_HTL_constrution_GWP(): + table_construction = lca.get_impact_table('Construction')['GlobalWarming [kg CO2-eq]'] + return +table_construction['Stainless_steel [kg]']['A100']+\ + table_construction['Stainless_steel [kg]']['A110']+table_construction['Stainless_steel [kg]']['A120'] + if AcidEx: def get_nutrient_constrution_GWP(): table_construction = lca.get_impact_table('Construction')['GlobalWarming [kg CO2-eq]'] @@ -1315,17 +1654,31 @@ def get_CHP_stream_GWP(): table_stream = lca.get_impact_table('Stream')['GlobalWarming [kg CO2-eq]'] return table_stream['natural_gas'] - @metric(name='HTL_utility_GWP',units='kg CO2 eq',element='LCA') - def get_HTL_utility_GWP(): - table_other = lca.get_impact_table('Other')['GlobalWarming [kg CO2-eq]'] - a = 0 - for i in range (len(HTL.heat_utilities)): - if HTL.heat_utilities[i].duty < 0: - a += HTL.heat_utilities[i].duty - return table_other['Cooling [MJ]']/sys.get_cooling_duty()*(-a*sys.operating_hours)+\ - table_other['Electricity [kWh]']/(sys.get_electricity_consumption()-sys.get_electricity_production())*\ - (SluC.power_utility.consumption+P1.power_utility.consumption)*sys.operating_hours - + try: + SluC = unit.SluC + @metric(name='HTL_utility_GWP',units='kg CO2 eq',element='LCA') + def get_HTL_utility_GWP(): + table_other = lca.get_impact_table('Other')['GlobalWarming [kg CO2-eq]'] + a = 0 + for i in range (len(HTL.heat_utilities)): + if HTL.heat_utilities[i].duty < 0: + a += HTL.heat_utilities[i].duty + return table_other['Cooling [MJ]']/sys.get_cooling_duty()*(-a*sys.operating_hours)+\ + table_other['Electricity [kWh]']/(sys.get_electricity_consumption()-sys.get_electricity_production())*\ + (SluC.power_utility.consumption+P1.power_utility.consumption)*sys.operating_hours + + except AttributeError: + @metric(name='HTL_utility_GWP',units='kg CO2 eq',element='LCA') + def get_HTL_utility_GWP(): + table_other = lca.get_impact_table('Other')['GlobalWarming [kg CO2-eq]'] + a = 0 + for i in range (len(HTL.heat_utilities)): + if HTL.heat_utilities[i].duty < 0: + a += HTL.heat_utilities[i].duty + return table_other['Cooling [MJ]']/sys.get_cooling_duty()*(-a*sys.operating_hours)+\ + table_other['Electricity [kWh]']/(sys.get_electricity_consumption()-sys.get_electricity_production())*\ + (P1.power_utility.consumption)*sys.operating_hours + @metric(name='CHG_utility_GWP',units='kg CO2 eq',element='LCA') def get_CHG_utility_GWP(): table_other = lca.get_impact_table('Other')['GlobalWarming [kg CO2-eq]'] @@ -1485,9 +1838,9 @@ def get_HTL_biocrude_yield(): def get_HTL_aqueous_yield(): return HTL.aqueous_yield - @metric(name='HTL_biochar_yield',units='-',element='HTL') - def get_HTL_biochar_yield(): - return HTL.biochar_yield + @metric(name='HTL_hydrochar_yield',units='-',element='HTL') + def get_HTL_hydrochar_yield(): + return HTL.hydrochar_yield @metric(name='HTL_gasyield',units='-',element='HTL') def get_HTL_gas_yield(): @@ -1499,7 +1852,7 @@ def get_MDSP(): diesel_gal_2_kg=3.220628346 return tea.solve_price(diesel)*diesel_gal_2_kg - raw_wastewater = stream.raw_wastewater + raw_wastewater = stream.feedstock_assumed_in_wastewater @metric(name='sludge_management_price',units='$/ton dry sludge',element='TEA') def get_sludge_treatment_price(): return -tea.solve_price(raw_wastewater)*_MMgal_to_L/WWTP.ww_2_dry_sludge @@ -1522,7 +1875,7 @@ def get_OZP_diesel(): @metric(name='OzoneDepletion_sludge',units='kg CFC-11-eq/ton dry sludge',element='LCA') def get_OZP_sludge(): - return lca.get_total_impacts()['OzoneDepletion']/raw_wastewater.F_vol/_m3perh_to_MGD/WWTP.ww_2_dry_sludge/(sys.operating_hours/24)/lca.lifetime + return lca.get_total_impacts(exclude=(raw_wastewater,))['OzoneDepletion']/raw_wastewater.F_vol/_m3perh_to_MGD/WWTP.ww_2_dry_sludge/(sys.operating_hours/24)/lca.lifetime @metric(name='Carcinogenics_diesel',units='kg benzene-eq/MMBTU diesel',element='LCA') def get_CAR_diesel(): @@ -1530,7 +1883,7 @@ def get_CAR_diesel(): @metric(name='Carcinogenics_sludge',units='kg benzene-eq/ton dry sludge',element='LCA') def get_CAR_sludge(): - return lca.get_total_impacts()['Carcinogenics']/raw_wastewater.F_vol/_m3perh_to_MGD/WWTP.ww_2_dry_sludge/(sys.operating_hours/24)/lca.lifetime + return lca.get_total_impacts(exclude=(raw_wastewater,))['Carcinogenics']/raw_wastewater.F_vol/_m3perh_to_MGD/WWTP.ww_2_dry_sludge/(sys.operating_hours/24)/lca.lifetime @metric(name='Acidification_diesel',units='moles of H+-eq/MMBTU diesel',element='LCA') def get_ACD_diesel(): @@ -1538,7 +1891,7 @@ def get_ACD_diesel(): @metric(name='Acidification_sludge',units='moles of H+-eq/ton dry sludge',element='LCA') def get_ACD_sludge(): - return lca.get_total_impacts()['Acidification']/raw_wastewater.F_vol/_m3perh_to_MGD/WWTP.ww_2_dry_sludge/(sys.operating_hours/24)/lca.lifetime + return lca.get_total_impacts(exclude=(raw_wastewater,))['Acidification']/raw_wastewater.F_vol/_m3perh_to_MGD/WWTP.ww_2_dry_sludge/(sys.operating_hours/24)/lca.lifetime @metric(name='RespiratoryEffects_diesel',units='kg PM2.5-eq/MMBTU diesel',element='LCA') def get_RES_diesel(): @@ -1546,7 +1899,7 @@ def get_RES_diesel(): @metric(name='RespiratoryEffects_sludge',units='kg PM2.5-eq/ton dry sludge',element='LCA') def get_RES_sludge(): - return lca.get_total_impacts()['RespiratoryEffects']/raw_wastewater.F_vol/_m3perh_to_MGD/WWTP.ww_2_dry_sludge/(sys.operating_hours/24)/lca.lifetime + return lca.get_total_impacts(exclude=(raw_wastewater,))['RespiratoryEffects']/raw_wastewater.F_vol/_m3perh_to_MGD/WWTP.ww_2_dry_sludge/(sys.operating_hours/24)/lca.lifetime @metric(name='Eutrophication_diesel',units='kg N/MMBTU diesel',element='LCA') def get_EUT_diesel(): @@ -1554,7 +1907,7 @@ def get_EUT_diesel(): @metric(name='Eutrophication_sludge',units='kg N/ton dry sludge',element='LCA') def get_EUT_sludge(): - return lca.get_total_impacts()['Eutrophication']/raw_wastewater.F_vol/_m3perh_to_MGD/WWTP.ww_2_dry_sludge/(sys.operating_hours/24)/lca.lifetime + return lca.get_total_impacts(exclude=(raw_wastewater,))['Eutrophication']/raw_wastewater.F_vol/_m3perh_to_MGD/WWTP.ww_2_dry_sludge/(sys.operating_hours/24)/lca.lifetime @metric(name='PhotochemicalOxidation_diesel',units='kg NOx-eq/MMBTU diesel',element='LCA') def get_PHO_diesel(): @@ -1562,7 +1915,7 @@ def get_PHO_diesel(): @metric(name='PhotochemicalOxidation_sludge',units='kg NOx-eq/ton dry sludge',element='LCA') def get_PHO_sludge(): - return lca.get_total_impacts()['PhotochemicalOxidation']/raw_wastewater.F_vol/_m3perh_to_MGD/WWTP.ww_2_dry_sludge/(sys.operating_hours/24)/lca.lifetime + return lca.get_total_impacts(exclude=(raw_wastewater,))['PhotochemicalOxidation']/raw_wastewater.F_vol/_m3perh_to_MGD/WWTP.ww_2_dry_sludge/(sys.operating_hours/24)/lca.lifetime @metric(name='Ecotoxicity_diesel',units='kg 2,4-D-eq/MMBTU diesel',element='LCA') def get_ECO_diesel(): @@ -1570,7 +1923,7 @@ def get_ECO_diesel(): @metric(name='Ecotoxicity_sludge',units='kg 2,4-D-eq/ton dry sludge',element='LCA') def get_ECO_sludge(): - return lca.get_total_impacts()['Ecotoxicity']/raw_wastewater.F_vol/_m3perh_to_MGD/WWTP.ww_2_dry_sludge/(sys.operating_hours/24)/lca.lifetime + return lca.get_total_impacts(exclude=(raw_wastewater,))['Ecotoxicity']/raw_wastewater.F_vol/_m3perh_to_MGD/WWTP.ww_2_dry_sludge/(sys.operating_hours/24)/lca.lifetime @metric(name='NonCarcinogenics_diesel',units='kg toluene-eq/MMBTU diesel',element='LCA') def get_NCA_diesel(): @@ -1578,7 +1931,7 @@ def get_NCA_diesel(): @metric(name='NonCarcinogenics_sludge',units='kg toluene-eq/ton dry sludge',element='LCA') def get_NCA_sludge(): - return lca.get_total_impacts()['NonCarcinogenics']/raw_wastewater.F_vol/_m3perh_to_MGD/WWTP.ww_2_dry_sludge/(sys.operating_hours/24)/lca.lifetime + return lca.get_total_impacts(exclude=(raw_wastewater,))['NonCarcinogenics']/raw_wastewater.F_vol/_m3perh_to_MGD/WWTP.ww_2_dry_sludge/(sys.operating_hours/24)/lca.lifetime if include_check: diff --git a/exposan/htl/readme_figures/example_system.png b/exposan/htl/readme_figures/example_system.png new file mode 100644 index 00000000..a6c00671 Binary files /dev/null and b/exposan/htl/readme_figures/example_system.png differ diff --git a/exposan/htl/systems.py b/exposan/htl/systems.py index a12ce274..44f9bc76 100644 --- a/exposan/htl/systems.py +++ b/exposan/htl/systems.py @@ -28,8 +28,6 @@ https://doi.org/10.2172/1111191. ''' - - import os, qsdsan as qs from qsdsan import sanunits as qsu from biosteam.units import IsenthalpicValve @@ -44,9 +42,12 @@ __all__ = ('create_system',) -def create_system(configuration='baseline', waste_price=0, waste_GWP=0): +def create_system(configuration='baseline', capacity=100, + sludge_moisture_content=0.8, sludge_dw_ash_content=0.257, + sludge_afdw_lipid_content=0.204, sludge_afdw_protein_content=0.463, + waste_cost=0, waste_GWP=0): configuration = configuration or 'baseline' - if configuration not in ('baseline', 'no_P', 'PSA'): + if configuration not in ('baseline','no_P','PSA'): raise ValueError('`configuration` can only be "baseline", ' '"no_P" (without acid extraction and P recovery), ' 'or "PSA" (with H2 recovery through pressure swing adsorption), ' @@ -69,8 +70,7 @@ def create_system(configuration='baseline', waste_price=0, waste_GWP=0): qs.ImpactIndicator.load_from_file(os.path.join(folder, 'data/impact_indicators.csv')) qs.ImpactItem.load_from_file(os.path.join(folder, 'data/impact_items.xlsx')) - - raw_wastewater = qs.WasteStream('raw_wastewater', H2O=100, units='MGD', T=25+273.15) + raw_wastewater = qs.WasteStream('feedstock_assumed_in_wastewater', H2O=capacity, units='MGD', T=25+273.15) # Jones baseline: 1276.6 MGD, 1.066e-4 $/kg ww # set H2O equal to the total raw wastewater into the WWTP @@ -79,43 +79,61 @@ def create_system(configuration='baseline', waste_price=0, waste_GWP=0): # ============================================================================= WWTP = su.WWTP('S000', ins=raw_wastewater, outs=('sludge','treated_water'), - ww_2_dry_sludge=0.94, + ww_2_dry_sludge=1, # how much metric ton/day sludge can be produced by 1 MGD of ww - sludge_moisture=0.99, sludge_dw_ash=0.257, - sludge_afdw_lipid=0.204, sludge_afdw_protein=0.463, operation_hours=7920) + sludge_moisture=sludge_moisture_content, sludge_dw_ash=sludge_dw_ash_content, + sludge_afdw_lipid=sludge_afdw_lipid_content, sludge_afdw_protein=sludge_afdw_protein_content, + operation_hours=7920) WWTP.register_alias('WWTP') - raw_wastewater.price = -WWTP.ww_2_dry_sludge*waste_price/3.79/(10**6) + raw_wastewater.price = -WWTP.ww_2_dry_sludge*waste_cost/3.79/(10**6) + + if WWTP.sludge_moisture <= 0.8: + + Humidifier = su.Humidifier(ID='S010', ins=(WWTP-0, 'makeup_water', 'recycle'), outs='HTL_influent') + + Humidifier.ins[1].price = 0.000528 # U.S. average price: 2 $/1000 gal (1 gal = 3.79 kg) + # water weight: https://www.omnicalculator.com/conversion/kg-to-gallons#:~:text=1%20gal%20%3D%203.79%20kg%20of%20water (accessed 2023-10-27) + # tap water price: https://portal.ct.gov/-/media/Departments-and-Agencies/DPH/dph/drinking_water/pdf/dwcfedfundpdf.pdf (accessed 2023-10-27) + Humidifier.register_alias('Humidifier') + + P1 = qsu.SludgePump('A100', ins=Humidifier-0, outs='press_sludge', P=3049.7*6894.76, + init_with='Stream') + P1.register_alias('P1') + # Jones 2014: 3049.7 psia - SluC = qsu.SludgeCentrifuge('A000', ins=WWTP-0, - outs=('supernatant','compressed_sludge'), - init_with='Stream', - solids=('Sludge_lipid','Sludge_protein', - 'Sludge_carbo','Sludge_ash'), - sludge_moisture=0.8) - SluC.register_alias('SluC') + elif WWTP.sludge_moisture > 0.8: + + SluC = qsu.SludgeCentrifuge('A000', ins=WWTP-0, + outs=('supernatant','compressed_sludge'), + init_with='Stream', + solids=('Sludge_lipid','Sludge_protein', + 'Sludge_carbo','Sludge_ash'), + sludge_moisture=0.8) + SluC.register_alias('SluC') + + P1 = qsu.SludgePump('A100', ins=SluC-1, outs='press_sludge', P=3049.7*6894.76, + init_with='Stream') + P1.register_alias('P1') + # Jones 2014: 3049.7 psia # ============================================================================= # HTL (Area 100) # ============================================================================= - P1 = qsu.SludgePump('A100', ins=SluC-1, outs='press_sludge', P=3049.7*6894.76, - init_with='Stream') - P1.register_alias('P1') - # Jones 2014: 3049.7 psia - H1 = qsu.HXutility('A110', include_construction=True, ins=P1-0, outs='heated_sludge', T=351+273.15, - U=0.0795, init_with='Stream', rigorous=True) + U=0.0198739, init_with='Stream', rigorous=True) # feed T is low, thus high viscosity and low U (case B in Knorr 2013) - # U: 3, 14, 15 BTU/hr/ft2/F as minimum, baseline, and maximum - # U: 0.0170348, 0.0794957, 0.085174 kW/m2/K + # U: 3, 3.5, 4 BTU/hr/ft2/F as minimum, baseline, and maximum + # U: 0.0170348, 0.0198739, 0.0227131 kW/m2/K # H1: SS PNNL 2020: 50 (17-76) Btu/hr/ft2/F ~ U = 0.284 (0.096-0.4313) kW/m2/K - # but not in other pumps (low viscosity, don't need U to enforce total heat transfer efficiency) + # but not in other heat exchangers (low viscosity, don't need U to enforce total heat transfer efficiency) # unit conversion: https://www.unitsconverters.com/en/Btu(It)/Hmft2mdegf-To-W/M2mk/Utu-4404-4398 H1.register_alias('H1') - HTL = qsu.HydrothermalLiquefaction('A120', ins=H1-0, outs=('biochar','HTL_aqueous','biocrude','offgas_HTL')) + HTL = qsu.HydrothermalLiquefaction('A120', ins=H1-0, outs=('hydrochar','HTL_aqueous','biocrude','offgas_HTL'), + mositure_adjustment_exist_in_the_system=True) HTL.register_alias('HTL') # ============================================================================= @@ -156,7 +174,7 @@ def create_system(configuration='baseline', waste_price=0, waste_GWP=0): StruPre.register_alias('StruPre') CHG = qsu.CatalyticHydrothermalGasification( - 'A230', ins=(StruPre-1, '7.8%_Ru/C'), outs=('CHG_out', '7.8%_Ru/C_out')) + 'A230', ins=(StruPre-1, '7.8%_Ru/C'), outs=('CHG_out','7.8%_Ru/C_out')) CHG.ins[1].price = 134.53 CHG.register_alias('CHG') @@ -168,7 +186,7 @@ def create_system(configuration='baseline', waste_price=0, waste_GWP=0): F1.register_alias('F1') MemDis = qsu.MembraneDistillation('A260', ins=(F1-1, SP1-1, 'NaOH', 'Membrane_in'), - outs=('ammonium_sulfate','MemDis_ww', 'Membrane_out','solution'), init_with='WasteStream') + outs=('ammonium_sulfate','MemDis_ww','Membrane_out','solution'), init_with='WasteStream') MemDis.ins[2].price = 0.5256 MemDis.outs[0].price = 0.3236 MemDis.register_alias('MemDis') @@ -195,7 +213,7 @@ def create_system(configuration='baseline', waste_price=0, waste_GWP=0): # HT_cls = su.HT if configuration != 'PSA' else su.HT_PSA include_PSA = False if 'PSA' not in configuration else True HT = qsu.Hydrotreating('A310', ins=(P2-0, RSP1-0, 'CoMo_alumina_HT'), - outs=('HTout', 'CoMo_alumina_HT_out'), include_PSA=include_PSA) + outs=('HTout','CoMo_alumina_HT_out'), include_PSA=include_PSA) HT.ins[2].price = 38.79 HT.register_alias('HT') @@ -256,7 +274,7 @@ def create_system(configuration='baseline', waste_price=0, waste_GWP=0): # (844.6 F). HC = qsu.Hydrocracking('A410', ins=(P3-0, RSP1-1, 'CoMo_alumina_HC'), - outs=('HC_out', 'CoMo_alumina_HC_out')) + outs=('HC_out','CoMo_alumina_HC_out')) HC.ins[2].price = 38.79 HC.register_alias('HC') @@ -329,8 +347,12 @@ def create_system(configuration='baseline', waste_price=0, waste_GWP=0): outs=('fuel_gas'), init_with='Stream') GasMixer.register_alias('GasMixer') - WWmixer = su.WWmixer('S590', ins=(SluC-0, MemDis-1, SP2-0), - outs='wastewater', init_with='Stream') + try: + WWmixer = su.WWmixer('S590', ins=(SluC-0, MemDis-1, SP2-0), + outs='wastewater', init_with='Stream') + except UnboundLocalError: + WWmixer = su.WWmixer('S590', ins=('', MemDis-1, SP2-0), + outs='wastewater', init_with='Stream') # effluent of WWmixer goes back to WWTP WWmixer.register_alias('WWmixer') @@ -338,7 +360,8 @@ def create_system(configuration='baseline', waste_price=0, waste_GWP=0): # facilities # ============================================================================= - qsu.HeatExchangerNetwork('HXN', force_ideal_thermo=True) + qsu.HeatExchangerNetwork('HXN', T_min_app=86, force_ideal_thermo=True) + # 86 K: Jones et al. PNNL, 2014 CHP = qsu.CombinedHeatPower('CHP', include_construction=True, ins=(GasMixer-0, 'natural_gas', 'air'), @@ -356,8 +379,8 @@ def create_system(configuration='baseline', waste_price=0, waste_GWP=0): ##### Add stream impact items ##### # add impact for waste sludge - qs.StreamImpactItem(ID='waste_sludge_item', - linked_stream=stream.raw_wastewater, + qs.StreamImpactItem(ID='feedstock_item', + linked_stream=stream.feedstock_assumed_in_wastewater, Acidification=0, Ecotoxicity=0, Eutrophication=0, @@ -368,6 +391,19 @@ def create_system(configuration='baseline', waste_price=0, waste_GWP=0): NonCarcinogenics=0, RespiratoryEffects=0) + # add impact for makeup water + qs.StreamImpactItem(ID='makeup_water_item', + linked_stream=stream.makeup_water, + Acidification=0.00011676, + Ecotoxicity=0.0050151, + Eutrophication=0.000000073096, + GlobalWarming=0.00030228, + OzoneDepletion=0.00000000016107, + PhotochemicalOxidation=0.00000074642, + Carcinogenics=0.0000061925, + NonCarcinogenics=0.009977, + RespiratoryEffects=0.00000068933) + # Biocrude upgrading qs.StreamImpactItem(ID='H2_item', linked_stream=stream.H2, @@ -557,7 +593,7 @@ def create_system(configuration='baseline', waste_price=0, waste_GWP=0): NonCarcinogenics=-2.9281, RespiratoryEffects=-0.0011096) - create_tea(sys) + create_tea(sys, IRR_value=0.03, finance_interest_value=0.03) qs.LCA( system=sys, lifetime=30, lifetime_unit='yr', Electricity=lambda:(sys.get_electricity_consumption()-sys.get_electricity_production())*30, diff --git a/tests/test_htl.py b/tests/test_htl.py index ca46b685..c84ec107 100644 --- a/tests/test_htl.py +++ b/tests/test_htl.py @@ -24,6 +24,11 @@ def test_htl(): rtol = 5e-2 kwargs = dict( + feedstock='sludge', + plant_size=False, + ternary=False, + high_IRR=False, + exclude_sludge_compositions=False, include_HTL_yield_as_metrics=False, include_other_metrics=False, include_other_CFs_as_metrics=False, @@ -32,17 +37,17 @@ def test_htl(): # m1 = htl.create_model('baseline', **kwargs) # df1 = m1.metrics_at_baseline() - # values1 = [5.81, 165.134, 58.689, 397.735] + # values1 = [3.994, 53.217, 50.398, 326.790] # assert_allclose(df1.values, values1, rtol=rtol) # m2 = htl.create_model('no_P', **kwargs) # df2 = m2.metrics_at_baseline() - # values2 = [6.323, 196.806, 40.221, 239.713] + # values2 = [4.549, 87.407, 37.748, 218.554] # assert_allclose(df2.values, values2, rtol=rtol) m3 = htl.create_model('PSA', **kwargs) df3 = m3.metrics_at_baseline() - values3 = [4.791, 102.337, 79.657, 577.158] + values3 = [3.319, 11.595, 64.928, 451.126] assert_allclose(df3.values, values3, rtol=rtol)