-
Notifications
You must be signed in to change notification settings - Fork 53
Description
In my process simulation, after fermentation the broth containing different solutes and biomass (which is defined as a pseudo-compound) go through a decanter (assumed using gravity). After the decanter, the concentrated biomass has to be filtered. For this I need a pump (e.g. assuming a pressure filter) beforehand.
However, it seems the pump in BioSTEAM has an issue with the viscosity method 'NEGLECT_P' due to a component with CASRN 'None'. Likely this is the Biomass.
I would only need the pump for it's electricity usage, for my LCA afterwards.
_settings.py:
import thermosteam as tmo
import biosteam as bst
from _chemicals import chemicals
from thermosteam import settings
#Activate chemicals list & register them in bio/thermosteam
tmo.settings.set_thermo(chemicals)
bst.settings.set_thermo(chemicals)
_chemicals.py:
import thermosteam as tmo
import biosteam as bst
from thermosteam import Chemicals, Chemical
def create_chemicals():
chemicals = Chemicals([])
def add_chemical(ID, ref=None, **data):
chemical = Chemical(ID, **data) if ref is None else ref.copy(ID, **data)
chemicals.append(chemical)
return chemical
Water = add_chemical('H2O')
Glucose = add_chemical('Glucose', phase='l', rho=1560, Cp=1.213)
CaCO3 = add_chemical('CaCO3', phase='s', rho=2710, Cp=0.834, default=True)
CalciumLactate = add_chemical('CalciumLactate', phase='l', Hf=-1686100, rho=1494) # NOTE: Cp value not available (yet), filler could be ca. 1.4, Hf value probably off as for dissociated aq. species... makes whole fermentor endothermic...
lla = add_chemical('L-LacticAcid', phase='l', Hf=-686300, Cp=2.109, rho=1206)
SulfuricAcid = add_chemical('H2SO4', phase='l', rho=1840, Cp=1.38)
CarbonDioxide = add_chemical('CO2', phase='g')
Gypsum = add_chemical('CaSO4_2H2O', phase='s', rho=2320, MW=172.18, Hf=-2023000, Cp=1.081, CAS='10101-41-4', mu = 0.00095, search_db=False) # NOTE: Einstein-Equation gives 0.00095 sPa for Gypsum viscosity
Biomass_s = add_chemical('Biomass_s', phase='s', formula='CH1.8O0.5N0.2', MW=24.626, rho=1093, Hf=-130412, Cp=1.25, search_db=False)
nutrient_N = add_chemical('NH3', phase='l')
Octanol = add_chemical('Octanol', phase='l')
Trioctylamine = add_chemical('Trioctylamine', phase='l')
SodiumHydroxide = add_chemical('NaOH', phase='l')
SodiumSulfate = add_chemical('Na2SO4', phase='l', rho=1200, Cp=3.5, mu = 0.001, search_db=True)
chemicals.compile()
return chemicals
chemicals = create_chemicals()
_reactions.py:
import thermosteam as tmo
def fermentation_reaction():
fermentation = tmo.Reaction(reaction='Glucose -> 1.84 L-LacticAcid + 0.585 Biomass_s', reactant='Glucose', X=1, basis='mol', correct_atomic_balance=False)
return fermentation
def neutralization_reaction():
neutralization = tmo.Reaction(reaction='2 L-LacticAcid + CaCO3 -> CalciumLactate + CO2 + H2O', reactant='L-LacticAcid', X=1, basis='mol')
return neutralization
def acidification_reaction():
acidification = tmo.Reaction(reaction='CalciumLactate + H2SO4 -> 2 L-LacticAcid + CaSO4_2H2O', reactant='CalciumLactate', X=1, basis='mol')
return acidification
def acidification_2_reaction():
acidification_2 = tmo.Reaction(reaction='CaCO3 + H2SO4 -> CaSO4_2H2O + CO2 + H2O', reactant='CaCO3', X=1, basis='mol')
return acidification_2
def caustic_wash_reaction():
caustic_wash = tmo.Reaction(reaction='2 NaOH + H2SO4 -> Na2SO4 + 2 H2O', reactant='H2SO4', X=1, basis='mol')
return caustic_wash
_units.py:
import biosteam as bst
import thermosteam as tmo
from biosteam import CSTR
import numpy as np
from _reactions import fermentation_reaction, neutralization_reaction, acidification_reaction, acidification_2_reaction
# Custom Fermentor based on CSTR for cont. ferm.
class Fermentor(bst.CSTR):
_N_ins = 1
_N_outs = 2
def __init__(self, ID='Fermentor', ins=None, outs=(), thermo=None, *, T=323.15, **kwargs):
if outs == ():
outs = [None, None] # liquid, gas
super().__init__(ID, ins, outs, thermo=thermo, **kwargs)
self.T=T
self.fermentation_rxn = fermentation_reaction()
self.neutralization_rxn = neutralization_reaction()
def _run(self):
feed = self.ins[0]
liquid_effluent, gas_vent = self.outs
liquid_effluent.copy_like(feed)
# Apply reactions using the class-defined reactions, first parallel fermentation, then neutralization
self.fermentation_rxn(liquid_effluent.mol)
self.neutralization_rxn(liquid_effluent.mol)
# Handle gas separation - remove CO2 from liquid and send to gas vent
gas_vent.copy_flow(liquid_effluent, 'CO2', remove=True)
liquid_effluent.phase = 'l'
gas_vent.phase = 'g'
liquid_effluent.T = gas_vent.T = self.T
# Custom Acidification Reactor
class AcidificationReactor(bst.CSTR):
_N_ins = 2
_N_outs = 2
def __init__(self, ID='', ins=None, outs=(), P=101325, T=298.15, tau=1.0, V=1.0): # check assumptions
if outs == ():
outs = [None, None]
super().__init__(ID, ins, outs, tau=tau, T=T, P=P)
self.acidification_rxn = acidification_reaction()
self.acidification_2_rxn = acidification_2_reaction()
def _run(self):
feed, acid = self.ins
effluent, vent = self.outs
# Acid dosing
n_CL = feed.imol['CalciumLactate']
acid.imol['H2SO4'] = n_CL * 1.1
acid.imass['H2O'] = acid.imass['H2SO4'] / 0.93 * 0.07
# Mix and pre‑instantiate phases
effluent.mix_from([feed, acid])
effluent.phases = ('l', 's')
effluent.T = self.T
effluent.P = self.P
# Reactions
self.acidification_rxn(effluent)
self.acidification_2_rxn(effluent)
# CO2 Vent
vent.copy_flow(effluent, 'CO2', remove=True)
vent.phase = 'g'
vent.T = effluent.T
vent.P = effluent.P
# Force gypsum into solid phase (safety net)
n_total = effluent.imol['CaSO4_2H2O']
if n_total:
effluent.imol['l', 'CaSO4_2H2O'] = 0
effluent.imol['s', 'CaSO4_2H2O'] = n_total
# Keep outlet as slurry/liquid
effluent.phase = 'l'
_system.py:
import biosteam as bst
from _units import Fermentor, AcidificationReactor # custom classes
from _reactions import fermentation_reaction, neutralization_reaction, acidification_reaction, caustic_wash_reaction # reaction functions
from _settings import chemicals # compiled thermo package
# -------------------- FEED STREAMS FOR ALL UNITS --------------------
glucose_feed = bst.Stream('glucose_feed', Glucose=2476, Water=28483, units='kg/hr') # 80 g/L
CaCO3_feed = bst.Stream('CaCO3_feed', CaCO3=1277.8, Water=2373, units='kg/hr') # 0.92mol CaCO3 per 1mol Glucose (reactions) --> + excess 1% // 35wt% CaCO3 slurry
H2SO4_feed = bst.Stream('H2SO4_feed', units='kg/hr')
Organic_Solvent = bst.Stream('Organic_Solvent', Octanol=5340.6, Trioctylamine=5340.6, units='kg/hr') # 1.5 : 1 ratio -> org : aq phase
Extr_Water_1 = bst.Stream('Extraction_water_1', H2O=5494.6, units='kg/hr') # Patent Ratio 0.4 : 1 (Water kg : Extract kg)
Extr_Water_2 = bst.Stream('Extraction_water_2', H2O=4695.1, units='kg/hr') # Patent Ratio 0.4 : 1 (Water kg : Extract kg)
Caustic_Sol = bst.Stream('Caustic_Solution', NaOH=123.84, H2O=1548, units='kg/hr')
OrgSolvent_Makeup = bst.Stream('Solvent_Makeup', Octanol=53.2512, Trioctylamine=53.5608, units='kg/hr') # Add the 1% of solvent lost in caustic wash
Recycled_Solvent = bst.Stream('Recycled_Solvent')
# -------------------- SUBSYSTEM 1: CONVERSION --------------------
# Unit and stream definitions
with bst.System('conversion_sys') as conversion_sys:
feedstock_mixer = bst.Mixer('feedstock_mixer', ins=[glucose_feed, CaCO3_feed], outs=('crude_feed'))
preheater = bst.HXutility('preheater', feedstock_mixer.outs[0], outs=('heated_feed'), T=323.15) # NOTE: Placeholder 50°C
fermentor = Fermentor('fermentor', preheater.outs[0], outs=['fermented_broth', 'CO2'], P=101325, T=323.15, tau=72, V_wf=0.8, kW_per_m3=0.05, dT_hx_loop=35) # NOTE find source for possible kW_per_m3 value for anaerobic ferm.
ferm_cooler = bst.HXutility('fermentor_cooler', fermentor.outs[0], outs=('cooled_broth'), T=298.15)
# -------------------- SUBSYSTEM 2.1: SEPARATION --------------------
# Biomass Removal --> NOTE: Fine tuning of splits, realism vs. idealism
# Acidification & Gypsum Filtration
with bst.System('separation_sys') as separation_sys:
gravity_decanter = bst.units.SolidsSeparator('GravityDecanter',
ins=ferm_cooler.outs[0], outs=('decanter_underflow', 'decanter_overflow'),
split={'Biomass_s': 1, 'CaCO3': 0.85, 'CalciumLactate': 0.05}, # NOTE: FINE TUNING, HOW MUCH PRODUCT LOST?
moisture_content=0.35)
slurry_p = bst.Pump('slurry_pump', gravity_decanter.outs[0], outs=('pressurized_decanted_cake'), P=200000, pump_type='Default')
bm_Filter = bst.units.SolidsSeparator('BM_Filter',
ins=slurry_p.outs[0], outs=('BM_cake', 'BM_filtrate'),
split={'Biomass_s': 1, 'CaCO3': 0.75},
moisture_content=0.08)
filtrate_mixer = bst.Mixer('filtrate_mixer', ins=(gravity_decanter.outs[1], bm_Filter.outs[1]), outs='clarified_broth')
acidification_reactor = AcidificationReactor('AcidificationReactor', ins=[filtrate_mixer.outs[0], 'H2SO4_feed'], outs=('acidified_slurry', 'CO2'))
gypsum_filter = bst.units.SolidsSeparator('GypsumFilter', ins=acidification_reactor.outs[0], outs=('gypsum_cake', 'lactic_acid_solution'),
split={'CaSO4_2H2O': 0.99}, # Fine particles stay, removed by extraction
moisture_content=0.2)
# -------------------- SUBSYSTEM 2.2A: PURIFICATION --------------------
# MEE 1 to conc. extraction feed
# Extraction with organic amine solvent
# 2 Stage Back-Extraction into fresh water
# MEE 2 to 88wt% lla
with bst.System('purification_sys') as purification_sys:
Evaporator_1 = bst.units.MultiEffectEvaporator(ID='MEE_pre_extr', ins=[gypsum_filter.outs[1]], outs=('conc_la_sol', 'condensate'),
V=0.85, V_definition='Overall', # NOTE adjust to 30wt% lactic acid conc. before extraction for eff.
P=(101325, 90000, 70000))
ExtrMixer1 = bst.LiquidsMixingTank(ID='Extraction_M1', ins=[Evaporator_1.outs[0], Organic_Solvent], outs=['mixed_phase_1'])
PreExtr_Cooler = bst.HXutility('extraction_cooler', ins=ExtrMixer1.outs[0], T=313.15) # 40°C extr feed for optimal eff.
ExtrSettler1 = bst.LiquidsSplitSettler(ID='Extraction_S1', ins=[PreExtr_Cooler.outs[0]], outs=['organic_phase_1', 'aq_phase_1'],
split={'L-LacticAcid': 0.99, # Simulate two stage extraction in one
'H2SO4': 0.98,
'Octanol': 1,
'Trioctylamine': 1,
'H2O': 0.01, # some water carry-over
'CaCO3': 0,
'CaSO4_2H2O': 0,
'NH3' : 0}) # NOTE "realistic" assumptions of split
BackExtrMixer1 = bst.LiquidsMixingTank('BackExtr_Mixer1', ins=[ExtrSettler1.outs[0], Extr_Water_1])
PreBackExtr_Heater1 = bst.HXutility('backextr_heater1', ins=BackExtrMixer1.outs[0], T=348.15) # ca. 20-30°C higher T for Back Extraction
BackExtrSettler1 = bst.LiquidsSplitSettler('BackExtr_Settler1',
ins=PreBackExtr_Heater1.outs[0],
outs=('back_extr_aq1', 'back_extr_org1'),
split={'L-LacticAcid': 0.85,
'H2SO4': 0,
'H2O': 0.99,
'Octanol': 0,
'Trioctylamine': 0})
BackExtrMixer2 = bst.LiquidsMixingTank('BackExtr_Mixer2', ins=[BackExtrSettler1.outs[1], Extr_Water_2])
PreBackExtr_Heater2 = bst.HXutility('backextr_heater2', ins=BackExtrMixer2.outs[0], T=348.15) # ca. 20-30°C higher T for Back Extraction
BackExtrSettler2 = bst.LiquidsSplitSettler('BackExtr_Settler2',
ins=PreBackExtr_Heater2.outs[0],
outs=('back_extr_aq2', 'back_extr_org2'),
split={'L-LacticAcid': 0.8, # 80% of remaining LLA (total 97%)
'H2SO4': 0,
'H2O': 0.99,
'Octanol': 0,
'Trioctylamine': 0})
AqPhaseMixer = bst.MixTank('PostExtraction_AqPhase', ins=[BackExtrSettler1.outs[0], BackExtrSettler2.outs[0]], outs='pure_lactic_acid_solution')
# -------------------- SUBSYSTEM 2.2B: SOLVENT RECOVERY & RECYCLING --------------------
#Organic solvent washed (neutralized) with aq. NaOH solution
Caustic_Mixer = bst.LiquidsMixingTank('Caustic_Mixer', ins=[BackExtrSettler2.outs[1], Caustic_Sol], outs=('caustic_mix'))
CausticWash_Reactor = bst.SinglePhaseReactor('Caustic_Wash', ins=Caustic_Mixer.outs[0], outs=('neutralized_solvent'),
T=323.15, P=101325, V_wf=0.8,
tau=0.5,
reaction=caustic_wash_reaction())
Caustic_Settler = bst.LiquidsSplitSettler('Caustic_Settler',
ins=CausticWash_Reactor.outs[0],
outs=('caustic_w_waste_brine', 'recovered_solvent'),
split={
'Octanol': 0.01,
'Trioctylamine': 0.01,
'NaOH': 1,
'Na2SO4': 1,
'H2O': 1,
'L-LacticAcid': 1})
OrgSolvent_Recycler = bst.Mixer('Solvent_Mixer', ins=[Caustic_Settler.outs[1], OrgSolvent_Makeup], outs=Organic_Solvent)
# MEE 2
Evaporator_2 = bst.MultiEffectEvaporator(ID='MEE_post_extr', ins=[AqPhaseMixer.outs[0]], outs=('product', 'condensate'),
V=0.95, V_definition='Overall', P=(101325, 80000, 60000, 30000)) # NOTE Fine tuning
Evaporator_2.split={
'H2O': 1,
'L-LacticAcid': 0}
# Finisher Flash for 88wt%
Evaporator_3 = bst.Flash(ID='FlashEvaporator', ins=Evaporator_2.outs[0], outs=('condensate', '88wt_product'), P=30000, V=0.414) # Tuned to give 88wt% LLA
# -------------------- MAIN SYSTEM --------------------
# Combine subsystems into the main system
lactic_acid_system = bst.System('lactic_acid_system',
path=[conversion_sys, separation_sys, purification_sys],
recycle=[Organic_Solvent])
# SIMULATION
if __name__ == '__main__':
lactic_acid_system.simulate()
print("Simulation completed!")
gravity_decanter.outs[0].show()
gravity_decanter.outs[1].show()
bm_Filter.outs[0].show()
bm_Filter.outs[1].show()
ERROR:
Traceback (most recent call last):
File "c:\Users\ivano\Desktop\Masterarbeit\Code\_system.py", line 136, in <module>
lactic_acid_system.simulate()
File "C:\Users\ivano\miniconda3\envs\glukose-env\Lib\site-packages\biosteam\_system.py", line 3105, in simulate
with self.flowsheet:
File "C:\Users\ivano\miniconda3\envs\glukose-env\Lib\site-packages\biosteam\_flowsheet.py", line 120, in __exit__
if exception: raise exception
^^^^^^^^^^^^^^^
File "C:\Users\ivano\miniconda3\envs\glukose-env\Lib\site-packages\biosteam\_system.py", line 3166, in simulate
raise error
File "C:\Users\ivano\miniconda3\envs\glukose-env\Lib\site-packages\biosteam\_system.py", line 3154, in simulate
if design_and_cost: self._summary()
^^^^^^^^^^^^^^^
File "C:\Users\ivano\miniconda3\envs\glukose-env\Lib\site-packages\biosteam\_system.py", line 2862, in _summary
f(i, i._summary)
File "C:\Users\ivano\miniconda3\envs\glukose-env\Lib\site-packages\thermosteam\exceptions.py", line 94, in try_method_with_object_stamp
raise_error_with_object_stamp(object, error)
File "C:\Users\ivano\miniconda3\envs\glukose-env\Lib\site-packages\thermosteam\exceptions.py", line 84, in raise_error_with_object_stamp
raise error
File "C:\Users\ivano\miniconda3\envs\glukose-env\Lib\site-packages\thermosteam\exceptions.py", line 88, in try_method_with_object_stamp
return method(*args)
^^^^^^^^^^^^^
File "C:\Users\ivano\miniconda3\envs\glukose-env\Lib\site-packages\biosteam\_system.py", line 2862, in _summary
f(i, i._summary)
File "C:\Users\ivano\miniconda3\envs\glukose-env\Lib\site-packages\thermosteam\exceptions.py", line 94, in try_method_with_object_stamp
raise_error_with_object_stamp(object, error)
File "C:\Users\ivano\miniconda3\envs\glukose-env\Lib\site-packages\thermosteam\exceptions.py", line 84, in raise_error_with_object_stamp
raise error
File "C:\Users\ivano\miniconda3\envs\glukose-env\Lib\site-packages\thermosteam\exceptions.py", line 88, in try_method_with_object_stamp
return method(*args)
^^^^^^^^^^^^^
File "C:\Users\ivano\miniconda3\envs\glukose-env\Lib\site-packages\biosteam\_unit.py", line 1191, in _summary
self._design(**design_kwargs) if design_kwargs else self._design()
^^^^^^^^^^^^^^
File "C:\Users\ivano\miniconda3\envs\glukose-env\Lib\site-packages\biosteam\units\_pump.py", line 175, in _design
nu = si.nu
^^^^^
File "C:\Users\ivano\miniconda3\envs\glukose-env\Lib\site-packages\thermosteam\_stream.py", line 1450, in nu
mu = self.mu
^^^^^^^
File "C:\Users\ivano\miniconda3\envs\glukose-env\Lib\site-packages\thermosteam\_stream.py", line 1415, in mu
return self._get_property('mu')
^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\ivano\miniconda3\envs\glukose-env\Lib\site-packages\thermosteam\_stream.py", line 1351, in _get_property
property_cache[name] = value = calculate(
^^^^^^^^^^
File "C:\Users\ivano\miniconda3\envs\glukose-env\Lib\site-packages\thermosteam\mixture\ideal_mixture_model.py", line 62, in __call__
return sum([j * models[i](phase, T, P) for i, j in mol.dct.items()])
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\ivano\miniconda3\envs\glukose-env\Lib\site-packages\thermosteam\mixture\ideal_mixture_model.py", line 62, in <listcomp>
return sum([j * models[i](phase, T, P) for i, j in mol.dct.items()])
^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\ivano\miniconda3\envs\glukose-env\Lib\site-packages\thermosteam\base\phase_handle.py", line 131, in __call__
return self.model(T, P)
^^^^^^^^^^^^^^^^
File "C:\Users\ivano\miniconda3\envs\glukose-env\Lib\site-packages\thermosteam\thermo\tp_dependent_property.py", line 44, in __call__
return self.TP_dependent_property(T, P)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\ivano\miniconda3\envs\glukose-env\Lib\site-packages\thermo\utils\tp_dependent_property.py", line 288, in TP_dependent_property
raise RuntimeError(f"{self.name} method '{method_P}' is not valid at T={T} K and P={P} Pa for component with CASRN '{self.CASRN}'")
RuntimeError: <System: separation_sys> <Pump: slurry_pump> liquid viscosity method 'NEGLECT_P' is not valid at T=298.15 K and P=101325.0 Pa for component with CASRN 'None'
What should happen is that the pump increases the pressure of the fermentation broth to 200 kPa for the filter to work. Obviously a centrifugal pump wouldn't make sense here as the biomass is very dense (low moisture and solute content).
- Is there a way to make the pump work or would you recommend another approach for such dense mixtures (and pseudo compound)?
Alternatively I will have to use a ScrewPress, but then I will neglect the electricity usage in my LCA and it won't be accurate in terms of representing the process description I'm using.