BioSTEAMDevelopmentGroup / Bioindustrial-Park

BioSTEAM's Premier Repository for Biorefinery Models and Results
MIT License
36 stars 17 forks source link

Request about error: failed to find bracket #64

Closed zasddsgg closed 1 year ago

zasddsgg commented 1 year ago

Hello, I encountered an error while using BioSTEAM: failed to find bracket, it seems that it is caused by ms.vle(T=self.T, V=self.V). I set V=0 because I want to control the gas fraction of outlet stream to be zero. I tried to run the condition in the code in Aspen, and it worked. May I trouble you help to see how to solve this error code. Thanks for your help. Wish you a good day. The code and error information is as follows:

import biosteam as bst bst.nbtutorial() from biosteam.units.decorators import cost from biosteam import Unit from biosteam import units from biosteam.units.design_tools.geometry import cylinder_diameter_from_volume

from thermosteam import MultiStream import thermosteam as tmo

from biorefineries import cellulosic cs = cellulosic.Biorefinery('corn stover ethanol') chemicals_cs = cs.chemicals bst.settings.set_thermo(chemicals_cs) bst.main_flowsheet.set_flowsheet('corn stover_ethanol')

cornstover = bst.Stream( 'cornstover', Water=8376.35530314805, Ethanol=0.777817467000001, Glucose=0.138222603, Galactose=0.204447216, Mannose=0.0876245290000003, Xylose=2.32770801, Arabinose=0.743299376000001, Cellobiose=0.310691989000001, Sucrose=1.87457352235111, GlucoseOligomer=3.38385322, GalactoseOligomer=0.037254861, MannoseOligomer=0.015967151, XyloseOligomer=0.816486943000001, ArabinoseOligomer=0.137168892, Extract=143.655450486199, SolubleLignin=1.45892539, HMF=0.504619411, Furfural=3.86516298, AceticAcid=34.7794066000001, LacticAcid=8.97227473, Xylitol=3.15299514000001, Glycerol=1.04685476, SuccinicAcid=2.38173359000001, NH3=6.64670000000001E-06, H2SO4=19.9625986528278, Oil=0.0682150190000001, O2=1.54240000000001E-90, N2=2.90730000000002E-99, CO2=3.25180000000002E-63, SO2=1.6187E-20, Protein=133.6292384, Glucan=180.173144058494, Galactan=7.3508188902784, Mannan=3.08427932896995, Xylan=123.218046720306, Arabinan=15.018301874109, Lignin=86.713967821333, Acetate=25.1139172462299, Ash=73.9073961631699, Z_mobilis=8.50261289000001, Biomass=5.66877035, Tar=0.042694621, P=9*101325, T=100.758+273.15, units='kmol/hr', price=0.05158816935126135, )

@cost('Dry flow rate', 'Pretreatment reactor system', units='kg/hr', S=83333, CE=522, cost=19812400 * 0.993, n=0.6, kW=4578, BM=1.5) class PretreatmentReactorSystem(bst.units.design_tools.PressureVessel, Unit): _N_ins = 1
_N_outs = 2 _graphics = bst.Flash._graphics _units = {'Residence time': 'hr', 'Reactor volume': 'm3'}

def __init__(self, ID='', ins=None, outs=(), T=100.758+273.15, V=0, thermo=None, 
             tau=0.166, V_wf=0.8, length_to_diameter=2, 
             vessel_material='Stainless steel 316', 
             vessel_type='Horizontal',
             reactions=None,
             run_vle=True):
    Unit.__init__(self, ID, ins, outs, thermo)
    self._load_components()
    vapor, liquid = self.outs
    vapor.phase = 'g'
    self.T = T
    self.V = V
    chemicals = self.chemicals
    if reactions is None:
        self.reactions = ParallelRxn([
    #            Reaction definition                 Reactant    Conversion
    Rxn('Glucan + H2O -> GlucoseOligomer',           'Glucan',   0.0030, chemicals),
    Rxn('Glucan + H2O -> Glucose',                   'Glucan',   0.0990, chemicals),
    Rxn('Glucan -> HMF + 2 H2O',                     'Glucan',   0.0030, chemicals),
    Rxn('Xylan + H2O -> XyloseOligomer',             'Xylan',    0.0240, chemicals),
    Rxn('Xylan + H2O -> Xylose',                     'Xylan',    0.9000, chemicals), 
    Rxn('Xylan -> Furfural + 2 H2O',                 'Xylan',    0.0500, chemicals),
    Rxn('Mannan + H2O -> MannoseOligomer',           'Mannan',   0.0240, chemicals),
    Rxn('Mannan + H2O -> Mannose',                   'Mannan',   0.9000, chemicals),
    Rxn('Mannan -> HMF + 2 H2O',                     'Mannan',   0.0500, chemicals),
    Rxn('Galactan + H2O -> GalactoseOligomer',       'Galactan', 0.0240, chemicals),        
    Rxn('Galactan + H2O -> Galactose',               'Galactan', 0.9000, chemicals),   
    Rxn('Galactan -> HMF + 2 H2O',                   'Galactan', 0.0500, chemicals),        
    Rxn('Arabinan + H2O -> ArabinoseOligomer',       'Arabinan', 0.0240, chemicals),
    Rxn('Arabinan + H2O -> Arabinose',               'Arabinan', 0.9000, chemicals),
    Rxn('Arabinan -> Furfural + 2 H2O',              'Arabinan', 0.0500, chemicals),
    Rxn('Acetate -> AceticAcid',                     'Acetate',  1.0000, chemicals),        
    Rxn('Furfural + 3 H2O  -> Tar',                  'Furfural',  1.0000, chemicals),
    Rxn('HMF + 3 H2O  -> 1.2 Tar',                   'HMF',       1.0000, chemicals),
    Rxn('Lignin -> SolubleLignin',                   'Lignin',    0.0500, chemicals),        
    Rxn('Sucrose -> HMF + Glucose + 2H2O',           'Sucrose',   1.0000, chemicals)
        ])
        # self.glucan_to_glucose = self.reactions[0]
         #self.xylan_to_xylose = self.reactions[8]
         # self.glucose_to_byproducts = self.reactions[1:3]
        # self.xylose_to_byproducts = self.reactions[9:12]
    else:
        self.reactions = reactions
    self.tau = tau
    self.V_wf = V_wf
    self.length_to_diameter = length_to_diameter
    self.vessel_material = vessel_material
    self.vessel_type = vessel_type
    self.run_vle = run_vle

def _load_components(self):
    thermo = self.thermo
    self._multistream = MultiStream(None, thermo=thermo)

def _run(self):
    feed = self.ins[0]
    vapor, liquid = self.outs
    liquid.copy_like(feed)
    self.reactions.adiabatic_reaction(liquid) 
    if self.T:
        if self.run_vle:
            ms = self._multistream
            ms.copy_like(liquid)
            ms.vle(T=self.T, V=self.V)
            vapor.mol[:] = ms.imol['g']
            liquid.mol[:] = ms.imol['l']
            vapor.T = liquid.T = ms.T
            vapor.P = liquid.P = ms.P
        else:
            liquid.T = self.T

def _design(self):
    Design = self.design_results
    ins_F_vol = self.F_vol_in
    V_reactor = ins_F_vol * self.tau / self.V_wf
    P = self.outs[0].P * 0.000145038 # Pa to psi
    length_to_diameter = self.length_to_diameter
    D = cylinder_diameter_from_volume(V_reactor, self.length_to_diameter)
    D *= 3.28084 # convert from m to ft
    L = D * length_to_diameter
    Design['Residence time'] = self.tau
    Design['Reactor volume'] = V_reactor
    Design.update(self._vessel_design(float(P), float(D), float(L)))
    self._decorated_design()
    Design = self.design_results

