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.
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:
_chemicals.py:
_reactions.py:
_units.py:
_system.py:
ERROR:
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).
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.