def __new__(cls, chemicals, cache=None): chemicals = tmo.Chemicals(chemicals) chemicals_tuple = tuple(chemicals) cache = cls._cache if chemicals in cache: self = cache[chemicals] else: chemicals.compile(cache) self = cache[chemicals_tuple] = chemicals return self
def test_chemical_ID_parsing(): H2O = tmo.Chemical('H2O,g') assert H2O.ID == 'H2O' assert H2O.locked_state == 'g' Steam = tmo.Chemical('Steam,g', search_ID='H2O') assert Steam.ID == 'Steam' assert Steam.locked_state == 'g' chemicals = tmo.Chemicals(['H2O', 'Ethanol', 'O2,g', 'CO2,g']) assert [i.ID for i in chemicals] == ['H2O', 'Ethanol', 'O2', 'CO2'] assert [i.locked_state for i in chemicals] == [None, None, 'g', 'g'] with pytest.raises(ValueError): tmo.Chemical('CO2,v')
def chemicals_from_data(data, all_data): new_chemicals = dict(data) chemical_copies = {} for ID, kwargs in data.items(): if kwargs: if 'Copy of' in kwargs: chemical_copies[ID] = kwargs else: new_chemicals[ID] = tmo.Chemical(ID, **kwargs) else: new_chemicals[ID] = tmo.Chemical(ID) for ID, kwargs in chemical_copies.items(): copied_ID = kwargs.pop('Copy of') try: copied_chemical = new_chemicals[copied_ID] except KeyError: new_chemicals[copied_ID] = copied_chemical = tmo.Chemical( copied_ID, **all_data[copied_ID]) new_chemicals[ID] = copied_chemical.copy(ID, **kwargs) return tmo.Chemicals([new_chemicals[i] for i in data])
def test_reaction_enthalpy_balance(): # Combustion; ensure heat of gas phase reaction without sensible heats is # the lower heating value chemicals = H2O, Methane, CO2, O2, H2 = tmo.Chemicals(['H2O', 'Methane', 'CO2', 'O2', 'H2']) H2O.H.g.Hvap_Tb = 44011.496 # Depending on the model, this value may be different. tmo.settings.set_thermo(chemicals) combustion = tmo.Reaction('Methane + O2 -> H2O + CO2', reactant='Methane', X=1, correct_atomic_balance=True) Tref = 298.15 Tb = H2O.Tb feed = tmo.Stream(Methane=1, O2=2, T=Tb, phase='g') H0 = feed.Hnet - Methane.Cn.g.T_dependent_property_integral(Tref, Tb) - 2 * O2.Cn.g.T_dependent_property_integral(Tref, Tb) combustion(feed) Hf = feed.Hnet - 2 * H2O.Cn.l.T_dependent_property_integral(Tref, Tb) - CO2.Cn.g.T_dependent_property_integral(Tref, Tb) assert_allclose(Hf - H0, -Methane.LHV) # Electrolysis of water; ensure heat of reaction without sensible # heats is the higher heating value of hydrogen (with opposite sign) tmo.settings.set_thermo(chemicals) reaction = tmo.Reaction('2H2O,l -> 2H2,g + O2,g', reactant='H2O', X=1) feed = tmo.Stream(None, H2O=1) H0 = feed.Hnet feed.phases = ('g', 'l') # Gas and liquid phases must be available reaction(feed) # Call to run reaction on molar flow Hf = feed.Hnet assert_allclose(Hf - H0, H2.HHV) # Electrolysis of water; ensure gas phase heat of reaction without sensible # heats is the lower heating value of hydrogen (with opposite sign) reaction = tmo.Reaction('2H2O -> 2H2 + O2', reactant='H2O', X=1) feed = tmo.Stream(None, H2O=1, T=Tref, phase='g') H0 = feed.Hnet - H2O.Cn.l.T_dependent_property_integral(Tref, H2O.Tb) - H2O.Cn.g.T_dependent_property_integral(H2O.Tb, Tref) reaction(feed) # Call to run reaction on molar flow Hf = feed.Hnet assert_allclose(Hf - H0, H2.LHV)
def create_chemicals(): chems = tmo.Chemicals([]) def append_single_phase_chemical(ID, search_ID=None, **data): chemical = tmo.Chemical(ID, search_ID=search_ID, **data) try: chemical.at_state(phase=chemical.phase_ref) except: pass chemical.default() chems.append(chemical) def extend_single_phase_chemicals(IDs, **data): for ID in IDs: append_single_phase_chemical(ID, **data) def append_new_single_phase_chemical(ID, source=None, **data): chemical = tmo.Chemical.blank(ID, **data) if source: default_phase_ref = source.phase_ref chemical.copy_models_from(source) else: default_phase_ref = 'l' if not chemical.phase_ref: chemical.phase_ref = default_phase_ref chemical.at_state(chemical.phase_ref) chemical.default() chems.append(chemical) def append_chemical_copy(ID, chemical): new_chemical = chemical.copy(ID) chems.append(new_chemical) def set_Cp(single_phase_chemical, Cp): chem = single_phase_chemical chem.Cn.add_model(Cp * chem.MW, top_priority=True) def set_rho(single_phase_chemical, rho): V = fn.rho_to_V(rho, single_phase_chemical.MW) single_phase_chemical.V.add_model(V, top_priority=True) ### Define species # As is in data bank chems.extend( tmo.Chemicals([ 'Water', 'Ethanol', 'AceticAcid', 'Furfural', 'Glycerol', 'H2SO4', 'LacticAcid', 'SuccinicAcid', lc.chemicals.P4O10 ])) chems.H2SO4.at_state('l') append_single_phase_chemical('HNO3', 'NitricAcid') append_single_phase_chemical('Denaturant', 'Octane') append_single_phase_chemical('DAP', 'Diammonium Phosphate') append_single_phase_chemical('AmmoniumAcetate') append_single_phase_chemical('AmmoniumSulfate') append_single_phase_chemical('NaNO3', 'SodiumNitrate') append_single_phase_chemical('Oil', 'Oleic acid') append_single_phase_chemical('HMF') # Will remain in the vapor phase extend_single_phase_chemicals(['N2', 'NH3', 'O2', 'CH4', 'H2S', 'SO2']) append_single_phase_chemical('CO2') # Analagous vapors append_new_single_phase_chemical('NO2', chems.N2, formula='NO2', Hf=7925 * cal2joule) append_new_single_phase_chemical('NO', chems.N2, formula='NO', Hf=82.05) append_single_phase_chemical('CO', 'Carbon monoxide', Hf=-110.522) # Will remain as solid extend_single_phase_chemicals(['Glucose', 'Xylose', 'Sucrose'], Hfus=0) append_single_phase_chemical('CaSO4') subgroup = chems['Glucose', 'Xylose', 'Sucrose', 'CaSO4', 'AmmoniumSulfate'] for chemical in subgroup: set_Cp(chemical, Cp_cellulosic) # Analagous sugars append_chemical_copy('Mannose', chems.Glucose) append_chemical_copy('Galactose', chems.Glucose) append_chemical_copy('Arabinose', chems.Xylose) # Other analogues append_chemical_copy('CellulaseNutrients', chems.Glucose) append_chemical_copy('Extract', chems.Glucose) append_chemical_copy('Acetate', chems.AceticAcid) append_chemical_copy('Tar', chems.Xylose) chems.Acetate.Hf = -103373 # Chemicals taken from previous study chems.append(lc.chemicals.CaO) chems.append(lc.chemicals.Ash) chems.append(lc.chemicals.NaOH) append_new_single_phase_chemical('Lignin', formula='C8H8O3', Hf=-108248 * cal2joule) set_rho(chems.Lignin, 1540) set_Cp(chems.Lignin, Cp_cellulosic) append_chemical_copy('SolubleLignin', chems.Lignin) # Create structural carbohydrates append_chemical_copy('GlucoseOligomer', chems.Glucose) set_Cp(chems.GlucoseOligomer, Cp_cellulosic) chems.GlucoseOligomer._formula = None chems.GlucoseOligomer.formula = "C6H10O5" chems.GlucoseOligomer.Hf = -233200 * cal2joule append_chemical_copy('GalactoseOligomer', chems.GlucoseOligomer) append_chemical_copy('MannoseOligomer', chems.GlucoseOligomer) append_chemical_copy('XyloseOligomer', chems.Xylose) set_Cp(chems.XyloseOligomer, Cp_cellulosic) chems.XyloseOligomer._formula = None chems.XyloseOligomer.formula = "C5H8O4" chems.XyloseOligomer.Hf = -182100 * cal2joule append_chemical_copy('ArabinoseOligomer', chems.XyloseOligomer) # Other append_new_single_phase_chemical('Z_mobilis', formula="CH1.8O0.5N0.2", Hf=-31169.39 * cal2joule) append_new_single_phase_chemical('T_reesei', formula="CH1.645O0.445N0.205S0.005", Hf=-23200.01 * cal2joule) append_new_single_phase_chemical('Biomass', formula="CH1.64O0.39N0.23S0.0035", Hf=-23200.01 * cal2joule) append_new_single_phase_chemical( 'Cellulose', formula="C6H10O5", # Glucose monomer minus water Hf=-233200.06 * cal2joule) append_new_single_phase_chemical('Protein', formula="CH1.57O0.31N0.29S0.007", Hf=-17618 * cal2joule) append_new_single_phase_chemical('Enzyme', formula="CH1.59O0.42N0.24S0.01", Hf=-17618 * cal2joule) append_new_single_phase_chemical('Glucan', formula='C6H10O5', Hf=-233200 * cal2joule) append_new_single_phase_chemical('Xylan', formula="C5H8O4", Hf=-182100 * cal2joule) append_new_single_phase_chemical('Xylitol', formula="C5H12O5", Hf=-243145 * cal2joule) append_new_single_phase_chemical('Cellobiose', formula="C12H22O11", Hf=-480900 * cal2joule) append_new_single_phase_chemical( 'CSL', MW=(chems.Protein.MW / 4 + chems.Water.MW / 2 + chems.LacticAcid.MW / 4), Hf=(chems.Protein.Hf / 4 + chems.Water.Hf / 2 + chems.LacticAcid.Hf / 4)) append_chemical_copy('DenaturedEnzyme', chems.Enzyme) append_chemical_copy('Arabinan', chems.Xylan) append_chemical_copy('Mannan', chems.Glucan) append_chemical_copy('Galactan', chems.Glucan) # TODO: Maybe remove this # For waste water append_chemical_copy('WWTsludge', chems.Biomass) append_chemical_copy('Cellulase', chems.Enzyme) chems.compile() chems.set_synonym('CaO', 'Lime') chems.set_synonym('Water', 'H2O') chems.set_synonym('H2SO4', 'SulfuricAcid') chems.set_synonym('NH3', 'Ammonia') chems.set_synonym('AmmoniumSulfate', 'NH4SO4') chems.set_synonym('Denaturant', 'Octane') chems.set_synonym('CO2', 'CarbonDioxide') return chems
# %% Facilities # tmo.Stream.default_ID_number = 500 J7_1 = bst.Junction('J7_1', upstream=S402 - 0, downstream=Stream()) BT = bst.facilities.BoilerTurbogenerator('BT', ins=(J7_1 - 0), turbogenerator_efficiency=0.85) BT.outs[-1].T = 373.15 # tmo.Stream.default_ID_number = 700 CWP = bst.facilities.ChilledWaterPackage('CWP') CT = bst.facilities.CoolingTower('CT') CT.outs[1].T = 273.15 + 28 water_thermo = tmo.Thermo(tmo.Chemicals(['Water'])) process_water = tmo.Stream(ID='process_water', thermo=water_thermo) process_water_streams = (caustic, stripping_water, process_water1, process_water2, steam, BT - 1, CT - 1) def update_water_loss(): process_water.imol['Water'] = sum( [i.imol['Water'] for i in process_water_streams]) makeup_water = Stream('makeup_water', thermo=water_thermo, price=price['Makeup water'])
# %% Constants # Common structural carbohydrates properties # Assume heat capacity of lignin, cellulose, and hemicellulose # and all components at 350 K are about the same [2,2]. Cp_cellulosic = 1.364 # Assume density is similar for most solids rho = 1540 # kg/m3 cal2joule = 4.184 # %% Initialize Chemicals object and define functions chems = wheatstraw_chemicals = tmo.Chemicals([]) # Chemicals to define chemical_IDs = [ 'Water', 'Ethanol', 'Glucose', 'Galactose', 'Mannose', 'Xylose', 'Arabinose', 'Cellobiose', 'Sucrose', 'GlucoseOligomer', 'GalactoseOligomer', 'MannoseOligomer', 'XyloseOligomer', 'ArabinoseOligomer', 'Extract', 'SolubleLignin', 'HMF', 'Furfural', 'AceticAcid', 'LacticAcid', 'Xylitol', 'Glycerol', 'SuccinicAcid', 'NH3', 'H2SO4', 'NH4SO4', 'AmmoniumAcetate', 'DAP', 'HNO3', 'NaNO3', 'NaOH', 'CellulaseNutrients', 'Denaturant', 'Oil', 'Cellulose', 'Galactan', 'Mannan', 'Glucan', 'Xylan', 'Arabinan', 'Lignin', 'Acetate', 'Protein', 'Ash', 'Enzyme', 'DenaturedEnzyme', 'S_cerevisiae', 'T_reesei', 'Biomass', 'Tar', 'CaO', 'CaSO4', 'Graphite', 'N2', 'O2', 'CO2', 'CH4', 'H2S', 'SO2', 'NO', 'CO', 'AmmoniumSulfate', 'NO2', 'CSL', 'WWTsludge', 'Cellulase' ]
@author: yalinli_cabbi """ # %% # ============================================================================= # Setup # ============================================================================= import thermosteam as tmo __all__ = ('chems', 'chemical_groups', 'soluble_organics', 'combustibles') # All chemicals used in this biorefinery chems = tmo.Chemicals([]) # To keep track of which chemicals are available in the database and which # are created from scratch database_chemicals_dict = {} copied_chemicals_dict = {} defined_chemicals_dict = {} def chemical_database(ID, phase=None, **kwargs): chemical = tmo.Chemical(ID, **kwargs) if phase: chemical.at_state(phase) chemical.phase_ref = phase chems.append(chemical) database_chemicals_dict[ID] = f'{ID}: {chemical.formula}/{chemical.MW}'
# %% Constants # Common structural carbohydrates properties # Assume heat capacity of lignin, cellulose, and hemicellulose # and all components at 350 K are about the same [2,2]. Cp_cellulosic = 1.364 # Assume density is similar for most solids rho = 1540 # kg/m3 cal2joule = 4.184 # %% Initialize Chemicals object and define functions chems = cornstover_chemicals = tmo.Chemicals([]) # Chemicals to define chemical_IDs = [ 'Water', 'Ethanol', 'Glucose', 'Galactose', 'Mannose', 'Xylose', 'Arabinose', 'Cellobiose', 'Sucrose', 'GlucoseOligomer', 'GalactoseOligomer', 'MannoseOligomer', 'XyloseOligomer', 'ArabinoseOligomer', 'Extract','SolubleLignin','HMF', 'Furfural', 'AceticAcid', 'LacticAcid', 'Xylitol', 'Glycerol', 'SuccinicAcid', 'NH3', 'H2SO4', 'NH4SO4', 'AmmoniumAcetate', 'DAP', 'HNO3', 'NaNO3', 'NaOH', 'CellulaseNutrients', 'Denaturant', 'Oil', 'Cellulose', 'Galactan', 'Mannan', 'Glucan', 'Xylan', 'Arabinan', 'Lignin', 'Acetate', 'Protein', 'Ash', 'Enzyme', 'DenaturedEnzyme', 'Z_mobilis', 'T_reesei', 'Biomass', 'Tar', 'CaO', 'CaSO4', 'Graphite', 'N2', 'O2', 'CO2',
# -*- coding: utf-8 -*- """ Created on Tue Feb 4 06:42:02 2020 @author: yoelr """ from thermosteam import functional as fn import thermosteam as tmo __all__ = ('lipidcane_chemicals', 'pretreatment_chemicals', 'ethanol_chemicals', 'biodiesel_chemicals') # %% Define common chemicals Biodiesel = tmo.Chemical('Biodiesel', search_ID='Methyl oleate') lipidcane_chemicals = tmo.Chemicals([ 'Water', 'Methanol', 'Ethanol', 'Glycerol', 'Glucose', 'Sucrose', 'H3PO4', 'P4O10', 'CO2', 'Octane', 'O2', Biodiesel ]) (Water, Methanol, Ethanol, Glycerol, Glucose, Sucrose, H3PO4, P4O10, CO2, Octane, O2, Biodiesel) = lipidcane_chemicals O2.at_state(phase='g') CO2.at_state(phase='g') H3PO4.at_state(phase='s') P4O10.at_state(phase='s') Glucose.at_state(phase='s') Sucrose.at_state(phase='s') # %% Define new chemicals
def test_stream(): import thermosteam as tmo tmo.settings.set_thermo(['Water'], cache=True) stream = tmo.Stream(None, Water=1, T=300) assert [stream.chemicals.Water] == stream.available_chemicals assert_allclose(stream.epsilon, 77.70030000000003) assert_allclose(stream.alpha * 1e6, 0.14330776454124503) assert_allclose(stream.nu, 8.799123532986536e-07) assert_allclose(stream.Pr, 6.14001869413997) assert_allclose(stream.Cn, 75.29555729396768) assert_allclose(stream.C, 75.29555729396768) assert_allclose(stream.Cp, 4.179538552493643) assert_allclose(stream.P_vapor, 3533.918074415897) assert_allclose(stream.mu, 0.0008766363688287887) assert_allclose(stream.kappa, 0.5967303492959747) assert_allclose(stream.rho, 996.2769195618362) assert_allclose(stream.V, 1.80826029854462e-05) assert_allclose(stream.H, 139.31398526921475) assert_allclose(stream.S, 70.46581776376684) assert_allclose(stream.sigma, 0.07176932405246211) assert_allclose(stream.z_mol, [1.0]) assert_allclose(stream.z_mass, [1.0]) assert_allclose(stream.z_vol, [1.0]) assert not stream.source assert not stream.sink assert stream.main_chemical == 'Water' assert not stream.isfeed() assert not stream.isproduct() assert stream.vapor_fraction == 0. with pytest.raises(ValueError): stream.get_property('isfeed', 'kg/hr') with pytest.raises(ValueError): stream.set_property('invalid property', 10, 'kg/hr') with pytest.raises(ValueError): tmo.Stream(None, Water=1, units='kg') stream.mol = 0. stream.mass = 0. stream.vol = 0. with pytest.raises(AttributeError): stream.F_mol = 1. with pytest.raises(AttributeError): stream.F_mass = 1. with pytest.raises(AttributeError): stream.F_vol = 1. # Make sure energy balance is working correctly with mix_from and vle chemicals = tmo.Chemicals(['Water', 'Ethanol']) thermo = tmo.Thermo(chemicals) tmo.settings.set_thermo(thermo) s3 = tmo.Stream('s3', T=300, P=1e5, Water=10, units='kg/hr') s4 = tmo.Stream('s4', phase='g', T=400, P=1e5, Water=10, units='kg/hr') s_eq = tmo.Stream('s34_mixture') s_eq.mix_from([s3, s4]) s_eq.vle(H=s_eq.H, P=1e5) H_sum = s3.H + s4.H H_eq = s_eq.H assert_allclose(H_eq, H_sum, rtol=1e-3) s_eq.vle(H=s3.H + s4.H, P=1e5) assert_allclose(s_eq.H, H_sum, rtol=1e-3)
def create_system(ID='wheatstraw_sys'): System.maxiter = 400 System.converge_method = 'Aitken' System.molar_tolerance = 0.01 ### Streams ### chemicals = bst.settings.get_chemicals() non_soluble = [ 'Xylan', 'Glucan', 'Arabinan', 'Lignin', 'Extract', 'Ash', 'Mannan', 'Galactan', 'Acetate' ] # feed flow drycomposition = chemicals.kwarray( dict(Glucan=0.3342, Xylan=0.2330, Arabinan=0.0420, Lignin=0.2260, Extract=0.1330, Ash=0.0180, Acetate=0.0130)) TS = 0.95 moisture_content = chemicals.kwarray(dict(Water=1 - TS)) dryflow = 83333.0 netflow = dryflow / TS feedflow = netflow * (drycomposition * TS + moisture_content) process_water_over_dryflow = 19.96 sulfuric_acid_over_dryflow = 0.04 wheatstraw = Stream('wheatstraw', feedflow, units='kg/hr', price=price['Feedstock'] * TS) # %% Pretreatment system process_water1 = Stream( 'process_water1', T=25 + 273.15, P=1 * 101325, Water=process_water_over_dryflow * dryflow, #only an initialization units='kg/hr') sulfuric_acid = Stream('sulfuric_acid', P=1 * 101325, T=25 + 273.15, Water=0.05 * sulfuric_acid_over_dryflow * dryflow, SulfuricAcid=0.95 * sulfuric_acid_over_dryflow * dryflow, units='kg/hr', price=price['Sulfuric acid'] * 0.95) steam = Stream( 'steam', phase='g', T=212 + 273.15, P=20 * 101325, Water=dryflow * 0.5, #This is just a guess units='kg/hr') U101 = units.FeedStockHandling('U101', ins=wheatstraw) U101.cost_items['System'].cost = 0 T201 = units.SulfuricAcidTank('T201', ins=sulfuric_acid) M201 = bst.Mixer('M201', ins=(process_water1, T201 - 0, Stream())) M202 = units.WashingTank('M202', ins=(M201 - 0, U101 - 0)) S200 = units.SieveFilter('S200', ins=(M202 - 0), outs=(Stream('feed_20TS'), Stream('recycled_water1')), moisture_content=1 - 0.20, split=find_split_solids(M202 - 0, non_soluble)) S201 = units.PressureFilter('S201', ins=(S200 - 0), outs=(Stream('feed_50TS'), Stream('recycled_water2')), moisture_content=0.5, split=find_split_solids(S200 - 0, non_soluble)) M200 = bst.Mixer('M200', ins=(S200 - 1, S201 - 1), outs='recycled_water') M200 - 0 - 2 - M201 recycled_water = M200 - 0 def update_process_water1(): process_water1.imass[ 'Water'] = process_water_over_dryflow * dryflow - recycled_water.imass[ 'Water'] sulfuric_acid.imass[ 'SulfuricAcid'] = 0.95 * sulfuric_acid_over_dryflow * dryflow - recycled_water.imass[ 'SulfuricAcid'] sulfuric_acid.imass[ 'Water'] = 0.05 / 0.95 * sulfuric_acid.imass['SulfuricAcid'] water_recycle_sys = System('water_recycle_sys', path=(U101, T201, M201, M202, S200, S201, M200, update_process_water1), recycle=M201 - 0) M205 = bst.Mixer('M205', ins=(S201 - 0, None)) M203 = units.SteamMixer('M203', ins=(M205 - 0, steam), P=steam.chemicals.Water.Psat(190.0 + 273.15)) R201 = units.PretreatmentReactorSystem( 'R201', ins=M203 - 0, outs=(Stream('pretreatment_steam'), Stream('pretreatment_effluent'))) P201 = units.BlowdownDischargePump('P201', ins=R201 - 1) T202 = units.OligomerConversionTank('T202', ins=P201 - 0) F201 = units.PretreatmentFlash('F201', ins=T202 - 0, outs=(Stream('flash_steam'), Stream('flash_effluent')), P=101325, Q=0) M204 = bst.Mixer('M204', ins=(R201 - 0, F201 - 0)) S202 = units.PressureFilter('S202', ins=(F201 - 1), outs=(Stream('pretreated_stream'), Stream('pretreated_liquid')), moisture_content=0.5, split=find_split_solids(F201 - 1, non_soluble)) S203 = bst.Splitter('S203', ins=M204 - 0, outs=(Stream('steam_back'), Stream('residual_steam')), split=0.25) H201 = units.WasteVaporCondenser('H201', ins=S203 - 1, outs=Stream('condensed_steam'), T=99 + 273.15, V=0) S203 - 0 - 1 - M205 steam_out1 = S203 - 1 steam_inS203 = 0 - S203 steam_out0 = S203 - 0 def update_split(): steam_out1.mol[:] = steam_inS203.mol[:] - steam_out0.mol[:] pretreatment_sys = System( 'pretreatment_sys', path=( water_recycle_sys, M205, M203, R201, P201, T202, F201, M204, S202, S203, update_split, H201), # TODO: H201 moved to the end, no need to resimulate system recycle=M204 - 0) ### TODO: There is a bug in original code; for now spec is constant # S203.split[:] = 0.5 T90 = 90 + 273.15 def f_DSpret(split): S203.split[:] = split for i in range(3): pretreatment_sys.simulate() sobj = M205 - 0 return sobj.T - T90 pretreatment_sys.specification = BoundedNumericalSpecification( f_DSpret, 0.10, 0.70) ### Fermentation system ### cellulase_conc = 0.05 cellulase = Stream('cellulase', units='kg/hr', price=price['Enzyme']) ammonia = Stream( 'ammonia', Ammonia=1051 / 1000 * dryflow, #This is just a initialization units='kg/hr', phase='l', price=price['Ammonia']) process_water2 = Stream( 'process_water2', T=10 + 273.15, P=1 * 101325, Water=1664.8 / 1000 * dryflow, #This is just a guess units='kg/hr') ammonia1 = Stream( 'ammonia1', Ammonia=26 / 1000 * dryflow, #This is just a initialization units='kg/hr', price=price['Ammonia']) ammonia2 = Stream( 'ammonia2', Ammonia=116 / 1000 * dryflow, #This is just a initialization units='kg/hr', price=price['Ammonia']) ammonia_fresh = Stream('ammonia_fresh', units='kg/hr', price=price['Ammonia']) ammonia_storage = units.DAPTank('Ammonia_storage', ins=ammonia_fresh, outs='Ammonia_fermentation') S301 = bst.ReversedSplitter('S301', ins=ammonia_storage - 0, outs=(ammonia, ammonia1, ammonia2)) air1 = Stream('air_lagoon1', O2=51061, N2=168162, phase='g', units='kg/hr') air2 = Stream('air_lagoon2', O2=51061, N2=168162, phase='g', units='kg/hr') J1 = bst.Junction('J1', upstream=S202 - 0, downstream=Stream()) sacch_split = 0.05 #This is just a initialization ammonia_zmass = 0.0052 M301 = bst.Mixer('M301', ins=(ammonia, process_water2)) M302 = bst.Mixer('M302', ins=(J1 - 0, M301 - 0)) S303 = units.PressureFilter('S303', ins=(M302 - 0), outs=(Stream('cooled_hydrolysate'), Stream('residual_water')), moisture_content=0.4, split=find_split_solids(M302 - 0, non_soluble)) WIS_prehyd = 0.20 cooled_hydrolyzate_pre = M302.outs[0] S303_out0 = S303.outs[0] S303_out1 = S303.outs[1] def update_moisture_content(): F_non_sol_S303in = find_WIS( cooled_hydrolyzate_pre, non_soluble) * cooled_hydrolyzate_pre.F_mass F_sol_S303in = cooled_hydrolyzate_pre.F_mass - F_non_sol_S303in F_sol_S303out = F_non_sol_S303in / WIS_prehyd - cellulase.F_mass - F_non_sol_S303in split_soluble = F_sol_S303out / F_sol_S303in new_split = find_split_solids(cooled_hydrolyzate_pre, non_soluble) new_split[new_split == 0] = split_soluble S303_out0.mass[:] = cooled_hydrolyzate_pre.mass[:] * new_split S303_out1.mass[:] = cooled_hydrolyzate_pre.mass[:] * (1 - new_split) T203 = units.AmmoniaAdditionTank('T203', ins=S303 - 0) M303 = units.EnzymeHydrolysateMixer('M303', ins=(T203 - 0, cellulase)) cellulase_over_WIS = 0.05 * cellulase_conc water_over_WIS = 0.05 * (1 - cellulase_conc) def update_cellulase_and_nutrient_loading(): WIS_premixer = cooled_hydrolyzate_pre.F_mass * find_WIS( cooled_hydrolyzate_pre, non_soluble) cellulase_mass = cellulase_over_WIS * WIS_premixer water_mass = water_over_WIS * WIS_premixer cellulase.imass['Cellulase'] = cellulase_mass * 1.1 cellulase.imass['Water'] = water_mass * 1.1 # Note: An additional 10% is produced for the media glucose/sophorose mixture # Humbird (2011) p[g. 37 def update_ammonia_loading(): water_cooled_hydrolyzate = cooled_hydrolyzate_pre.imass['Water'] ammonia.F_mass = water_cooled_hydrolyzate * ammonia_zmass M304 = bst.Mixer('M304', ins=(M303 - 0, None)) R301 = units.SaccharificationAndCoFermentation( 'R301', ins=(M304 - 0, ammonia1, air1), outs=(Stream('CO2_1'), Stream('fermentation_slurry'), Stream('saccharified_to_seed')), saccharified_slurry_split=sacch_split) M305 = bst.Mixer('M305', ins=(R301 - 2, ammonia2, air2)) R302 = units.SeedTrain('R302', ins=M305 - 0, outs=(Stream('CO2_2'), Stream('effluent'))) T301 = units.SeedHoldTank('T301', ins=R302 - 1) T301 - 0 - 1 - M304 air2_over_glucose = (R302.reactions.X[1] * 2.17 + R302.reactions.X[3] * 1.5 / 2 - R302.reactions.X[2]) * 1.1 ammonia2_over_glucose = R302.reactions.X[1] * 0.62 * 1.1 preseed = M305 - 0 def update_nutrient_loading2(): glucose_preseed = preseed.imol['Glucose'] air2.imol['O2'] = air2_over_glucose * glucose_preseed air2.imol['N2'] = (air2_over_glucose * glucose_preseed) / 0.21 * 0.79 ammonia2_mol = ammonia2_over_glucose * glucose_preseed - preseed.imol[ 'Ammonia'] if ammonia2_mol < 0: ammonia2.imol['NH3'] = 0 else: ammonia2.imol['NH3'] = ammonia2_mol air1_over_glucose = (R301.cofermentation.X[1] * 2.17 + R301.cofermentation.X[3] * 1.5 / 2 - R301.cofermentation.X[2]) * 1.2 ammonia1_over_glucose = R301.cofermentation.X[1] * 0.62 * 1.1 preferm = M304 - 0 glucose_over_glucan = R301.saccharification.X[ 0] + R301.saccharification.X[1] * 0.5 + R301.saccharification.X[2] def update_nutrient_loading1(): glucose_preferm = preferm.imol['Glucan'] * glucose_over_glucan * ( 1 - R301.saccharified_slurry_split) air1.imol['O2'] = air1_over_glucose * glucose_preferm air1.imol['N2'] = (air1_over_glucose * glucose_preferm) / 0.21 * 0.79 ammonia1_mol = ammonia1_over_glucose * glucose_preferm - preferm.imol[ 'Ammonia'] * (1 - R301.saccharified_slurry_split) if ammonia1_mol < 0: ammonia1.imol['NH3'] = 0 else: ammonia1.imol['NH3'] = ammonia1_mol # TODO: Bug in update nutrient loading (not enough O2 to run R301 and R302 seed train) # TODO: so just ignore negative flow in the meanwhile # def ignore_negative_O2_flow(): # for i in (R301.outs + R302.outs): i.imol['O2'] = 0 seed_recycle_sys = System('seed_recycle_sys', path=(M304, update_nutrient_loading1, R301, M305, update_nutrient_loading2, R302, T301), recycle=M304 - 0) conc_yeast = 3.0 def f_DSferm1(x): sacch_split = x R301.saccharified_slurry_split = sacch_split for i in range(3): seed_recycle_sys.simulate() s_obj2 = R301 - 1 light_ind = s_obj2.chemicals._light_indices l = [a for a in s_obj2.vol[light_ind] if not a == 0] v_0 = s_obj2.F_vol - sum(l) conc_yeast_obtained = s_obj2.imass['S_cerevisiae'] / v_0 return ((conc_yeast_obtained - conc_yeast) / conc_yeast) seed_recycle_sys.specification = BoundedNumericalSpecification( f_DSferm1, 0.01, 0.35) fermentation_sys = System( 'fermentation_sys', path=(J1, M301, M302, S303, update_ammonia_loading, T203, update_cellulase_and_nutrient_loading, update_moisture_content, M303, seed_recycle_sys)) #update_moisture_content, T_solid_cool = 50.0 + 273.15 def f_DSferm2(x): mass_water = x process_water2.F_mass = mass_water for i in range(3): fermentation_sys.simulate() s_obj1 = M302 - 0 return ((s_obj1.T - T_solid_cool) / T_solid_cool) fermentation_sys.specification = BoundedNumericalSpecification( f_DSferm2, process_water2.F_mass / 2, process_water2.F_mass * 2) ### Ethanol purification ### stripping_water = Stream( 'stripping_water', Water=26836, #This is just a initialization units='kg/hr') M306 = bst.Mixer('M306', ins=(R302 - 0, R301 - 0)) T302 = units.BeerTank('T302', outs=Stream('cool_feed')) # tmo.Stream.default_ID_number = 400 M401 = bst.Mixer('M401', ins=(R301 - 1, None)) M401 - 0 - T302 D401 = bst.VentScrubber('D401', ins=(stripping_water, M306 - 0), outs=(Stream('CO2_purified'), Stream('bottom_liquid')), gas=('CO2', 'NH3', 'O2', 'N2')) D401 - 1 - 1 - M401 # Heat up before beer column # Exchange heat with stillage mid_eth_massfrac = 0.50 high_eth_massfrac = 0.915 bott_eth_massfrac = 0.00001 dist_high_pres = 2 * 101325 high_dist_stream = Stream('high_eth_stream', Ethanol=high_eth_massfrac, Water=1 - high_eth_massfrac, units='kg/hr') mid_dist_stream = Stream('mid_eth_stream', Ethanol=mid_eth_massfrac, Water=1 - mid_eth_massfrac, units='kg/hr') bottom_stream = Stream( 'bottom_stream', Ethanol= bott_eth_massfrac, #only an initialization. Later it gets updated with the real composition Water=1 - bott_eth_massfrac, units='kg/hr') dist_high_dp = high_dist_stream.dew_point_at_P(dist_high_pres) bott_mid_dp = bottom_stream.dew_point_at_T(dist_high_dp.T - 5) dist_mid_dp = mid_dist_stream.dew_point_at_P(bott_mid_dp.P) bott_low_dp = bottom_stream.dew_point_at_T(dist_mid_dp.T - 5) dist_low_dp = mid_dist_stream.dew_point_at_P(bott_low_dp.P) S401 = bst.Splitter('S401', ins=(T302 - 0), outs=(Stream('feed_low_pressure', P=bott_low_dp.P), Stream('feed_mid_pressure', P=bott_mid_dp.P)), split=0.5) H402 = bst.HXprocess('H402', ins=(S401 - 0, None), outs=(Stream('warmed_feed_lp'), Stream('cooled_bottom_water_lp')), U=1.28) H403 = bst.HXprocess('H403', ins=(S401 - 1, None), outs=(Stream('warmed_feed_mp'), Stream('cooled_bottom_water_mp')), U=1.28) # Beer column Ethanol_MW = chemicals.Ethanol.MW Water_MW = chemicals.Water.MW def Ethanol_molfrac(e): """Return ethanol mol fraction in a ethanol water mixture""" return e / Ethanol_MW / (e / Ethanol_MW + (1 - e) / Water_MW) xbot = Ethanol_molfrac(bott_eth_massfrac) ytop = Ethanol_molfrac(mid_eth_massfrac) D402 = units.DistillationColumn('D402', ins=H402 - 0, P=bott_low_dp.P, y_top=ytop, x_bot=xbot, k=1.5, LHK=('Ethanol', 'Water'), energy_integration=True) D402.tray_material = 'Stainless steel 304' D402.vessel_material = 'Stainless steel 304' D402.BM = 2.4 D402.boiler.U = 1.85 # Condense distillate H402_dist = bst.HXutility('H402_dist', ins=D402 - 0, V=0, T=dist_low_dp.T - 1) P402_2 = bst.Pump('P402_2', ins=H402_dist - 0, P=bott_mid_dp.P) P402_2.BM = 3.1 D402 - 1 - 1 - H402 LP_dist_sys = System('LP_dist_sys', path=(H402, D402, H402_dist), recycle=H402 - 0) D403 = units.DistillationColumn('D403', ins=H403 - 0, P=bott_mid_dp.P, y_top=ytop, x_bot=xbot, k=1.5, LHK=('Ethanol', 'Water'), energy_integration=True) D403.tray_material = 'Stainless steel 304' D403.vessel_material = 'Stainless steel 304' D403.BM = 2.4 D403.boiler.U = 1.85 # Condense distillate H403_dist = bst.HXutility('H403_dist', ins=D403 - 0, V=0, T=dist_mid_dp.T - 1) D403 - 1 - 1 - H403 MP_dist_sys = System('MP_dist_sys', path=(H403, D403, H403_dist), recycle=H403 - 0) M402 = bst.Mixer('M402', ins=(P402_2 - 0, H403_dist - 0), outs=Stream(P=bott_mid_dp.P)) P404 = bst.Pump('P404', ins=M402 - 0, P=dist_high_pres) M403 = bst.Mixer('M403', ins=(H402 - 1, H403 - 1), outs=Stream('bottom_water')) S402 = units.PressureFilter('S402', ins=(M403 - 0), outs=(Stream('Lignin'), Stream('Thin_spillage')), flux=1220.6 * 0.8, moisture_content=0.35, split=find_split_solids(M403 - 0, non_soluble)) # Mix ethanol Recycle (Set-up) M404 = bst.Mixer('M404', ins=(P404 - 0, None), outs=Stream(P=dist_high_pres)) ytop = Ethanol_molfrac(high_eth_massfrac) D404 = units.DistillationColumn('D404', ins=M404 - 0, P=dist_high_pres, y_top=ytop, x_bot=xbot, k=1.5, LHK=('Ethanol', 'Water'), energy_integration=True) D404.tray_material = 'Stainless steel 304' D404.vessel_material = 'Stainless steel 304' D404.BM = 2.4 D404.boiler.U = 1.85 P405 = bst.Pump('P405', ins=D404 - 1, outs=Stream('bottom_water')) # Superheat vapor for mol sieve H404 = bst.HXutility('H404', ins=D404 - 0, T=dist_high_dp.T + 37.0, V=1) # Molecular sieve U401 = bst.MolecularSieve('U401', ins=H404 - 0, split=(2165.14 / 13356.04, 1280.06 / 1383.85), order=('Ethanol', 'Water')) U401 - 0 - 1 - M404 ethanol_recycle_sys = System('ethanol_recycle_sys', path=(M404, D404, H404, U401), recycle=M404 - 0) # Condense ethanol product H405 = bst.HXutility('H405', ins=U401 - 1, V=0, T=dist_high_dp.T - 1) T701 = bst.StorageTank('T701', ins=H405 - 0, tau=7 * 24, vessel_type='Floating roof', vessel_material='Carbon steel') ethanol = Stream('ethanol', price=price['Ethanol']) P701 = bst.Pump('P701', ins=T701 - 0, outs=ethanol) P701.BM = 3.1 T701.BM = 1.7 vent_stream = M306 - 0 stripping_water_over_vent = stripping_water.mol / 21202.490455845436 def update_stripping_water(): stripping_water.mol[:] = stripping_water_over_vent * vent_stream.F_mass purification_sys = System( 'purification_sys', path=(M306, update_stripping_water, D401, M401, T302, S401, MP_dist_sys, LP_dist_sys, P402_2, M402, P404, M403, S402, ethanol_recycle_sys, P405, H405, T701, P701)) def f_DSpur(split): S401.split[:] = split for i in range(3): purification_sys.simulate() heat_cond = D403.condenser.Q + H403_dist.Q heat_boil = D402.boiler.Q return heat_boil + heat_cond #heat_boil and heat_cond have different signs purification_sys.specification = BoundedNumericalSpecification( f_DSpur, 0.10, 0.70) ### Biogas production organic_groups = [ 'OtherSugars', 'SugarOligomers', 'OrganicSolubleSolids', 'Furfurals', 'OtherOrganics', 'Protein', 'CellMass' ] organics = list( sum([chemical_groups[i] for i in organic_groups], ('Ethanol', 'AceticAcid', 'Xylose', 'Glucose', 'ExtractVol', 'ExtractNonVol'))) organics.remove('WWTsludge') P_sludge = 0.05 / 0.91 / chemicals.WWTsludge.MW MW = np.array([chemicals.CH4.MW, chemicals.CO2.MW]) CH4_molcomp = 0.60 mass = np.array([CH4_molcomp, 1 - CH4_molcomp]) * MW mass /= mass.sum() mass *= 0.381 / (0.91) P_ch4, P_co2 = mass / MW def anaerobic_rxn(reactant): MW = getattr(chemicals, reactant).MW return rxn.Reaction( f"{1/MW}{reactant} -> {P_ch4}CH4 + {P_co2}CO2 + {P_sludge}WWTsludge", reactant, 0.91) anaerobic_digestion = rxn.ParallelReaction( [anaerobic_rxn(i) for i in organics] + [rxn.Reaction(f"H2SO4 -> H2S + 2O2", 'H2SO4', 1.)]) well_water1 = Stream('well_water1', Water=1, T=15 + 273.15) J5_1 = bst.Junction('J5_1', upstream=S303 - 1, downstream=Stream()) J5_2 = bst.Junction('J5_2', upstream=S402 - 1, downstream=Stream()) J5_3 = bst.Junction('J5_3', upstream=S202 - 1, downstream=Stream()) J5_4 = bst.Junction('J5_4', upstream=H201 - 0, downstream=Stream()) J5_5 = bst.Junction('J5_5', upstream=P405 - 0, downstream=Stream()) M501 = bst.Mixer('M501', ins=(J5_1 - 0, J5_2 - 0, J5_3 - 0, J5_4 - 0, J5_5 - 0)) splits = [('Ethanol', 1, 15), ('Water', 27158, 356069), ('Glucose', 3, 42), ('Xylose', 7, 85), ('OtherSugars', 13, 175), ('SugarOligomers', 10, 130), ('OrganicSolubleSolids', 182, 2387), ('InorganicSolubleSolids', 8, 110), ('Ammonia', 48, 633), ('AceticAcid', 0, 5), ('Furfurals', 5, 70), ('OtherOrganics', 9, 113), ('Cellulose', 19, 6), ('Xylan', 6, 2), ('OtherStructuralCarbohydrates', 1, 0), ('Lignin', 186, 64), ('Protein', 51, 18), ('CellMass', 813, 280), ('OtherInsolubleSolids', 68, 23)] raw_biogas = Stream('raw_biogas', price=price['Pure biogas'] * 0.33) Tin_digestor = 37 + 273.15 R501 = units.AnaerobicDigestion('R501', ins=(M501 - 0, well_water1), outs=(raw_biogas, 'waste_effluent', 'sludge_effluent', ''), reactions=anaerobic_digestion, sludge_split=find_split(*zip(*splits)), T=Tin_digestor) digestor_sys = System('digestor_sys', path=(J5_1, J5_2, J5_3, J5_4, J5_5, M501, R501)) ### Waste water treatment combustion = chemicals.get_combustion_reactions() def growth(reactant): f = chemicals.WWTsludge.MW / getattr(chemicals, reactant).MW return rxn.Reaction(f"{f}{reactant} -> WWTsludge", reactant, 1.) # Note, nitrogenous species included here, but most of it removed in R601 digester aerobic_digestion = rxn.ParallelReaction([ i * 0.74 + 0.22 * growth(i.reactant) for i in combustion if (i.reactant in organics) ]) aerobic_digestion.X[:] = 0.96 # tmo.Stream.default_ID_number = 600 well_water = Stream('well_water', Water=1, T=15 + 273.15) raw_biogas2 = Stream('raw_biogas2', price=price['Pure biogas'] * 0.33) WWTC = units.WasteWaterSystemCost('WWTC', ins=R501 - 1) R601 = units.AnaerobicDigestionWWT('R601', ins=(WWTC - 0, well_water), outs=(raw_biogas2, '', '', ''), reactions=anaerobic_digestion, sludge_split=find_split(*zip(*splits)), T=Tin_digestor - 2) air = Stream('air_lagoon', O2=51061, N2=168162, phase='g', units='kg/hr') caustic = Stream('WWT_caustic', Water=2252, NaOH=2252, units='kg/hr', price=price['Caustic'] * 0.5) # polymer = Stream('WWT polymer') # Empty in humbird report :-/ M602 = bst.Mixer('M602', ins=(R601 - 1, None)) caustic_over_waste = caustic.mol / 2544300.6261793654 air_over_waste = air.mol / 2544300.6261793654 waste = M602 - 0 def update_aerobic_input_streams(): F_mass_waste = waste.F_mass caustic.mol[:] = F_mass_waste * caustic_over_waste air.mol[:] = F_mass_waste * air_over_waste R602 = units.AerobicDigestionWWT('R602', ins=(waste, air, caustic), outs=('evaporated_water', ''), reactions=aerobic_digestion) splits = [('Ethanol', 0, 1), ('Water', 381300, 2241169), ('Glucose', 0, 2), ('Xylose', 1, 3), ('OtherSugars', 1, 7), ('SugarOligomers', 1, 6), ('OrganicSolubleSolids', 79, 466), ('InorganicSolubleSolids', 4828, 28378), ('Ammonia', 3, 16), ('Furfurals', 0, 3), ('OtherOrganics', 1, 7), ('CarbonDioxide', 6, 38), ('O2', 3, 17), ('N2', 5, 32), ('Cellulose', 0, 194), ('Xylan', 0, 65), ('OtherStructuralCarbohydrates', 0, 15), ('Lignin', 0, 1925), ('Protein', 0, 90), ('CellMass', 0, 19778), ('OtherInsolubleSolids', 0, 707)] S601 = bst.Splitter('S601', ins=R602 - 1, split=find_split(*zip(*splits))) S602 = bst.Splitter('S602', ins=S601 - 1, split=0.96) M603 = bst.Mixer('M603', ins=(S602 - 0, None)) M603 - 0 - 1 - M602 M604 = bst.Mixer('M604', ins=(R601 - 2, S602 - 1)) centrifuge_species = ('Water', 'Glucose', 'Xylose', 'OtherSugars', 'SugarOligomers', 'OrganicSolubleSolids', 'InorganicSolubleSolids', 'Ammonia', 'Furfurals', 'OtherOrganics', 'CO2', 'COxSOxNOxH2S', 'Cellulose', 'Xylan', 'OtherStructuralCarbohydrates', 'Lignin', 'Protein', 'CellMass', 'OtherInsolubleSolids') S623_flow = np.array( [7708, 0, 0, 1, 1, 13, 75, 3, 0, 1, 1, 2, 25, 8, 2, 250, 52, 1523, 92]) S616_flow = np.array([ 109098, 3, 6, 13, 9, 187, 1068, 46, 5, 8, 14, 31, 1, 0, 0, 13, 3, 80, 5 ]) S603 = bst.Splitter('S603', ins=M604 - 0, outs=('', 'sludge'), split=find_split(centrifuge_species, S616_flow, S623_flow)) S603 - 0 - 1 - M603 S604 = bst.Splitter('S604', ins=S601 - 0, outs=('treated_water', 'waste_brine'), split={'Water': 0.987}) aerobic_recycle_sys = System('aerobic_recycle_sys', path=(M602, update_aerobic_input_streams, R602, S601, S602, M604, S603, M603), recycle=M602 - 0) WWT_sys = System('WWT_sys', path=(WWTC, R601, aerobic_recycle_sys, S604)) ### Facilities # %% Facilities # TODO: Double check that I did is right. # TODO: The BoilerTurbogenerator burns both biogas and lignin # Note that lime and boilerchems cost is taking into account in the # unit operation now # M605 = bst.Mixer('M605', ins=(R501-0, R601-0)) BT = bst.facilities.BoilerTurbogenerator( 'BT', ins=(S402 - 0, '', 'boiler_makeup_water', 'natural_gas', 'lime', 'boilerchems'), turbogenerator_efficiency=0.85) # tmo.Stream.default_ID_number = 700 CWP = bst.facilities.ChilledWaterPackage('CWP') CT = bst.facilities.CoolingTower('CT') CT.outs[1].T = 273.15 + 28 water_thermo = tmo.Thermo(tmo.Chemicals(['Water'])) process_water = tmo.Stream(ID='process_water', thermo=water_thermo) process_water_streams = (caustic, stripping_water, process_water1, process_water2, steam, BT - 1, CT - 1) def update_water_loss(): process_water.imol['Water'] = sum( [i.imol['Water'] for i in process_water_streams]) makeup_water = Stream('makeup_water', thermo=water_thermo, price=price['Makeup water']) PWC = bst.facilities.ProcessWaterCenter( 'PWC', ins=(S604 - 0, makeup_water), outs=(process_water, ''), makeup_water_streams=(makeup_water, ), process_water_streams=process_water_streams) Substance = tmo.Chemical.blank('Substance') Substance.at_state(phase='l') Substance.default() substance_thermo = tmo.Thermo(tmo.Chemicals([Substance])) CIP = Stream('CIP', thermo=substance_thermo, flow=(126 / 83333 * dryflow, )) CIP_package = units.CIPpackage('CIP_package', ins=CIP, thermo=substance_thermo) plant_air = Stream('plant_air', flow=(83333 / 83333 * dryflow, ), thermo=substance_thermo) ADP = bst.facilities.AirDistributionPackage('ADP', ins=plant_air, thermo=substance_thermo) FT = units.FireWaterTank('FT', ins=Stream('fire_water', flow=(8343 / 83333 * dryflow, ), thermo=substance_thermo), thermo=substance_thermo) ### Complete system wheatstraw_sys = System('wheatstraw_sys', path=(pretreatment_sys, fermentation_sys, ammonia_storage, S301, purification_sys, digestor_sys, WWT_sys), facilities=(CWP, BT, CT, update_water_loss, PWC, ADP, CIP_package, S301, ammonia_storage, FT)) return wheatstraw_sys
def create_chemicals(): ### Define common chemicals ### Biodiesel = tmo.Chemical('Biodiesel', search_ID='Methyl oleate') lipidcane_chemicals = tmo.Chemicals([ 'Water', 'Methanol', 'Ethanol', 'Glycerol', 'Glucose', 'Sucrose', 'H3PO4', 'P4O10', 'CO2', 'Octane', 'O2', Biodiesel, 'CH4' ]) (Water, Methanol, Ethanol, Glycerol, Glucose, Sucrose, H3PO4, P4O10, CO2, Octane, O2, Biodiesel, CH4) = lipidcane_chemicals O2.at_state(phase='g') CH4.at_state(phase='g') CO2.at_state(phase='g') H3PO4.at_state(phase='s') P4O10.at_state(phase='s') Glucose.at_state(phase='s') Sucrose.at_state(phase='s') ### Define new chemicals ### def create_new_chemical(ID, phase='s', **constants): solid = tmo.Chemical.blank(ID, phase=phase, **constants) lipidcane_chemicals.append(solid) return solid Ash = create_new_chemical('Ash', MW=1.) Cellulose = create_new_chemical( 'Cellulose', formula="C6H10O5", # Glucose monomer minus water Hf=-975708.8) Hemicellulose = create_new_chemical( 'Hemicellulose', formula="C5H8O5", # Xylose monomer minus water Hf=-761906.4) Flocculant = create_new_chemical('Flocculant', MW=1.) Lignin = create_new_chemical( 'Lignin', formula='C8H8O3', # Vainillin Hf=-452909.632) Solids = create_new_chemical('Solids', MW=1.) DryYeast = create_new_chemical('DryYeast', MW=1., CAS='Yeast') CaO = create_new_chemical('CaO', formula='CaO') HCl = create_new_chemical('HCl', formula='HCl') NaOH = create_new_chemical('NaOH', formula='NaOH') NaOCH3 = create_new_chemical('NaOCH3', formula='NaOCH3') Lipid = create_new_chemical('Lipid', phase='l', formula='C57H104O6', Hf=-2193.7e3) Lipid.Dortmund.set_group_counts_by_name({ 'CH3': 3, 'CH2': 41, 'CH': 1, 'CH=CH': 3, 'CH2COO': 3 }) ### Fill missing properties ### # Assume properties are similar for trioleate and tripalmitin Tripalmitin = tmo.Chemical('Tripalmitin').at_state(phase='l', copy=True) Lipid.copy_models_from(Tripalmitin, ['V', 'sigma', 'kappa', 'Cn']) # Assume a constant volume for lipid lipid_molar_volume = fn.rho_to_V(rho=900, MW=Lipid.MW) Lipid.V.add_model(lipid_molar_volume) # Insolubles occupy a significant volume insoluble_solids = (Ash, Cellulose, Hemicellulose, Flocculant, Lignin, Solids, DryYeast, P4O10) # Solubles don't occupy much volume soluble_solids = (CaO, HCl, NaOH, H3PO4, Glucose, Sucrose) for chemical in insoluble_solids: V = fn.rho_to_V(rho=1540, MW=chemical.MW) chemical.V.add_model(V, top_priority=True) for chemical in soluble_solids: V = fn.rho_to_V(rho=1e5, MW=chemical.MW) chemical.V.add_model(V, top_priority=True) # Assume sodium methoxide has some of the same properities as methanol LiquidMethanol = Methanol.at_state(phase='l', copy=True) NaOCH3.copy_models_from(LiquidMethanol, ['V', 'sigma', 'kappa', 'Cn']) # Add constant models for molar heat capacity of solids Ash.Cn.add_model(0.09 * 4.184 * Ash.MW) CaO.Cn.add_model(1.02388 * CaO.MW) Cellulose.Cn.add_model(1.364 * Cellulose.MW) Hemicellulose.Cn.add_model(1.364 * Hemicellulose.MW) Flocculant.Cn.add_model(4.184 * Flocculant.MW) Lignin.Cn.add_model(1.364 * Lignin.MW) Solids.Cn.add_model(1.100 * Solids.MW) for chemical in lipidcane_chemicals: chemical.default() lipidcane_chemicals.compile() lipidcane_chemicals.set_synonym('Water', 'H2O') return lipidcane_chemicals
def create_ethanol_subsystem_example(): """ Test BioSTEAM by creating a conventional sugarcane fermentation and ethanol purification process. Examples -------- >>> ethanol_sys = create_ethanol_subsystem_example() >>> # The sugarcane_example_subsystem flowsheet may help for accessing units >>> from biosteam import main_flowsheet as F >>> fs = F.flowsheet['ethanol_subsystem_example'] >>> fs.unit # Check unit operation registry Register: <Fermentation: R301> <StorageTank: T301> <VentScrubber: D301> <SolidsCentrifuge: C301> <Mixer: M302> <Pump: P301> <HXprocess: H302> <BinaryDistillation: D302> <Pump: P302> <Mixer: M303> <BinaryDistillation: D303> <Pump: P303> <HXutility: H303> <MolecularSieve: U301> >>> R301 = fs.unit.R301 # Get unit operation """ original_flowsheet = main_flowsheet.get_flowsheet() main_flowsheet.set_flowsheet('ethanol_subsystem_example') ### Create property package ### chemicals = tmo.Chemicals( ['Water', 'Ethanol', 'Glucose', 'Sucrose', 'H3PO4', 'P4O10', 'CO2', 'Octane', 'O2'] ) Water, Ethanol, Glucose, Sucrose, H3PO4, P4O10, CO2, Octane, O2 = chemicals CO2.at_state(phase='g') H3PO4.at_state(phase='s') P4O10.at_state(phase='s') Glucose.at_state(phase='s') Sucrose.at_state(phase='s') DryYeast = tmo.Chemical('DryYeast', MW=1., phase='s', search_db=False, default=True, CAS='Yeast') chemicals.append(DryYeast) Ash = tmo.Chemical('Ash', MW=1., search_db=False, phase='s', default=True) chemicals.append(Ash) # Insolubles occupy a significant volume insoluble_solids = (Ash, DryYeast) # Solubles don't occupy much volume soluble_solids = (H3PO4, Glucose, Sucrose) for chemical in insoluble_solids: V = fn.rho_to_V(rho=1540, MW=chemical.MW) chemical.V.add_model(V, top_priority=True) for chemical in soluble_solids: V = fn.rho_to_V(rho=1e5, MW=chemical.MW) chemical.V.add_model(V, top_priority=True) # Add constant models for molar heat capacity of solids Ash.Cn.add_model(0.09 * 4.184 * Ash.MW) for chemical in chemicals: chemical.default() bst.settings.set_thermo(chemicals) chemicals.set_synonym('Water', 'H2O') ### Create fresh streams ### # Fresh water stripping_water = bst.Stream('stripping_water', Water=5000, units='kg/hr') fermentation_feed = bst.Stream('fermentation_feed', phase='l', Glucose=21.11, Sucrose=125.9, Water=1370 + 4409, H3PO4=0.7575, DryYeast=1.03e+04) # Ethanol Production R301 = units.Fermentation('R301', outs=('CO2', ''), tau=9, efficiency=0.90, N=4) T301 = units.StorageTank('T301', tau=4, vessel_material='Carbon steel') T301.line = 'Beer tank' D301 = units.VentScrubber('D301', ins=(stripping_water, R301-0), gas=('CO2',)) # Separate 99% of yeast C301 = units.SolidsCentrifuge('C301', outs=('recycle_yeast', ''), split=(1, 0.99999, 1, 0.96, 0.01), order=('Ethanol', 'Glucose', 'H3PO4', 'Water', 'DryYeast'), solids=('DryYeast',)) C301.split[:] = 1. - C301.split # Mix in Water M302 = units.Mixer('M302') P301 = units.Pump('P301') # Heat up before beer column # Exchange heat with stillage H302 = units.HXprocess('H302', outs=('', 'stillage'), phase0='l', phase1='l', U=1.28) # Beer column x_bot = 3.910570816782338e-06 y_top = 0.34508430224337167 D302 = units.BinaryDistillation('D302', P=101325, y_top=y_top, x_bot=x_bot, k=1.25, LHK=('Ethanol', 'Water')) D302.tray_material = 'Stainless steel 304' D302.vessel_material = 'Stainless steel 304' D302.boiler.U = 1.85 P302 = units.Pump('P302') # Mix ethanol Recycle (Set-up) M303 = units.Mixer('M303') x_bot = 3.910570816782338e-06 y_top = 0.7906528373264998 D303 = units.BinaryDistillation('D303', P=101325, y_top=y_top, x_bot=x_bot, k=1.25, LHK=('Ethanol', 'Water'), tray_material='Stainless steel 304', vessel_material='Stainless steel 304', is_divided=True) D303.boiler.U = 1.85 P303 = units.Pump('P303') # Superheat vapor for mol sieve H303 = units.HXutility('H303', T=115+273.15, V=1) # Molecular sieve U301 = units.MolecularSieve('U301', split=(2165.14/13356.04, 1280.06/1383.85), order=('Ethanol', 'Water')) fermentation_feed-R301-1-T301-0-C301 (C301-1, D301-1)-M302-P301 (P301-0, P302-0)-H302-0-D302-1-P302 (D302-0, U301-0)-M303-0-D303-0-H303-U301 D303-1-P303 ethanol_subsystem_example = main_flowsheet.create_system('ethanol_subsystem_example') ethanol_subsystem_example.simulate() main_flowsheet.set_flowsheet(original_flowsheet) return ethanol_subsystem_example