def _cost(self):
    Design = self.design_results
    self.baseline_purchase_costs.update(
        self._vessel_purchase_cost(
            Design['Weight'], Design['Diameter'], Design['Length']
        )
    )
    self._decorated_cost()

Rxn = tmo.reaction.Reaction ParallelRxn = tmo.reaction.ParallelReaction

R201 = PretreatmentReactorSystem('R201', cornstover)

A_sys = bst.main_flowsheet.create_system('A_sys') A_sys.simulate() A_sys.diagram('thorough', format='png')

error information: RuntimeError Traceback (most recent call last) File ~\AppData\Roaming\Python\Python39\site-packages\flexsolve\iterative_solvers.py:110, in aitken(f, x, xtol, args, maxiter, checkiter, checkconvergence, convergenceiter) 109 for iter in range(maxiter): --> 110 try: g = f(x, *args) 111 except: # pragma: no cover

File D:\anaconda\envs\zddx\lib\site-packages\thermosteam\equilibrium\vle.py:36, in xV_iter(xV, pcf_Psat_over_P_phi, T, P, z, z_light, z_heavy, f_gamma, gamma_args) 35 Ks = pcf_Psat_over_P_phi f_gamma(x, T, gamma_args) ---> 36 V = binary.solve_phase_fraction_Rashford_Rice(z, Ks, V, z_light, z_heavy) 37 if V < 0.: V = 0.

File D:\anaconda\envs\zddx\lib\site-packages\thermosteam\equilibrium\binary_phase_fraction.py:76, in solve_phase_fraction_Rashford_Rice(zs, Ks, guess, za, zb) 75 if y1 < y0 < 0.: return 0. ---> 76 x0, x1, y0, y1 = flx.find_bracket(f, x0, x1, y0, y1, args, tol=5e-8) 77 if abs(x1 - x0) < 1e-6: return (x0 + x1) / 2.

File ~\AppData\Roaming\Python\Python39\site-packages\flexsolve\bounded_solvers.py:72, in find_bracket(f, x0, x1, y0, y1, args, maxiter, tol) 71 y0 = y ---> 72 raise RuntimeError('failed to find bracket')

RuntimeError: failed to find bracket

During handling of the above exception, another exception occurred:

RuntimeError Traceback (most recent call last) Cell In [4], line 177 174 R201 = PretreatmentReactorSystem('R201', cornstover) 176 A_sys = bst.main_flowsheet.create_system('A_sys') --> 177 A_sys.simulate() 178 A_sys.diagram('thorough', format='png')

File D:\anaconda\envs\zddx\lib\site-packages\biosteam_system.py:2200, in System.simulate(self, update_configuration, **kwargs) 2198 elif self._facility_loop: 2199 self._facility_loop.converge() -> 2200 return outputs

File D:\anaconda\envs\zddx\lib\site-packages\biosteam_flowsheet.py:36, in TemporaryFlowsheet.exit(self, type, exception, traceback) 34 def exit(self, type, exception, traceback): 35 main_flowsheet.set_flowsheet(self.original) ---> 36 if exception: raise exception

File D:\anaconda\envs\zddx\lib\site-packages\biosteam_system.py:2192, in System.simulate(self, update_configuration, kwargs) 2190 outputs = self.simulate(update_configuration=True, kwargs) 2191 else: -> 2192 raise error 2193 else: 2194 if (not update_configuration # Avoid infinite loop 2195 and self._connections != [i.get_connection() for i in self.streams]): 2196 # Connections has been updated within simulation.

File D:\anaconda\envs\zddx\lib\site-packages\biosteam_system.py:2183, in System.simulate(self, update_configuration, kwargs) 2181 else: 2182 try: -> 2183 outputs = self.converge(kwargs) 2184 self._summary() 2185 except Exception as error:

File D:\anaconda\envs\zddx\lib\site-packages\biosteam_system.py:1904, in System.converge(self, material_data, update_material_data) 1902 for i in range(self._N_runs): method() 1903 else: -> 1904 method() 1905 if update_material_data: 1906 if reset_material_data:

File D:\anaconda\envs\zddx\lib\site-packages\biosteam_system.py:1803, in System.run(self) 1801 f = try_method_with_object_stamp 1802 for i in self._path: -> 1803 if isa(i, Unit): f(i, i.run) 1804 elif isa(i, System): f(i, i.converge) 1805 else: i()

File D:\anaconda\envs\zddx\lib\site-packages\thermosteam\exceptions.py:90, in try_method_with_object_stamp(object, method, args) 88 raise StampedKeyError(message_with_object_stamp(object, repr(error.args[0]))) 89 except Exception as error: ---> 90 raise_error_with_object_stamp(object, error)

File D:\anaconda\envs\zddx\lib\site-packages\thermosteam\exceptions.py:80, in raise_error_with_object_stamp(object, error) 78 error.args = (message_with_object_stamp(object, msg), *args) 79 except: pass ---> 80 raise error

File D:\anaconda\envs\zddx\lib\site-packages\thermosteam\exceptions.py:84, in try_method_with_object_stamp(object, method, args) 82 def try_method_with_object_stamp(object, method, args=()): 83 try: ---> 84 return method(*args) 85 except StampedKeyError as error: 86 raise StampedKeyError(message_with_object_stamp(object, error.args[0]))

File D:\anaconda\envs\zddx\lib\site-packages\biosteam_unit.py:1258, in Unit.run(self) 1256 if self.run_after_specifications: self._run() 1257 else: -> 1258 self._run()

Cell In [4], line 139, in PretreatmentReactorSystem._run(self) 137 ms = self._multistream 138 ms.copy_like(liquid) --> 139 ms.vle(T=self.T, V=self.V) 140 vapor.mol[:] = ms.imol['g'] 141 liquid.mol[:] = ms.imol['l']

File D:\anaconda\envs\zddx\lib\site-packages\thermosteam\equilibrium\vle.py:363, in VLE.call(self, T, P, V, H, S, x, y) 361 elif V_spec: 362 try: --> 363 self.set_TV(T, V) 364 except NoEquilibrium: 365 thermal_condition = self._thermal_condition

File D:\anaconda\envs\zddx\lib\site-packages\thermosteam\equilibrium\vle.py:741, in VLE.set_TV(self, T, V) 738 if self._F_mol_light: P_bubble = self._bubble_point.Pmax 739 if self._F_mol_heavy: P_dew = self._bubble_point.Pmin --> 741 V_bubble = self._V_err_at_P(P_bubble, 0.) 742 if V_bubble > V: 743 F_mol_vapor = self._F_mol * V

File D:\anaconda\envs\zddx\lib\site-packages\thermosteam\equilibrium\vle.py:1193, in VLE._V_err_at_P(self, P, V) 1192 def _V_err_at_P(self, P, V): -> 1193 return self._solve_v(self._T , P).sum()/self._F_mol_vle - V

File D:\anaconda\envs\zddx\lib\site-packages\thermosteam\equilibrium\vle.py:1255, in VLE._solve_v(self, T, P) 1253 self._T = T 1254 if isinstance(self._phi, IdealFugacityCoefficients): -> 1255 y = self._y_iter(self._y, Psats, Psats_over_P, T, P) 1256 else: 1257 y = flx.aitken(self._y_iter, self._y, self.y_tol, 1258 args=(Psats, Psats_over_P, T, P), 1259 checkiter=False, 1260 checkconvergence=False, 1261 convergenceiter=5, 1262 maxiter=self.maxiter)

File D:\anaconda\envs\zddx\lib\site-packages\thermosteam\equilibrium\vle.py:1218, in VLE._y_iter(self, y, Psats, Psats_over_P, T, P) 1216 xV[:-1] = x 1217 xV[-1] = self._V -> 1218 xV = flx.aitken(f, xV, self.x_tol, args, checkiter=False, 1219 checkconvergence=False, convergenceiter=5, 1220 maxiter=self.maxiter) 1221 x = xV[:-1] 1222 self._V = V = xV[-1]

File ~\AppData\Roaming\Python\Python39\site-packages\flexsolve\iterative_solvers.py:113, in aitken(f, x, xtol, args, maxiter, checkiter, checkconvergence, convergenceiter) 111 except: # pragma: no cover 112 x = gg --> 113 g = f(x, *args) 114 dxg = x - g 115 e = np.abs(dxg)

File D:\anaconda\envs\zddx\lib\site-packages\thermosteam\equilibrium\vle.py:36, in xV_iter(xV, pcf_Psat_over_P_phi, T, P, z, z_light, z_heavy, f_gamma, gamma_args) 34 x = fn.normalize(x) 35 Ks = pcf_Psat_over_P_phi f_gamma(x, T, gamma_args) ---> 36 V = binary.solve_phase_fraction_Rashford_Rice(z, Ks, V, z_light, z_heavy) 37 if V < 0.: V = 0. 38 elif V > 1.: V = 1.

File D:\anaconda\envs\zddx\lib\site-packages\thermosteam\equilibrium\binary_phase_fraction.py:76, in solve_phase_fraction_Rashford_Rice(zs, Ks, guess, za, zb) 74 if y0 < y1 < 0.: return 1. 75 if y1 < y0 < 0.: return 0. ---> 76 x0, x1, y0, y1 = flx.find_bracket(f, x0, x1, y0, y1, args, tol=5e-8) 77 if abs(x1 - x0) < 1e-6: return (x0 + x1) / 2. 78 return flx.IQ_interpolation(f, x0, x1, y0, y1, 79 guess, 1e-16, 1e-16, 80 args, checkiter=False)

File ~\AppData\Roaming\Python\Python39\site-packages\flexsolve\bounded_solvers.py:72, in find_bracket(f, x0, x1, y0, y1, args, maxiter, tol) 70 x0 = x 71 y0 = y ---> 72 raise RuntimeError('failed to find bracket')

RuntimeError: failed to find bracket

yoelcortes commented 1 year ago

@zasddsgg, BioSTEAM cannot yet do VLE of supercritical components (Tc of O2 is 154 K and Tc of N2 is 126 K). The easiest way to resolve the issue is remove these components from the stream. Because the vapor pressure of these components are extrapolated past the critical temperature, the required pressure to maintain it in the liquid mixture becomes infeasibly high.

zasddsgg commented 1 year ago

Thank you for your suggestion. I removed O2, N2, CO2 and SO2 from the components, and the error disappeared. Should I remove O2, N2, CO2 and SO2 from the components in the other units when it comes to the calculation of VLE? With the error gone, I ran ParallelRxn one reaction at a time to see the Hnet for each reaction, and compared the heat load for the same reaction under the same conditions with Aspen, which uses the model in the corn stover 2011 report. The results seem to differ greatly. BioSTEAM and Aspen maintain the same feed conditions, reaction temperature and V, but each reaction heat duty seems to differ greatly, so the calculated unit heat load is also different from Aspen's. May I consult you why this happens. Thanks for your help. Wish you a good day.

The heat load of each reaction is as follows.

Glucan + H2O -> GlucoseOligomer, 0.0030(Conversion Rate), 133711.81 kJ/h (BioSTEAM),9012.7 kJ/h (Aspen) Glucan + H2O -> Glucose, 0.0990(Conversion Rate), -856444.97 kJ/h (BioSTEAM), 269692 kJ/h (Aspen) Glucan -> HMF + 2 H2O, 0.0030(Conversion Rate), 204822.58 kJ/h (BioSTEAM), 10991.1 kJ/h (Aspen) Xylan + H2O -> XyloseOligomer, 0.0240(Conversion Rate), 750492.76 kJ/h (BioSTEAM), -18044.8 kJ/h (Aspen) Xylan + H2O -> Xylose, 0.9000(Conversion Rate), -3106372.60680151 kJ/h (BioSTEAM), -1050000 kJ/h (Aspen) Xylan -> Furfural + 2 H2O, 0.0500(Conversion Rate), -179203.97 kJ/h (BioSTEAM), 67993.4 kJ/h (Aspen) Mannan + H2O -> MannoseOligomer, 0.0240(Conversion Rate), 18311.48 kJ/h (BioSTEAM),1234.26 kJ/h (Aspen) Mannan + H2O -> Mannose, 0.9000(Conversion Rate), -133281.67 kJ/h (BioSTEAM),41969.1 kJ/h (Aspen) Mannan -> HMF + 2 H2O, 0.0500(Conversion Rate), 58437.31 kJ/h (BioSTEAM),3136.34 kJ/h (Aspen) Galactan + H2O -> GalactoseOligomer, 0.0240(Conversion Rate), 43642.08 kJ/h (BioSTEAM), 2941.65 kJ/h(Aspen) Galactan + H2O -> Galactose, 0.9000(Conversion Rate), -317652.62 kJ/h(BioSTEAM), 100026 kJ/h(Aspen) Galactan -> HMF + 2 H2O, 0.0500(Conversion Rate), 139274.71 kJ/h (BioSTEAM), 7474.23 kJ/h(Aspen) Arabinan + H2O -> ArabinoseOligomer, 0.0240(Conversion Rate), 91473.02 kJ/h (BioSTEAM), -21996.4 kJ/h(Aspen) Arabinan + H2O -> Arabinose, 0.9000(Conversion Rate), -378616.95 kJ/h (BioSTEAM), -109841 kJ/h(Aspen) Arabinan -> Furfural + 2 H2O, 0.0500(Conversion Rate), -21842.09 kJ/h (BioSTEAM),8321.36 kJ/h(Aspen) Acetate -> AceticAcid, 1.0000(Conversion Rate), -9548487.13 kJ/h (BioSTEAM), -97979.6 kJ/h(Aspen) Furfural + 3 H2O -> Tar, 1.0000(Conversion Rate), 4158.03 kJ/h (BioSTEAM),1040000 kJ/h(Aspen) HMF + 3 H2O -> 1.2 Tar, 1.0000(Conversion Rate), -206031.70 kJ/h (BioSTEAM),168250 kJ/h(Aspen) Lignin -> SolubleLignin, 0.0500(Conversion Rate), 0.0 kJ/h (BioSTEAM), 13272.5 kJ/h(Aspen) Sucrose -> HMF + Glucose + 2H2O, 1.0000(Conversion Rate), 727180.77 kJ/h (BioSTEAM), -397445 kJ/h(Aspen)

yoelcortes commented 1 year ago

@zasddsgg, you don't need to remove O2, N2, CO2, SO2 if your vapor fraction is greater than the molar fraction of supercritical gases.

Reaction enthalpies and energy balances are rigorously tested in BioSTEAM against textbook references and other software. I do not know how you are generating those values (both in BioSTEAM and Aspen). Can you share the code and a manual calculation of what you expect the energy balance should be (for just one example)? Giving values from Aspen will not suffice because I cannot see how are your Aspen values generated.

Thanks

yoelcortes commented 1 year ago

@zasddsgg, I had a quick look at Hf values in cornstover chemicals and the value for acetic acid was off (the value set previously was in cal/mol, but it should be in J/mol). This is now fixed. I believe the other components should be OK too, but let me know if you find anything that is off.

zasddsgg commented 1 year ago

Thank you for your help. I used one of these reactions to do a manual energy balance calculation. Xylan -> Furfural + 2 H2O

Manual calculation: DHFORM (25 degree) is abbreviated as H, DHSFRM is abbreviated as DH, Xylan is abbreviated as a, Furfural is abbreviated as b, H2O is abbreviated as c,CPIGDP is abbreviated as CP, CPSPO is abbreviated as CPS, the reaction temperature is 100.758 degrees, heat calculation is calculated by the following formula: 2(Hc+CPc(100.758-25))+ Hb+ CPb(100.758-25)- DHa-CPSa(100.758-25) Among them, Hc was -241759236.7J /Kmol, CPc was 34034.45796 J/KmolK, Hb was -150965803.2J /Kmol, CPb was 118668.2694 J/KmolK, DHa was -762243323.6 J/Kmol and CPSa was 195091.253 J/KmolK. It can be obtained that the reaction heat is 127126.1595 kJ/kmol, and the Xylan converted by the reaction is 6.1609 kmol/h (0.05123.218), so the corresponding heat is 127126.1595*6.1609=783211 kJ/ h. The Hnet calculated by BioSTEAM is -179203.97kJ /h. The two values seem to be quite different. May I trouble you help me see where I'm going wrong? Thanks for your help. Wish you a good day.

The BioSTEAM code is as follows: The action I took was to react one reaction at a time to see the Hnet for that reaction.

import biosteam as bst bst.nbtutorial() from biosteam.units.decorators import cost from biosteam import Unit from biosteam import units from biosteam.units.design_tools.geometry import cylinder_diameter_from_volume

from thermosteam import MultiStream import thermosteam as tmo

from biorefineries import cellulosic cs = cellulosic.Biorefinery('corn stover ethanol') chemicals_cs = cs.chemicals bst.settings.set_thermo(chemicals_cs) bst.main_flowsheet.set_flowsheet('corn stover_ethanol')

cornstover = bst.Stream( 'cornstover', Water=8376.35530314805, Ethanol=0.777817467000001, Glucose=0.138222603, Galactose=0.204447216, Mannose=0.0876245290000003, Xylose=2.32770801, Arabinose=0.743299376000001, Cellobiose=0.310691989000001, Sucrose=1.87457352235111, GlucoseOligomer=3.38385322, GalactoseOligomer=0.037254861, MannoseOligomer=0.015967151, XyloseOligomer=0.816486943000001, ArabinoseOligomer=0.137168892, Extract=143.655450486199, SolubleLignin=1.45892539, HMF=0.504619411, Furfural=3.86516298, AceticAcid=34.7794066000001, LacticAcid=8.97227473, Xylitol=3.15299514000001, Glycerol=1.04685476, SuccinicAcid=2.38173359000001, NH3=6.64670000000001E-06, H2SO4=19.9625986528278, Oil=0.0682150190000001, Protein=133.6292384, Glucan=180.173144058494, Galactan=7.3508188902784, Mannan=3.08427932896995, Xylan=123.218046720306, Arabinan=15.018301874109, Lignin=86.713967821333, Acetate=25.1139172462299, Ash=73.9073961631699, Z_mobilis=8.50261289000001, Biomass=5.66877035, Tar=0.042694621, P=9*101325, T=100.758+273.15, units='kmol/hr', price=0.05158816935126135, )

@cost('Dry flow rate', 'Pretreatment reactor system', units='kg/hr', S=83333, CE=522, cost=19812400 * 0.993, n=0.6, kW=4578, BM=1.5) class PretreatmentReactorSystem(bst.units.design_tools.PressureVessel, Unit): _N_ins = 1 _N_outs = 2 _graphics = bst.Flash._graphics _units = {'Residence time': 'hr', 'Reactor volume': 'm3'}

def init(self, ID='', ins=None, outs=(), T=100.758+273.15, V=0, thermo=None, tau=0.166, V_wf=0.8, length_to_diameter=2, vessel_material='Stainless steel 316', vessel_type='Horizontal', reactions=None, run_vle=True): Unit.init(self, ID, ins, outs, thermo) self._load_components() vapor, liquid = self.outs vapor.phase = 'g' self.T = T self.V = V chemicals = self.chemicals if reactions is None: self.reactions = ParallelRxn([

Reaction definition Reactant Conversion

Rxn('Xylan -> Furfural + 2 H2O',                 'Xylan',    0.0500, chemicals),
# Rxn('Glucan + H2O -> GlucoseOligomer',           'Glucan',   0.0030, chemicals),
# Rxn('Glucan + H2O -> Glucose',                   'Glucan',   0.0990, chemicals),
# Rxn('Glucan -> HMF + 2 H2O',                     'Glucan',   0.0030, chemicals),
# Rxn('Xylan + H2O -> XyloseOligomer',             'Xylan',    0.0240, chemicals),
# Rxn('Xylan + H2O -> Xylose',                     'Xylan',    0.9000, chemicals), 
# Rxn('Mannan + H2O -> MannoseOligomer',           'Mannan',   0.0240, chemicals),
# Rxn('Mannan + H2O -> Mannose',                   'Mannan',   0.9000, chemicals),
# Rxn('Mannan -> HMF + 2 H2O',                     'Mannan',   0.0500, chemicals),
# Rxn('Galactan + H2O -> GalactoseOligomer',       'Galactan', 0.0240, chemicals),        
# Rxn('Galactan + H2O -> Galactose',               'Galactan', 0.9000, chemicals),   
# Rxn('Galactan -> HMF + 2 H2O',                   'Galactan', 0.0500, chemicals),        
# Rxn('Arabinan + H2O -> ArabinoseOligomer',       'Arabinan', 0.0240, chemicals),
# Rxn('Arabinan + H2O -> Arabinose',               'Arabinan', 0.9000, chemicals),
# Rxn('Arabinan -> Furfural + 2 H2O',              'Arabinan', 0.0500, chemicals),
# Rxn('Acetate -> AceticAcid',                     'Acetate',  1.0000, chemicals),        
# Rxn('Furfural + 3 H2O  -> Tar',                  'Furfural',  1.0000, chemicals),
# Rxn('HMF + 3 H2O  -> 1.2 Tar',                   'HMF',       1.0000, chemicals),
# Rxn('Lignin -> SolubleLignin',                   'Lignin',    0.0500, chemicals),        
# Rxn('Sucrose -> HMF + Glucose + 2H2O',           'Sucrose',   1.0000, chemicals)
    ])
    # self.glucan_to_glucose = self.reactions[0]
     #self.xylan_to_xylose = self.reactions[8]
     # self.glucose_to_byproducts = self.reactions[1:3]
    # self.xylose_to_byproducts = self.reactions[9:12]
else:
    self.reactions = reactions
self.tau = tau
self.V_wf = V_wf
self.length_to_diameter = length_to_diameter
self.vessel_material = vessel_material
self.vessel_type = vessel_type
self.run_vle = run_vle

def _load_components(self): thermo = self.thermo self._multistream = MultiStream(None, thermo=thermo)

def _run(self): feed = self.ins[0] vapor, liquid = self.outs liquid.copy_like(feed) self.reactions.adiabatic_reaction(liquid) if self.T: if self.run_vle: ms = self._multistream ms.copy_like(liquid) ms.vle(T=self.T, V=self.V) vapor.mol[:] = ms.imol['g'] liquid.mol[:] = ms.imol['l'] vapor.T = liquid.T = ms.T vapor.P = liquid.P = ms.P else: liquid.T = self.T

def _design(self): Design = self.design_results ins_F_vol = self.F_vol_in V_reactor = ins_F_vol self.tau / self.V_wf P = self.outs[0].P 0.000145038 # Pa to psi length_to_diameter = self.length_to_diameter D = cylinder_diameter_from_volume(V_reactor, self.length_to_diameter) D = 3.28084 # convert from m to ft L = D length_to_diameter Design['Residence time'] = self.tau Design['Reactor volume'] = V_reactor Design.update(self._vessel_design(float(P), float(D), float(L))) self._decorated_design() Design = self.design_results

def _cost(self): Design = self.design_results self.baseline_purchase_costs.update( self._vessel_purchase_cost( Design['Weight'], Design['Diameter'], Design['Length'] ) ) self._decorated_cost() Rxn = tmo.reaction.Reaction ParallelRxn = tmo.reaction.ParallelReaction

R201 = PretreatmentReactorSystem('R201', cornstover)

A_sys = bst.main_flowsheet.create_system('A_sys') A_sys.simulate() A_sys.diagram('thorough', format='png') R201.Hnet

yoelcortes commented 1 year ago

@zasddsgg, thanks! This clears things up and I know what is the discrepancy: you have some wrong values for both the heat of formation and heat capacities. I suspect you are using values for gas (it should be liquid). However, the heat capacity of Xylan being used in the cornstover model right now is too high and should be lower (I am open to update this). Please verify your values online. Here is the detailed calculation for Xylan -> Furfural + 2 H2O. Note that it matches BioSTEAM's result.

>>> from biorefineries import cellulosic as cs
>>> chemicals = cs.create_cellulosic_ethanol_chemicals()
>>> W, F, X = chemicals = chemicals['Water', 'Furfural', 'Xylan']
>>> T = 373.91
>>> P = 101325
>>> Tref = 298.15
>>> for i in chemicals:
...    print(f"Hf_{i.ID} = {i.Hf} J/mol")
...    assert i.phase_ref == 'l'
...    H_liq = (i.H.l if hasattr(i.H, 'l') else i.H)(T, P)
...    print(f"Cp_average_{i.ID} = {H_liq / (T - Tref)} J/mol/K")
>>> Hrxn = 2 * (W.Hf + W.H('l', T, P)) + F.Hf + F.H('l', T, P) - X.Hf - X.H(T, P)
>>> print(f"Hrxn = {Hrxn} J/mol")
>>> print(f"Hnet = {6.16 * Hrxn} kJ/hr") # 6.16 kmol/hr of Xylan reacted
Hf_Water = -285825.0 J/mol
Cp_average_Water = 75.50243498981328 J/mol/K
Hf_Furfural = -201600.0 J/mol
Cp_average_Furfural = 167.0190532693366 J/mol/K
Hf_Xylan = -761906.4 J/mol
Cp_average_Xylan = 552.2391116 J/mol/K
Hrxn = -29087.74266947461 J/mol
Hnet = -179180.4948439636 kJ/hr

Thanks!

zasddsgg commented 1 year ago

Thank you for your reminding and help. Hnet seems to use 2 (Hf_Water + Cp_average_Water(T-Tref)) + Hf_Furfural + Cp_average_Furfural*(T-Tref) -hf_xylan - Cp_average_Xylan and the amount of Xylan transformed to calculate. May I confirm with you whether my understanding is correct?

I am very sorry that I still have the following doubts. May I consult you for the following doubts. Thank you for your help.

a) Does the T above refer to the operating temperature of the reactor (the temperature of the outlet stream)? If so, the Hnet does not seem to be related to the feed temperature because T refers to the operating temperature of the reactor, but different feed temperatures seem to have an effect on the Hnet as well?

b) Also, is Hnet calculated in the above way same as self.H_out-self.H_in + self.Hf_out-self.Hf_in? Self.H_out - self.H_in + self.Hf_out - self.Hf_in seems to be dependent on feed temperature, since there is self.H_in and self.Hf_in.

c) In addition, does the Hnet calculated by the above two methods refer to the HeatUtility.unit_duty?

d) It seemed that the heat utility was not added in the class pretreatment reactor for the corn stover (https://github.com/BioSTEAMDevelopmentGroup/Bioindustrial-Park/blob/master/biorefineries/cellulosic/units.py). Why was that? I tried to make the reactor temperature the same as the feed stream temperature, set the vapor phase fraction to 0, and found that the pretreatment reactor was exothermic. If don't add the heat utility, isn't the released heat wasted?

e) Last, why was there steam in the Pretreatment system(https://github.com/BioSTEAMDevelopmentGroup/Bioindustrial-Park/blob/master/biorefineries/cellulosic/systems/pretreatment/dilute_acid.py)? Since in the pretreatment reactor, if the reactor temperature is the same as the feed stream temperature, and the gas phase fraction is set to be 0, the reactor load (Hnet) is negative, which means the reactor is exothermic. So why steam was added to provide heat?

I'm very sorry to have so many questions. Thank you for your help. Wish you a good day.

yoelcortes commented 1 year ago

@zasddsgg, sure thing:

Yes, the equation I used to calculate Hnet is the same equation you used. The only difference is I made sure to use the average heat capacity across temperature. I also think you meant to say:

2 (Hf_Water + Cp_average_Water(T-Tref)) + Hf_Furfural + Cp_average_Furfural*(T-Tref) -hf_xylan - Cp_average_Xylan *(T-Tref) times the amount of Xylan transformed

a) Your inlet stream temperature is the same as the outlet, that is why the equation I used gives the same answer as Hnet. Again, Hnet works for getting the enthalpy difference (including heats of formation) for all cases you can think of.

b) That is correct.

c) Unit operations may have multiple heat utilities. If only one heat utility is present, HeatUtility.unit_duty should be equal to Unit.Hnet (unless there is some sort of heat integration).

d) The pretreatment reactor in the Humbird model is adiabatic. You should add the heat utility because your's is not adiabatic.

e) Steam may be formed in the orginal model (which was adiabatic), especially if there is a pressure drop. Please do not confuse your changes with the original model.

Thanks

zasddsgg commented 1 year ago

Thank you for your help. May I consult you for the following doubts again. Thank you for your help. a) If the temperature of the inlet stream is different from that of the outlet stream, can the equation 2 (Hf_Water + Cp_average_Water(T-Tref)) + Hf_Furfural + Cp_average_Furfural(T-Tref) -hf_xylan - Cp_average_Xylan (t-tref) times the amount of Xylan transformed also be used to calculate Hnet? Because T in the above formula seems to refer to the temperature of the outflow stream, so the calculation of Hnet with above formula seems to have nothing to do with the temperature of the inlet stream? But should the Hnet be related to the temperature of the inlet stream?

b) Do I need to artificially set the average heat capacity across temperature when I use the above formula. Or will BioSTEAM automatically calculate the average heat capacity across temperature based on the T I input, and all I need to do is only input specific reaction to get the Hnet of the reactor?

c) Can Self.H_out - self.H_in + self.Hf_out - self.Hf be used to compute Hnet all the time? But 2 (Hf_Water + Cp_average_Water(T-Tref)) + Hf_Furfural + Cp_average_Furfural(T-Tref) -hf_xylan - Cp_average_Xylan (T-Tref) times the amount of Xylan transformed can be used to calculate Hnet only when the inlet stream temperature is the same as the outlet stream temperature?

d) Corn stover code (https://github.com/BioSTEAMDevelopmentGroup/Bioindustrial-Park/blob/master/biorefineries/cellulosic/units.py), sets self.reactions.adiabatic_reaction(liquid), indicates that the pretreatment reactor in the Humbird model is adiabatic. But I found through the following code that R201.Hnet is -49902254.8 kJ/h, does it mean exothermic heat? Why is that? If self.reactions.adiabatic_reaction(liquid) was set, shouldn't R201.Hnet be zero? Code I used to view the R201.Hnet of corn stover ethanol model is as followings: from biorefineries import cellulosic import biosteam as bst cs = cellulosic.Biorefinery('corn stover ethanol') cs.R201.Hnet

e) Through the code cs.R201, I found that the inlet pressure of R201 in corn stover model was 557288 Pa, the outlet pressure is 272394 Pa and there is pressure drop (https://github.com/BioSTEAMDevelopmentGroup/Bioindustrial-Park/blob/master/biorefineries/cellulosic/systems/pretreatment/dilute_acid.py). The gas flow in the R201 outlet stream is zero. Under adiabatic conditions, and there is also a pressure drop, but why is there no component in the gas phase of the R201 outlet stream?

f) In the corn stover model, there appeared to be a pretreatment steam stream for heat before R201 (https://github.com/BioSTEAMDevelopmentGroup/Bioindustrial-Park/blob/master/biorefineries/cellulosic/systems/pretreatmen t/dilute_acid.py), this seems to show that R201 is endothermic and needs to provide heat, but the Hnet of R201 is -49902254.8kJ /h, indicating that R201 is exothermic and does not seem to need to provide heat. I am a little confused about this.

I'm very sorry to have so many questions again. Thank you for your help. Wish you a good day.

yoelcortes commented 1 year ago

@zasddsgg, I am always happy to answer questions.

a) You are forgetting that all components are changing temperature and that change in enthalpy should be accounted for. Hnet is the duty without heat transfer losses. I recommend reviewing literature related to free energies.

b) BioSTEAM integrates the heat capacity across temperature for sensible energies.

c) Same as a).

d) There was a bug when solving VLE given T and H with significant amounts of solutes (such as glucose). This has been solved in thermosteam. You can use the github version or wait until next month's thermosteam release.

>>> cs.R201.Hnet / (cs.R201.ins[0].H + cs.R201.ins[0].Hf) # Return value should be near zero
3.1106505362759276e-08

e) There is now some gas in the outlet.

f) Steam is needed to reach reaction conditions. Please review papers on dilute acid pretreatment of biomass for more details.

Thanks

zasddsgg commented 1 year ago

Thank you for your advice and help. I have installed the thermosteam of github version and checked the Hnet of R201 in corn stover ethanol, which is 285.38kJ /h, which seems not to be 0, but with the adiabatic setting in R201, shouldn't Hnet be zero?

For the chemicals not contained in corn stover ethanol model, but retrieved by bst.Chemical, can it be used directly when calculating Hnet, and is there any modification required for the chemicals retrieved by bst.Chemical when calculating Hnet?

Finally, I understand your equation for calculating Hnet. But in Aspen model for the corn stover process, published by Humbird et al., I maintains the same feed conditions and reactor setup as BioSTEAM's R201, for Xylan -> Furfural + 2 H2O, the unit (R201) heat duty obtained by Aspen was 67,993.4 kJ/h (I only modified the feed conditions in the model published by Humbird et al to be consistent with BioSTEAM, and the physical properties were all set by Humbird et al). The Hnet calculated by BioSTEAM is -179203.97kJ /h. I believe that the models published by BioSTEAM and Humbird are all right, but I can't find the reason for such a big difference in unit heat load, which has been bothering me for several days. Could I send you the Aspen file and bother you help to see why the unit load is so different? Thank you for your help. Wish you a good day.

yoelcortes commented 1 year ago

@zasddsgg, the VLE solver stops within a defined tolerance and maximum number of iterations (https://biosteam.readthedocs.io/en/latest/tutorial/Convergence.html#Phase-equilibrium-and-energy-balance). 285.38kJ /h is actually quite close to zero if you normalize it (please see the code in answer d in my previous comment).

In general, it is good to make sure all properties are correct when using bst.Chemical. For common chemicals usually you don't need to make any modifications.

I am happy to give general support for BioSTEAM. For anything out of this scope, there would need to be a written consulting agreement. Please do not send me any Aspen files.

Thanks,

yoelcortes commented 1 year ago

@zasddsgg, I had a quick look at Hf values in cornstover chemicals and the value for acetic acid was off (the value set previously was in cal/mol, but it should be in J/mol). This is now fixed. I believe the other components should be OK too, but let me know if you find anything that is off.

Also, don't forget to use the master branch of the bioindustrial-park for the update above. Note that the original Hf value for acetic acid in BioSTEAM is correct and the value given previously in the cornstover model was not given in the correct units.

zasddsgg commented 1 year ago

Thank you for your help and reminding. I have checked the relevant materials, may I confirm with you whether my understanding is correct?

a) If I want to check the unit's Hnet, can I directly use unit.Hnet? BioSTEAM will automatically calculate Unit. Hnet without me setting anything.

b) Has BioSTEAM taken sensible heat and latent heat into account when calculating Hnet? Can BioSTEAM automatically calculate unit.Hnet, namely HeatUtility.unit_duty (if only one heat utility is present, HeatUtility.unit_duty should be equal to Unit.Hnet), no matter whether the feed temperature and outlet temperature are the same?

c) If I want to view HeatUtility.unit_duty, whether I create the units myself or call the existing units in BioSTEAM, can I directly use unit.Hnet to view the HeatUtility.unit_duty? And then use the following code to view the heat_utility flow, is it right? duty = self.H_out - self.H_in + self.Hf_out - self.Hf_in(reaction occurs) or duty = self.H_out - self.H_in (no reaction occurs) T_operation = self.outs[0].T (CSTRs and tanks outlet stream temperature) self.add_heat_utility(duty, T_operation)

d) For self.Hf_out - self.Hf_in, duty = self.H_out - self.H_in + self.Hf_out - self.Hf_in, is self.Hf_out - self.Hf_ins used to calculate the heat of reaction of chemical reactions? Does self.H_out refer to the enthalpy flow of the outlet stream and self.H_ins refer to the enthalpy flow of the inlet stream?

e) Is the enthalpy flow rate of a stream calculated as follows: For a liquid substance, the enthalpy of formation is obtained by multiplying the 25 degrees enthalpy of formation of the substance in the liquid state by the difference between the substance's temperature and the 25 degrees reference value. For a gaseous substance, the enthalpy of formation is obtained by multiplying the 25 degrees enthalpy of formation of the substance in the gas state by the difference between the substance's temperature and the 25 degrees reference value. And then multiply the enthalpy of each substance by the molar flow of each substance, and add it up, and that's the enthalpy flow of the stream?

f) Is the Hnet (HeatUtility.unit_duty) of the reactor obtained by reducing the enthalpy flow of the outlet stream by the enthalpy flow of the inlet stream and the heat of reaction of the chemical reaction: in other words, Hnet= self.H_out - self.H_in- heat of reaction of the chemical reaction

g) For ’’If only one heat utility is present, HeatUtility.unit_duty should be equal to Unit.Hnet (unless there is some sort of heat integration)’’, I'm a little confused about this sentence, if there is only one heat utility, shouldn't HeatUtility.unit_duty equal Unit.Hnet?

Thank you for your help. Wish you a good day.

yoelcortes commented 1 year ago

Sure thing,

a) Hnet is a property, so it is calculated when you access it.

b) Yes, everything is accounted for the enthalpy of a stream.

c) It all depends on what you are trying to do. I suggest you simply try whatever you are thinking.

d) Please see the definition and code of Unit.H_out and Unit.H_in: https://biosteam.readthedocs.io/en/latest/API/Unit.html#biosteam.Unit.H_in

f) It all depends on your reference phase. I cannot help you with explaining detailed chemical engineering calculations (please refer to literature instead), but the molar enthalpy (without heats of formation) is computed by functions here: https://github.com/BioSTEAMDevelopmentGroup/thermosteam/blob/master/thermosteam/free_energy.py. The heat of formation is at the reference phase, temperature, and pressure.

g) Heat integration refers to things like heat exchanger networks.

I hope this helps!

zasddsgg commented 1 year ago

Thank you for your help. I got it. Could I consult you the other questions as followings:

a) There is steam stream before R201 in corn stover model and R201.was set to be adiabatic. So may I consult you how to get flow, temperature and pressure of steam stream in specific process for pretreatment.

b) If inherit from the PressureVessel, Unit to creat new class (like class PretreatmentReactorSystem), can Hnet be called directly to view the unit.Hnet ?

c) Do I need to set the chemical added through bst.Chemicals to be solid state? If so, will BioSTEAM automatically select the corresponding formula to calculate the enthalpy based on the phase state of the chemical just like this site (https://github.com/BioSTEAMDevelopmentGroup/thermosteam/blob/master/thermosteam/free_energy.py)?

d) For supercritical components, Aspen defines them as Henry components. How does BioSTEAM handle supercritical components? Does BioSTEAM either remove the supercritical component or set the vapor pressure model (.Psat) as a constant to simulate a Henry component ?

e) For “you don't need to remove O2, N2, CO2, SO2 if your vapor fraction is greater than the molar fraction of supercritical gases”, does this mean that as long as there is no RuntimeError: failed to find bracket being reported, I don't have to remove the components or set the vapor pressure model (.Psat) as a constant to simulate a Henry component? Is it OK to treat them in terms of something like H2O if there is no RuntimeError: failed to find bracket being reported?

Thank you for your help. Wish you a good day.

yoelcortes commented 1 year ago

Absolutely, here are the answers:

a) Heat utility objects (in Unit.heat_utilities) have attributes like inlet_utility_stream and outlet_utility_stream (https://biosteam.readthedocs.io/en/latest/API/HeatUtility.html).

b) All inherited methods and properties should work.

c) Setting the chemical state is just for convenience when that chemical never changes phase in the process. When Chemical phase (state) is set, the reference phase becomes that phase as well.

d) BioSTEAM extrapolates Psat by default, but this can be changed to a constant (like Henry component) too: https://thermo.readthedocs.io/property_objects.html#limits-and-extrapolation

e) No it does not. It may not thrown an error and you'll need to explore on your own if the results are acceptable when it doesn't throw an error. H2O is extremely differnt to O2, I would not recommend switching it.

Thanks,

zasddsgg commented 1 year ago

Thank you for your help. For questions a) above, in the corn stover model, the unit M203 (steam mixer) has an inlet steam stream, accdording to the paper (BioSTEAM: A Fast and Flexible Platform for the Design, Simulation,and Techno-Economic Analysis of Biorefineries under Uncertainty), the steamand loaded into pretreatment reactors is used to provide high pressures and temperatures to break down the biomass and facilitate enzymatic hydrolysis (Area 200). Since R201 was adiabatic (Hnet near to zero), it seems infeasible to determine the amount of steam used based on Hnet, so may I consult you how to get flow, temperature and pressure of steam stream as inlet stream of M203.

Besides, could I consult the following things about chemicals phase:

b) For some solid chemicals, like sodium chloride, glucose, it might dissolve into a dissolved state, for some liquid chemicals, it might change into corresponding gaseous state, like ethanol, so it may have a solid and a dissolved state for the same substance, or a liquid and a gaseous state for the same substance, when calculate the enthalpy of the substance, will BioSTEAM automatically select the corresponding formula to calculate the corresponding enthalpy value according to the phase state of the substance? Because the enthalpy of the code (https://github.com/BioSTEAMDevelopmentGroup/thermosteam/blob/master/thermosteam/free_energy.py) shows that the formula for calculating enthalpy seems to be different for the chemicals of different phases.

c) For substances whose phase states do not change in the process, they seem to be set as liquid by default. For example, Glucan, Xylan and other substances, the reference phase states also seem to be liquid. In this way, when calculating their enthalpy values, is Liquid_Enthalpy_Ref_Liquid used to calculate their enthalpy values? (https://github.com/BioSTEAMDevelopmentGroup/thermosteam/blob/master/thermosteam/free_energy.py) Shouldn't Solid_Enthalpy_Ref_Solid be used to calculate enthalpy of solid state chemicals?

d) When building up my own process, can I import corn stover ethanol model’s chemicals, so that the materials in it do not need to be set up by myself. For the chemicals not in it but used in my own process, can I add with bst.Chemicals, and finally set all chemicals (together with the chemicals in corn stover ethanol model) in bst.settings.set_thermo. Is this OK?

e) For substances added with bst.Chemicals, what is the default phase state? For example, I added sodium chloride with bst.Chemicals, which seems to be set to solid by default, but methanol seems to be set to liquid?

f) For append_new_single_phase_chemical('NO2', chems.N2, formula='NO2',), what does chems.N2 mean? Why not define NO2 like the way as N2 (extend_single_phase_chemicals(['N2', 'O2', 'CH4', 'H2S', 'SO2'])) (https://github.com/BioSTEAMDevelopmentGroup/Bioindustrial-Park/blob/master/biorefineries/cellulosic/chemicals.py)?

g) Are there two ways to deal with supercritical components? One is to remove supercritical components from the stream and the other is to set Psat as constant. Are both ways OK?

Thank you for your help. Wish you a good day.

yoelcortes commented 1 year ago

Hi @zasddsgg,

a) The steam is solved for numerically using an energy balance in biosteam.mixer.SteamMixer (please find it in the documentation).

b) Heats of solution (which vary with composition) is neglected in the calculation of enthalpy. You'll need to include it on your own in the Unit operation (e.g., self.add_heat_utility(self.Hnet + dH_solution, T)). My understanding is that it is negligible and not worth the complexity.

c) The selection of Solid or Liquid does not matter because it is the same formula.

d) That is the expected functionality.

e) The reference phase is always the phase at 25 C at 1 atm.

f) It seems to be copying some properties from N2. Maybe NO2 was not in the database. It may be a good idea to double check. Regardless, I think it does not matter in the results since NO2 is only present in small quantities in combustion.

g) I cannot help you with supercritical components. Please explore on your own.

Thanks,

zasddsgg commented 1 year ago

Thank you for your help. May I ask you the other following two questions:

a) For the same substance, will the selection of different phase states and reference phase states affect the enthalpy of the substance under specified conditions. For example, the phase state and the reference phase state of substance A can choose any of the liquid, gas or solid state. Does the above choice have any effect on the enthalpy of A under certain pressure and temperature and the specific phase state of A (for example, A is liquid under certain conditions, but it may change into gas after the temperature changes, but the phase state and the reference phase state of A choose only liquid state)? If not, For the existing chemical substances in corn stover model and the chemical substances added by me through bst.Chemical, can their phase states and reference phase states be randomly selected? Will above selection influence the calculation of enthalpy values of the same substance in different phase states under specific conditions (only one phase state and reference phase state is selected)? b) For class PretreatmentReactorSystem, I set up a reaction inside XYLAN→Furfural + 2H2O. Is excess enthalpy (heat of mixing) not taken into account when calculating the enthalpy flow? Why is excess enthalpy (heat of mixing) not taken into account? Such as the following class PretreatmentReactorSystem code, it seems to has the same Hnet result as 2 (Hf_Water + Cp_average_Water(T-Tref)) + Hf_Furfural + Cp_average_Furfural(T-Tref) -hf_xylan-Cp_average_Xylan (T-Tref) times the amount of Xylan transformed, but if excess enthalpy (heat of mixing) is taken into account, the above results seem to be different. Because 2 (Hf_Water + Cp_average_Water(T-Tref)) + Hf_Furfural + Cp_average_Furfural(T-Tref) -hf_xylan - Cp_average_Xylan (T-Tref) times the amount of Xylan transformed is calculated as theoretical Hnet, i.e. Hnet when excess enthalpy (heat of mixing) is not taken into account.

Thank you for your help. Wish you a good day.

class PretreatmentReactorSystem code is as follows:

import biosteam as bst bst.nbtutorial() from biosteam.units.decorators import cost from biosteam import Unit from biosteam import units from biosteam.units.design_tools.geometry import cylinder_diameter_from_volume

from thermosteam import MultiStream import thermosteam as tmo

from biorefineries import cellulosic cs = cellulosic.Biorefinery('corn stover ethanol') chemicals_cs = cs.chemicals bst.settings.set_thermo(chemicals_cs) bst.main_flowsheet.set_flowsheet('corn stover_ethanol')

cornstover = bst.Stream( 'cornstover', Xylan=6.16, Water=1000, P=9*101325, T=100.758+273.15, )

@cost('Dry flow rate', 'Pretreatment reactor system', units='kg/hr', S=83333, CE=522, cost=19812400 * 0.993, n=0.6, kW=4578, BM=1.5) class PretreatmentReactorSystem(bst.units.design_tools.PressureVessel, Unit): _N_ins = 1
_N_outs = 2 _graphics = bst.Flash._graphics _units = {'Residence time': 'hr', 'Reactor volume': 'm3'}

def __init__(self, ID='', ins=None, outs=(), T=100.758+273.15, V=0, thermo=None, 
             tau=0.166, V_wf=0.8, length_to_diameter=2, 
             vessel_material='Stainless steel 316', 
             vessel_type='Horizontal',
             reactions=None,
             run_vle=True):
    Unit.__init__(self, ID, ins, outs, thermo)
    self._load_components()
    vapor, liquid = self.outs
    vapor.phase = 'g'
    self.T = T
    self.V = V
    chemicals = self.chemicals
    if reactions is None:
        self.reactions = ParallelRxn([
    #            Reaction definition                 Reactant    Conversion
    Rxn('Xylan -> Furfural + 2 H2O',                 'Xylan',    1, chemicals),
    # Rxn('Glucan + H2O -> GlucoseOligomer',           'Glucan',   0.0030, chemicals),
    # Rxn('Glucan + H2O -> Glucose',                   'Glucan',   0.0990, chemicals),
    # Rxn('Glucan -> HMF + 2 H2O',                     'Glucan',   0.0030, chemicals), 
    # Rxn('Xylan + H2O -> XyloseOligomer',             'Xylan',    0.0240, chemicals),
    # Rxn('Xylan + H2O -> Xylose',                     'Xylan',    0.9000, chemicals),
    # Rxn('Mannan + H2O -> MannoseOligomer',           'Mannan',   0.0240, chemicals),
    # Rxn('Mannan + H2O -> Mannose',                   'Mannan',   0.9000, chemicals), 
    # Rxn('Mannan -> HMF + 2 H2O',                     'Mannan',   0.0500, chemicals),
    # Rxn('Galactan + H2O -> GalactoseOligomer',       'Galactan', 0.0240, chemicals),     
    # Rxn('Galactan + H2O -> Galactose',               'Galactan', 0.9000, chemicals), 
    # Rxn('Galactan -> HMF + 2 H2O',                   'Galactan', 0.0500, chemicals),       
    # Rxn('Arabinan + H2O -> ArabinoseOligomer',       'Arabinan', 0.0240, chemicals), 
    # Rxn('Arabinan + H2O -> Arabinose',               'Arabinan', 0.9000, chemicals), 
    # Rxn('Arabinan -> Furfural + 2 H2O',              'Arabinan', 0.0500, chemicals), 
    # Rxn('Acetate -> AceticAcid',                     'Acetate',  1.0000, chemicals),      
    # Rxn('Furfural + 3 H2O  -> Tar',                  'Furfural',  1.0000, chemicals), 
    # Rxn('HMF + 3 H2O  -> 1.2 Tar',                   'HMF',       1.0000, chemicals), 
    # Rxn('Lignin -> SolubleLignin',                   'Lignin',    0.0500, chemicals),   
    # Rxn('Sucrose -> HMF + Glucose + 2H2O',           'Sucrose',   1.0000, chemicals)
        ])
    else:
        self.reactions = reactions
    self.tau = tau
    self.V_wf = V_wf
    self.length_to_diameter = length_to_diameter
    self.vessel_material = vessel_material
    self.vessel_type = vessel_type
    self.run_vle = run_vle

def _load_components(self):
    thermo = self.thermo
    self._multistream = MultiStream(None, thermo=thermo)

def _run(self):
    feed = self.ins[0]
    vapor, liquid = self.outs
    liquid.copy_like(feed)
    self. Reactions(liquid) 
    if self.T:
        if self.run_vle:
            ms = self._multistream
            ms.copy_like(liquid)
            ms.vle(T=self.T, V=self.V)
            vapor.mol[:] = ms.imol['g']
            liquid.mol[:] = ms.imol['l']
            vapor.T = liquid.T = ms.T
            vapor.P = liquid.P = ms.P
        else:
            liquid.T = self.T

def _design(self):
    Design = self.design_results
    ins_F_vol = self.F_vol_in
    V_reactor = ins_F_vol * self.tau / self.V_wf
    P = self.outs[0].P * 0.000145038 # Pa to psi
    length_to_diameter = self.length_to_diameter
    D = cylinder_diameter_from_volume(V_reactor, self.length_to_diameter)
    D *= 3.28084 # convert from m to ft
    L = D * length_to_diameter
    Design['Residence time'] = self.tau
    Design['Reactor volume'] = V_reactor
    Design.update(self._vessel_design(float(P), float(D), float(L)))
    self._decorated_design()
    Design = self.design_results

def _cost(self):
    Design = self.design_results
    self.baseline_purchase_costs.update(
        self._vessel_purchase_cost(
            Design['Weight'], Design['Diameter'], Design['Length']
        )
    )
    self._decorated_cost()

Rxn = tmo.reaction.Reaction ParallelRxn = tmo.reaction.ParallelReaction

R201 = PretreatmentReactorSystem('R201', cornstover)

A_sys = bst.main_flowsheet.create_system('A_sys') A_sys.simulate() R201.Hnet