# -*- coding: utf-8 -*-
"""
.. contents:: :local:
Reactors
--------
.. autoclass:: biorefineries.cellulosic.units.PretreatmentReactorSystem
.. autoclass:: biorefineries.cellulosic.units.SeedTrain
.. autoclass:: biorefineries.cellulosic.units.ContinuousPresaccharification
.. autoclass:: biorefineries.cellulosic.units.Saccharification
.. autoclass:: biorefineries.cellulosic.units.CoFermentation
.. autoclass:: biorefineries.cellulosic.units.SaccharificationAndCoFermentation
.. autoclass:: biorefineries.cellulosic.units.SimultaneousSaccharificationAndCoFermentation
Separations
-----------
.. autoclass:: biorefineries.cellulosic.units.ReverseOsmosis
.. autoclass:: biorefineries.cellulosic.units.Nanofilter
.. autoclass:: biorefineries.cellulosic.units.HydrolyzateSolidLiquidSeparator
.. autoclass:: biorefineries.cellulosic.units.PretreatmentFlash
Feedstock handling
------------------
.. autoclass:: biorefineries.cellulosic.units.FeedStockHandling
Tanks
-----
.. autoclass:: biorefineries.cellulosic.units.AmmoniaStorageTank
.. autoclass:: biorefineries.cellulosic.units.AmmoniaReacidificationTank
.. autoclass:: biorefineries.cellulosic.units.BeerTank
.. autoclass:: biorefineries.cellulosic.units.CSLStorageTank
.. autoclass:: biorefineries.cellulosic.units.DAPStorageTank
.. autoclass:: biorefineries.cellulosic.units.AmmoniaAdditionTank
.. autoclass:: biorefineries.cellulosic.units.AmmoniaMixer
.. autoclass:: biorefineries.cellulosic.units.EnzymeHydrolysateMixer
.. autoclass:: biorefineries.cellulosic.units.SulfuricAcidMixer
.. autoclass:: biorefineries.cellulosic.units.SeedHoldTank
.. autoclass:: biorefineries.cellulosic.units.SulfuricAcidStorageTank
.. autoclass:: biorefineries.cellulosic.units.SulfuricAcidTank
.. autoclass:: biorefineries.cellulosic.units.OligomerConversionTank
Heat exchange
-------------
.. autoclass:: biorefineries.cellulosic.units.HydrolysateHeatExchanger
.. autoclass:: biorefineries.cellulosic.units.PretreatmentWasteHeater
.. autoclass:: biorefineries.cellulosic.units.WasteVaporCondenser
Pumps
-----
.. autoclass:: biorefineries.cellulosic.units.HydrolysatePump
.. autoclass:: biorefineries.cellulosic.units.BlowdownDischargePump
.. autoclass:: biorefineries.cellulosic.units.HydrolyzatePump
.. autoclass:: biorefineries.cellulosic.units.ReacidifiedHydrolyzatePump
"""
from biosteam.units.design_tools.geometry import cylinder_diameter_from_volume
from thermosteam import MultiStream
from biosteam import Unit
from biosteam.units.decorators import cost
from biosteam.units.design_tools import size_batch
import thermosteam as tmo
import biosteam as bst
import numpy as np
__all__ = (
'PretreatmentReactorSystem',
'SeedTrain',
'ContinuousPresaccharification',
'Saccharification',
'CoFermentation',
'SaccharificationAndCoFermentation',
'SimultaneousSaccharificationAndCoFermentation',
'ReverseOsmosis',
'Nanofilter',
'HydrolysatePump',
'AmmoniaStorageTank',
'AmmoniaReacidificationTank',
'BeerTank',
'BlowdownDischargePump',
'CSLStorageTank',
'DAPStorageTank',
'FeedStockHandling',
'PretreatmentFlash',
'HydrolysateHeatExchanger',
'PretreatmentWasteHeater',
'WasteVaporCondenser',
'HydrolyzatePump',
'HydrolyzateSolidLiquidSeparator',
'AmmoniaAdditionTank',
'AmmoniaMixer',
'EnzymeHydrolysateMixer',
'SulfuricAcidMixer',
'OligomerConversionTank',
'ReacidifiedHydrolyzatePump',
'SeedHoldTank',
'SulfuricAcidStorageTank',
'SulfuricAcidTank',
)
Rxn = tmo.reaction.Reaction
ParallelRxn = tmo.reaction.ParallelReaction
# %% Constants
_gal2m3 = 0.003785
_gpm2m3hr = 0.227124
# _m3hr2gpm = 4.40287
_hp2kW = 0.7457
_Gcal2kJ = 4184e3
# %% Pretreatment
[docs]
@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, T=130+273.15, tau=0.166, V_wf=0.8, length_to_diameter=2,
vessel_material='Stainless steel 316',
vessel_type='Horizontal',
reactions=None,
run_vle=True):
self._load_components()
vapor, liquid = self.outs
vapor.phase = 'g'
self.T = T
chemicals = self.chemicals
if reactions is None:
self.reactions = ParallelRxn([
# Reaction definition Reactant Conversion
Rxn('Glucan + H2O -> Glucose', 'Glucan', 0.0990, chemicals),
Rxn('Glucan + H2O -> GlucoseOligomer', 'Glucan', 0.0030, chemicals),
Rxn('Glucan -> HMF + 2 H2O', 'Glucan', 0.0030, chemicals),
Rxn('Galactan + H2O -> GalactoseOligomer', 'Galactan', 0.0240, chemicals),
Rxn('Galactan -> HMF + 2 H2O', 'Galactan', 0.0030, chemicals),
Rxn('Mannan + H2O -> MannoseOligomer', 'Mannan', 0.0030, chemicals),
Rxn('Mannan -> HMF + 2 H2O', 'Mannan', 0.0030, chemicals),
Rxn('Sucrose -> HMF + Glucose + 2H2O', 'Sucrose', 1.0000, chemicals),
Rxn('Xylan + H2O -> Xylose', 'Xylan', 0.9000, chemicals),
Rxn('Xylan + H2O -> XyloseOligomer', 'Xylan', 0.0240, chemicals),
Rxn('Xylan -> Furfural + 2 H2O', 'Xylan', 0.0500, chemicals),
Rxn('Arabinan + H2O -> Arabinose', 'Arabinan', 0.9000, chemicals),
Rxn('Arabinan + H2O -> ArabinoseOligomer', 'Arabinan', 0.0240, chemicals),
Rxn('Arabinan -> Furfural + 2 H2O', 'Arabinan', 0.0050, chemicals),
Rxn('Acetate -> AceticAcid', 'Acetate', 1.0000, chemicals),
Rxn('Lignin -> SolubleLignin', 'Lignin', 0.0500, 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, H=ms.H)
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()
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()
[docs]
@cost('Flow rate', 'Pumps',
S=43149, CE=522, cost=24800, n=0.8, kW=40, BM=2.3)
@cost('Stage #1 reactor volume', 'Stage #1 reactors',
cost=37700, S=20*_gal2m3, CE=522, n=0.7, BM=1.8)
@cost('Stage #2 reactor volume', 'Stage #2 reactors',
cost=58300, S=200*_gal2m3, CE=522, n=0.7, BM=1.8)
@cost('Stage #3 reactor volume', 'Stage #3 reactors',
cost=78800, S=2e3*_gal2m3, CE=522, n=0.7, BM=1.8)
@cost('Stage #4 reactor volume', 'Stage #4 reactors',
cost=176e3, S=20e3*_gal2m3, CE=522, n=0.7, BM=1.8)
@cost('Stage #4 reactor volume', 'Stage #4 agitators',
cost=26e3/2, S=20e3*_gal2m3, kW=7.5, CE=522, n=0.5, BM=1.5)
@cost('Stage #5 reactor volume', 'Stage #5 reactors',
cost=590e3, S=200e3*_gal2m3, CE=522, n=0.7, BM=1.8)
@cost('Stage #5 reactor volume', 'Stage #5 agitators',
cost=43e3/2, S=200e3*_gal2m3, kW=10, CE=522, n=0.5, BM=1.5)
class SeedTrain(Unit):
_N_ins = 1
_N_outs = 2
_ins_size_is_fixed = False
_units= {'Flow rate': 'kg/hr',
'Stage #1 reactor volume': 'm3',
'Stage #2 reactor volume': 'm3',
'Stage #3 reactor volume': 'm3',
'Stage #4 reactor volume': 'm3',
'Stage #5 reactor volume': 'm3'}
@property
def N_stages(self):
"""Number of stages in series."""
return 5
#: Number of parallel seed trains
N_trains = 2
#: Cycle time for each batch (hr)
tau_batch = 24
@property
def tau_turnover(self):
"""Turnover time (hr) calculated by batch time divided by number of trains."""
return self.tau_batch/self.N_trains
#: Operating temperature (K)
T = 32+273.15
# #: wt % media (e.g. corn steep liquor) in each stage
# media_loading = 0.50
# #: Diammonium phosphate loading in g/L of fermentation broth
# DAP = 0.67
def _init(self, reactions=None, saccharification=None):
chemicals = self.chemicals
if reactions is None:
self.reactions = ParallelRxn([
# Reaction definition Reactant Conversion
Rxn('Glucose -> 2 Ethanol + 2 CO2', 'Glucose', 0.9000, chemicals),
Rxn('Glucose + 0.047 CSL + 0.018 DAP -> 6 Z_mobilis + 2.4 H2O',
'Glucose', 0.0400, chemicals),
Rxn('Glucose + 2 H2O -> 2 Glycerol + O2', 'Glucose', 0.0040, chemicals),
Rxn('Glucose + 2 CO2 -> 2 SuccinicAcid + O2', 'Glucose', 0.0060, chemicals),
Rxn('3 Xylose -> 5 Ethanol + 5 CO2', 'Xylose', 0.8000, chemicals),
Rxn('Xylose + 0.039 CSL + 0.015 DAP -> 5 Z_mobilis + 2 H2O',
'Xylose', 0.0400, chemicals),
Rxn('3 Xylose + 5 H2O -> 5 Glycerol + 2.5 O2', 'Xylose', 0.0030, chemicals),
Rxn('Xylose + H2O -> Xylitol + 0.5 O2', 'Xylose', 0.0460, chemicals),
Rxn('3 Xylose + 5 CO2 -> 5 SuccinicAcid + 2.5 O2', 'Xylose', 0.0090, chemicals)
])
self.glucose_to_ethanol = self.reactions[0]
self.xylose_to_ethanol = self.reactions[4]
self.glucose_to_byproducts = self.reactions[1:4]
self.xylose_to_byproducts = self.reactions[5:]
else:
self.reactions = reactions
if callable(saccharification):
self.saccharification = saccharification
elif saccharification:
self.saccharification = ParallelRxn([
Rxn('Glucan -> GlucoseOligomer', 'Glucan', 0.0400, chemicals),
Rxn('Glucan + 0.5 H2O -> 0.5 Cellobiose', 'Glucan', 0.0120, chemicals),
Rxn('Glucan + H2O -> Glucose', 'Glucan', 0.9000, chemicals),
Rxn('Cellobiose + H2O -> 2Glucose', 'Cellobiose', 1.0000, chemicals)]
)
else:
self.saccharification = None
_setup = Unit._setup
def _run(self):
vent, effluent= self.outs
effluent.mix_from(self.ins, energy_balance=False)
if self.saccharification:
self.saccharification(effluent)
self.reactions.force_reaction(effluent)
effluent.empty_negative_flows()
effluent.T = self.T
vent.phase = 'g'
vent.copy_flow(effluent, ('CO2', 'O2'), remove=True)
def _design(self):
maxvol = self.outs[1].F_vol*self.tau_turnover
vol = maxvol*10**-self.N_stages
Design = self.design_results
for i in range(1, self.N_stages+1):
Design[f'Stage #{i} reactor volume'] = vol
vol *= 10
Design['Flow rate'] = sum([i.F_mass for i in self.outs])
self.add_heat_utility(self.Hnet, self.T)
def _cost(self):
N = self.N_trains
D = self.design_results
C = self.baseline_purchase_costs
kW = 0
for i, x in self.cost_items.items():
S = D[x._basis]
q = S/x.S
C[i] = N*bst.CE/x.CE*x.cost*q**x.n
kW += N*x.kW*q
self.power_utility(kW)
# %% Saccharification and fermentation (consolidated bioprocess)
[docs]
@cost('Flow rate', 'Transfer pumps', kW=58, S=352*_gpm2m3hr,
cost=47200/5, CE=522, n=0.8, BM=2.3, N='N_transfer_pumps')
@cost('Tank volume', 'Tanks', cost=3840e3/8, S=250e3*_gal2m3,
CE=522, n=0.7, BM=2.0, N='N_tanks')
class ContinuousPresaccharification(Unit):
_N_ins = 1
_N_outs = 1
#: Residence time of countinuous saccharification tanks (hr)
tau_tank = 24
#: Number of continuous saccharification tanks
N_tanks = 8
#: Number of transfer pumps
N_transfer_pumps = 5
#: Working volume fraction (filled tank to total tank volume)
V_wf = 0.9
_units = {'Flow rate': 'm3/hr',
'Tank volume': 'm3'}
def _init(self, P=101325, reactions=None):
self.P = P
self.reactions = reactions
def _run(self):
inlet = self.ins[0]
outlet = self.outs[0]
outlet.copy_flow(inlet)
outlet.T = inlet.T
outlet.P = self.P
if self.reactions: self.reactions.adiabatic_reaction(outlet)
def _design(self):
inlet = self.ins[0]
v_0 = inlet.F_vol
Design = self.design_results
Design['Tank volume'] = v_0 * self.tau_tank / self.V_wf / self.N_tanks
Design['Flow rate'] = v_0 / self.N_transfer_pumps
[docs]
class Saccharification(bst.BatchBioreactor):
_N_ins = 1
_N_outs = 1
#: Unload and clean up time (hr)
tau_0 = 4
#: Working volume fraction (filled tank to total tank volume)
V_wf = 0.9
_units = {'Flow rate': 'm3/hr',
'Reactor volume': 'm3',
'Reactor duty': 'kJ/hr'}
def _init(self, tau=72, N=None, V=3785.4118, T=48+273.15, P=101325,
Nmin=2, Nmax=36, reactions=None):
super()._init(tau, N, V, T, P, Nmin, Nmax)
chemicals = self.chemicals
#: [ParallelReaction] Enzymatic hydrolysis reactions including from
#: downstream batch tank in co-fermentation.
self.reactions = reactions or ParallelRxn([
Rxn('Glucan -> GlucoseOligomer', 'Glucan', 0.0400, chemicals),
Rxn('Glucan + 0.5 H2O -> 0.5 Cellobiose', 'Glucan', 0.0120, chemicals),
Rxn('Glucan + H2O -> Glucose', 'Glucan', 0.9000, chemicals),
Rxn('Cellobiose + H2O -> 2Glucose', 'Cellobiose', 1.0000, chemicals)]
)
_setup = Unit._setup
@property
def saccharification(self):
return self.reactions
@property
def vent(self):
return None
@property
def effluent(self):
return self.outs[0]
def _run(self):
feed, = self.ins
effluent, = self.outs
effluent.copy_flow(feed)
effluent.T = self.T
effluent.P = self.P
self.reactions(effluent)
[docs]
class CoFermentation(bst.BatchBioreactor):
_N_ins = 1
_ins_size_is_fixed = False
#: Unload and clean up time (hr)
tau_0 = 4
def _init(self, tau=36, N=None, V=3785.4118, T=305.15, P=101325,
Nmin=2, Nmax=36, cofermentation=None, loss=None):
super()._init(tau, N, V, T, P, Nmin, Nmax)
self.P = P
chemicals = self.chemicals
self.loss = loss or ParallelRxn([
# Reaction definition Reactant Conversion
Rxn('Glucose -> 2 LacticAcid', 'Glucose', 0.0300, chemicals),
Rxn('3 Xylose -> 5 LacticAcid', 'Xylose', 0.0300, chemicals),
Rxn('3 Arabinose -> 5 LacticAcid', 'Arabinose', 0.0300, chemicals),
Rxn('Galactose -> 2 LacticAcid', 'Galactose', 0.0300, chemicals),
Rxn('Mannose -> 2 LacticAcid', 'Mannose', 0.0300, chemicals),
])
if cofermentation is None:
self.cofermentation = ParallelRxn([
# Reaction definition Reactant Conversion
Rxn('Glucose -> 2 Ethanol + 2 CO2', 'Glucose', 0.9500, chemicals),
Rxn('Glucose + 0.047 CSL + 0.018 DAP -> 6 Z_mobilis + 2.4 H2O', 'Glucose', 0.0200, chemicals),
Rxn('Glucose + 2 H2O -> 2 Glycerol + O2', 'Glucose', 0.0040, chemicals),
Rxn('Glucose + 2 CO2 -> 2 SuccinicAcid + O2', 'Glucose', 0.0060, chemicals),
Rxn('3 Xylose -> 5 Ethanol + 5 CO2', 'Xylose', 0.8500, chemicals),
Rxn('Xylose + 0.039 CSL + 0.015 DAP -> 5 Z_mobilis + 2 H2O',
'Xylose', 0.0190, chemicals),
Rxn('3 Xylose + 5 H2O -> 5 Glycerol + 2.5 O2', 'Xylose', 0.0030, chemicals),
Rxn('Xylose + H2O -> Xylitol + 0.5 O2', 'Xylose', 0.0460, chemicals),
Rxn('3 Xylose + 5 CO2 -> 5 SuccinicAcid + 2.5 O2', 'Xylose', 0.0090, chemicals),
])
self.glucose_to_ethanol = self.cofermentation[0]
self.xylose_to_ethanol = self.cofermentation[4]
self.glucose_to_byproducts = self.cofermentation[1:4]
self.xylose_to_byproducts = self.cofermentation[5:]
else:
self.cofermentation = cofermentation
if 'CSL' in self.chemicals:
self.CSL_to_constituents = Rxn(
'CSL -> 0.5 H2O + 0.25 LacticAcid + 0.25 Protein', 'CSL', 1.0000, chemicals, basis='wt',
)
self.CSL_to_constituents.basis = 'mol'
else:
self.CSL_to_constituents = None
if all([i in self.chemicals for i in ('FFA', 'DAG', 'TAG', 'Glycerol')]):
self.oil_reaction = self.lipid_reaction = ParallelRxn([
Rxn('TAG + 3 Water -> 3FFA + Glycerol', 'TAG', 0.23, chemicals),
Rxn('TAG + Water -> FFA + DAG', 'TAG', 0.02, chemicals)
])
else:
self.oil_reaction = self.lipid_reaction = None
def _run(self):
feeds = self.ins
vent, effluent = self.outs
vent.P = effluent.P = self.P
vent.T = effluent.T = self.T
vent.phase = 'g'
effluent.mix_from(feeds, energy_balance=False)
if self.loss: self.loss(effluent)
if self.lipid_reaction:
self.lipid_reaction.force_reaction(effluent)
effluent.empty_negative_flows()
self.cofermentation(effluent)
if self.CSL_to_constituents: self.CSL_to_constituents(effluent)
vent.empty()
vent.receive_vent(effluent, energy_balance=False)
[docs]
@cost('Flow rate', 'Recirculation pumps', kW=30, S=340*_gpm2m3hr,
cost=47200, n=0.8, BM=2.3, CE=522, N='N_reactors')
@cost('Reactor duty', 'Heat exchangers', CE=522, cost=23900,
S=-5*_Gcal2kJ, n=0.7, BM=2.2, N='N_reactors') # Based on a similar heat exchanger
@cost('Batch duty', 'Fermentor batch cooler', CE=522, cost=86928,
S=-5*_Gcal2kJ, n=0.7, BM=1.8) # Based on a similar heat exchanger
@cost('Reactor volume', 'Agitators', CE=522, cost=52500,
S=1e6*_gal2m3, n=0.5, kW=90, BM=1.5, N='N_reactors')
@cost('Reactor volume', 'Reactors', CE=522, cost=844000,
S=1e6*_gal2m3, n=0.5, BM=1.5, N='N_reactors')
class SaccharificationAndCoFermentation(Unit):
_N_ins = 1
_N_outs = 3
_ins_size_is_fixed = False
#: Saccharification temperature (K)
T_saccharification = 48+273.15
#: Fermentation temperature (K)
T_fermentation = 32+273.15
#: Saccharification time (hr)
tau_saccharification = 60
#: Co-Fermentation time (hr)
tau_cofermentation = 36
#: Unload and clean up time (hr)
tau_0 = 4
#: Working volume fraction (filled tank to total tank volume)
V_wf = 0.9
#: Number of reactors
N_reactors = 12
_units = {'Flow rate': 'm3/hr',
'Reactor volume': 'm3',
'Batch duty': 'kJ/hr',
'Reactor duty': 'kJ/hr'}
def _init(self, P=101325, saccharification_split=0.1,
saccharification=None, loss=None, cofermentation=None):
self.P = P
self.saccharification_split = saccharification_split
chemicals = self.chemicals
#: [ParallelReaction] Enzymatic hydrolysis reactions including from
#: downstream batch tank in co-fermentation.
self.saccharification = saccharification or ParallelRxn([
Rxn('Glucan -> GlucoseOligomer', 'Glucan', 0.0400, chemicals),
Rxn('Glucan + 0.5 H2O -> 0.5 Cellobiose', 'Glucan', 0.0120, chemicals),
Rxn('Glucan + H2O -> Glucose', 'Glucan', 0.9000, chemicals),
Rxn('Cellobiose + H2O -> 2Glucose', 'Cellobiose', 1.0000, chemicals)]
)
self.loss = loss or ParallelRxn([
# Reaction definition Reactant Conversion
Rxn('Glucose -> 2 LacticAcid', 'Glucose', 0.0300, chemicals),
Rxn('3 Xylose -> 5 LacticAcid', 'Xylose', 0.0300, chemicals),
Rxn('3 Arabinose -> 5 LacticAcid', 'Arabinose', 0.0300, chemicals),
Rxn('Galactose -> 2 LacticAcid', 'Galactose', 0.0300, chemicals),
Rxn('Mannose -> 2 LacticAcid', 'Mannose', 0.0300, chemicals),])
self.cofermentation = cofermentation or ParallelRxn([
# Reaction definition Reactant Conversion
Rxn('Glucose -> 2 Ethanol + 2 CO2', 'Glucose', 0.9500, chemicals),
Rxn('Glucose + 0.047 CSL + 0.018 DAP -> 6 Z_mobilis + 2.4 H2O', 'Glucose', 0.0200, chemicals),
Rxn('Glucose + 2 H2O -> 2 Glycerol + O2', 'Glucose', 0.0040, chemicals),
Rxn('Glucose + 2 CO2 -> 2 SuccinicAcid + O2', 'Glucose', 0.0060, chemicals),
Rxn('3 Xylose -> 5 Ethanol + 5 CO2', 'Xylose', 0.8500, chemicals),
Rxn('Xylose + 0.039 CSL + 0.015 DAP -> 5 Z_mobilis + 2 H2O',
'Xylose', 0.0190, chemicals),
Rxn('3 Xylose + 5 H2O -> 5 Glycerol + 2.5 O2', 'Xylose', 0.0030, chemicals),
Rxn('Xylose + H2O -> Xylitol + 0.5 O2', 'Xylose', 0.0460, chemicals),
Rxn('3 Xylose + 5 CO2 -> 5 SuccinicAcid + 2.5 O2', 'Xylose', 0.0090, chemicals),
])
self.CSL_to_constituents = Rxn(
'CSL -> 0.5 H2O + 0.25 LacticAcid + 0.25 Protein', 'CSL', 1.0000, chemicals, basis='wt',
)
self.CSL_to_constituents.basis = 'mol'
def _run(self):
feed, *other = self.ins
vent, effluent, sidedraw = self.outs
effluent.copy_like(feed)
vent.P = effluent.P = self.P
vent.T = effluent.T = self.T_fermentation
vent.phase = 'g'
self.saccharification(effluent)
self._batch_duty = effluent.Hnet - feed.Hnet
sidedraw.copy_like(effluent)
sidedraw.mol *= self.saccharification_split
effluent.mol -= sidedraw.mol
effluent.mix_from([effluent, *other], energy_balance=False)
self.loss(effluent)
self.cofermentation(effluent)
self.CSL_to_constituents(effluent)
vent.empty()
vent.receive_vent(effluent, energy_balance=False)
def _design(self):
effluent = self.outs[1]
v_0 = effluent.F_vol
Design = self.design_results
Design['Flow rate'] = v_0 / self.N_reactors
tau = self.tau_saccharification + self.tau_cofermentation
Design.update(size_batch(v_0, tau, self.tau_0, self.N_reactors, self.V_wf))
Design['Batch duty'] = batch_duty = self._batch_duty
Design['Reactor duty'] = reactor_duty = self.Hnet - batch_duty
self.add_heat_utility(reactor_duty + batch_duty, effluent.T)
[docs]
@cost('Flow rate', 'Recirculation pumps', kW=30, S=340*_gpm2m3hr,
cost=47200, n=0.8, BM=2.3, CE=522, N='N_reactors')
@cost('Reactor duty', 'Heat exchangers', CE=522, cost=23900,
S=-5*_Gcal2kJ, n=0.7, BM=2.2, N='N_reactors') # Based on a similar heat exchanger
@cost('Reactor volume', 'Agitators', CE=522, cost=52500,
S=1e6*_gal2m3, n=0.5, kW=90, BM=1.5, N='N_reactors')
@cost('Reactor volume', 'Reactors', CE=522, cost=844000,
S=1e6*_gal2m3, n=0.5, BM=1.5, N='N_reactors')
class SimultaneousSaccharificationAndCoFermentation(Unit):
_N_ins = 1
_N_outs = 2
_ins_size_is_fixed = False
#: Saccharification temperature (K)
T_saccharification = 48+273.15
#: Fermentation temperature (K)
T_fermentation = 32+273.15
#: Saccharification and Co-Fermentation time (hr)
tau = 72
#: Unload and clean up time (hr)
tau_0 = 4
#: Working volume fraction (filled tank to total tank volume)
V_wf = 0.9
#: Number of reactors
N_reactors = 12
_units = {'Flow rate': 'm3/hr',
'Reactor volume': 'm3',
'Batch duty': 'kJ/hr',
'Reactor duty': 'kJ/hr'}
def _init(self, P=101325, saccharification=None, loss=None, cofermentation=None):
self.P = P
chemicals = self.chemicals
#: [ParallelReaction] Enzymatic hydrolysis reactions including from
#: downstream batch tank in co-fermentation.
self.saccharification = saccharification or ParallelRxn([
Rxn('Glucan -> GlucoseOligomer', 'Glucan', 0.0400, chemicals),
Rxn('Glucan + 0.5 H2O -> 0.5 Cellobiose', 'Glucan', 0.0120, chemicals),
Rxn('Glucan + H2O -> Glucose', 'Glucan', 0.9000, chemicals),
Rxn('Cellobiose + H2O -> 2Glucose', 'Cellobiose', 1.0000, chemicals)
])
self.loss = loss or ParallelRxn([
# Reaction definition Reactant Conversion
Rxn('Glucose -> 2 LacticAcid', 'Glucose', 0.0300, chemicals),
Rxn('3 Xylose -> 5 LacticAcid', 'Xylose', 0.0300, chemicals),
Rxn('3 Arabinose -> 5 LacticAcid', 'Arabinose', 0.0300, chemicals),
Rxn('Galactose -> 2 LacticAcid', 'Galactose', 0.0300, chemicals),
Rxn('Mannose -> 2 LacticAcid', 'Mannose', 0.0300, chemicals),])
self.cofermentation = cofermentation or ParallelRxn([
# Reaction definition Reactant Conversion
Rxn('Glucose -> 2 Ethanol + 2 CO2', 'Glucose', 0.9500, chemicals),
Rxn('Glucose + 0.047 CSL + 0.018 DAP -> 6 Z_mobilis + 2.4 H2O', 'Glucose', 0.0200, chemicals),
Rxn('Glucose + 2 H2O -> 2 Glycerol + O2', 'Glucose', 0.0040, chemicals),
Rxn('Glucose + 2 CO2 -> 2 SuccinicAcid + O2', 'Glucose', 0.0060, chemicals),
Rxn('3 Xylose -> 5 Ethanol + 5 CO2', 'Xylose', 0.8500, chemicals),
Rxn('Xylose + 0.039 CSL + 0.015 DAP -> 5 Z_mobilis + 2 H2O',
'Xylose', 0.0190, chemicals),
Rxn('3 Xylose + 5 H2O -> 5 Glycerol + 2.5 O2', 'Xylose', 0.0030, chemicals),
Rxn('Xylose + H2O -> Xylitol + 0.5 O2', 'Xylose', 0.0460, chemicals),
Rxn('3 Xylose + 5 CO2 -> 5 SuccinicAcid + 2.5 O2', 'Xylose', 0.0090, chemicals),
])
self.CSL_to_constituents = Rxn(
'CSL -> 0.5 H2O + 0.25 LacticAcid + 0.25 Protein', 'CSL', 1.0000, basis='wt',
)
self.CSL_to_constituents.basis = 'mol'
def _run(self):
vent, effluent = self.outs
vent.P = effluent.P = self.P
vent.T = effluent.T = self.T_fermentation
vent.phase = 'g'
effluent.mix_from(self.ins, energy_balance=False)
self.saccharification(effluent)
self.loss(effluent)
self.cofermentation(effluent)
self.CSL_to_constituents(effluent)
vent.empty()
vent.receive_vent(effluent, energy_balance=False)
def _design(self):
effluent = self.outs[1]
v_0 = effluent.F_vol
Design = self.design_results
Design['Flow rate'] = v_0 / self.N_reactors
Design.update(size_batch(v_0, self.tau, self.tau_0, self.N_reactors, self.V_wf))
Design['Reactor duty'] = reactor_duty = self.Hnet
self.add_heat_utility(reactor_duty, effluent.T)
# %% Pretreatment separations
# Membrane separation processes. Perry's Chemical Engineer's Handbook 7th Edition.
[docs]
@cost('Flow rate', 'Nanofiltration', kW=18000, S=0.25, units='m3/s',
cost=17300, n=0.6, BM=1., CE=bst.units.design_tools.CEPCI_by_year[1996])
@cost('Annual flow rate', 'Membranes and maintenance', S=1., units='m3/yr',
cost=0.13, n=1., BM=1., CE=bst.units.design_tools.CEPCI_by_year[1996],
annual=True)
class ReverseOsmosis(bst.SolidsSeparator):
pass
Europe_investment_site_factor = 1.2
euro_to_dollar = 1.04
installation_cost = 115000 # euro
volume_treated = 10 * 24 * 30 * 18 # m3
membrane_area = 27.5 # m2
membrane_cost = 95 # euro / m2
cleaning_cost = 50 # euro / m2
maintenance_cost = installation_cost * 0.02
yearly_operating_hours = 8000
operating_cost = membrane_area * (membrane_cost + cleaning_cost) + maintenance_cost * yearly_operating_hours / (18 * 30 * 24)
operating_cost_per_volume_treated = operating_cost / volume_treated
US_operating_cost = euro_to_dollar * operating_cost_per_volume_treated / Europe_investment_site_factor
US_installation_cost = euro_to_dollar * installation_cost / Europe_investment_site_factor
CEPCI2013 = bst.units.design_tools.CEPCI_by_year[2013]
electricity_cost = 2e3 # euro / yr
electricity_price = 0.04 # euro / kWh
electricity_demand = electricity_cost / yearly_operating_hours / electricity_price
[docs]
@cost('Flow rate', 'Nanofiltration', kW=electricity_demand, S=10, units='m3/hr',
cost=US_installation_cost, n=0.6, BM=1., CE=CEPCI2013)
@cost('Annual flow rate', 'Membranes and maintenance', S=1., units='m3/yr',
cost=US_operating_cost, n=1., BM=1., CE=CEPCI2013,
annual=True)
class Nanofilter(bst.Unit):
_N_ins = 1
_N_outs = 2
volume_reduction = 0.80
lignin_retention = 0.60
NaOH_retention = 0.10
sugar_retention = 0.95
sugars_IDs = ('Glucose', 'Xylose', 'Arabinose', 'Galactose', 'Mannose',
'GlucoseOligomer', 'GalactoseOligomer', 'MannoseOligomer',
'XyloseOligomer', 'ArabinoseOligomer', 'HMF', 'Furfural', 'Xylobiose')
lignin_IDs = ('SolubleLignin',)
retentate_only_IDs = ('Lignin', 'Glucan', 'Xylan', 'Arabinan', 'Galactan',
'Mannan', 'Solids', 'Ash')
def _run(self):
feed, = self.ins
permeate, retentate = self.outs
permeate.empty()
retentate.empty()
lignin_retention = self.lignin_retention
sugar_retention = self.sugar_retention
NaOH_retention = self.NaOH_retention
volume_reduction = self.volume_reduction
water = feed.imass['Water']
permeate.imass['Water'] = volume_reduction * water
retentate.imass['Water'] = feed.imass['Water'] - permeate.imass['Water']
retentate.copy_flow(feed, self.retentate_only_IDs)
not_permeate_unique = (*self.retentate_only_IDs, *self.lignin_IDs, *self.sugars_IDs, 'NaOH', 'Water')
not_permeate_unique = set([self.chemicals[i].ID for i in not_permeate_unique])
permeate_unique = tuple([i.ID for i in self.chemicals if i.ID not in not_permeate_unique])
permeate.copy_flow(feed, permeate_unique)
F_mass = feed.F_mass
lignin_comps = feed.imass[self.lignin_IDs]
sugar_comps = feed.imass[self.sugars_IDs]
NaOH = feed.imass['NaOH']
lignin = lignin_comps.sum()
sugar = sugar_comps.sum()
lignin_def = lignin_comps / lignin
sugar_def = sugar_comps / sugar
NaOH = feed.imass['NaOH']
feed_mass = np.array([lignin, sugar, NaOH])
z_mass = feed_mass / F_mass
K = np.array([lignin_retention, sugar_retention, NaOH_retention])
phi_guess = 0.5
Fa = permeate.F_mass
Fb = retentate.F_mass
assert abs((Fa + Fb + lignin + sugar + NaOH) - F_mass) < 1e-6
permeate_mass = feed_mass / water * (1 - K) * permeate.imass['Water']
# phi = tmo.separations.compute_phase_fraction(z_mass, K, phi_guess, Fa/F_mass, Fb/F_mass)
# x_retentate = z_mass / (phi * K + (1. - phi))
# retentate_mass = x_retentate * (1. - phi) * F_mass
permeate.imass[self.lignin_IDs] = permeate_mass[0] * lignin_def
permeate.imass[self.sugars_IDs] = permeate_mass[1] * sugar_def
permeate.imass['NaOH'] = permeate_mass[2]
retentate.mol = feed.mol - permeate.mol
assert (permeate.mol >= 0.).all()
retentate.T = permeate.T = feed.T
# %% Simple unit operations
[docs]
@cost('Flow rate', 'Pump', units='kg/hr',
S=402194, CE=522, cost=22500, n=0.8, kW=74.57, BM=2.3)
class HydrolysatePump(Unit): pass
[docs]
@cost('Flow rate', 'Tank', S=1171, units='kg/hr',
CE=522, cost=196000, n=0.7, BM=2)
class AmmoniaStorageTank(Unit): pass
[docs]
@cost('Flow rate', 'Agitator', S=410846, units='kg/hr',
CE=522, cost=None, n=0.5, BM=1.5, kW=14.914)
@cost('Flow rate', 'Tank', S=410369, units='kg/hr',
CE=522, cost=None, n=0.7, BM=2)
class AmmoniaReacidificationTank(Unit): pass
[docs]
@cost('Flow rate', 'Tank', S=425878, units='kg/hr',
CE=522, cost=636000, n=0.7, BM=1.8)
@cost('Flow rate', 'Pump', S=425878, units='kg/hr',
CE=522, cost=26800, n=0.8, BM=2.3, kW=93.2125)
@cost('Flow rate', 'Agitator', S=425878, units='kg/hr',
CE=522, cost=68300, n=0.5, BM=1.5, kW=14.914)
class BeerTank(Unit): pass
[docs]
@cost('Flow rate', 'Pump', S=292407, units='kg/hr',
CE=551, cost=25365, n=0.8, BM=2.3, kW=93.2125)
class BlowdownDischargePump(Unit): pass
[docs]
@cost('Flow rate', 'Tank', S=1393, units='kg/hr',
CE=522, cost=70000, n=0.7, BM=2.6)
@cost('Flow rate', 'Pump', S=1393, units='kg/hr',
CE=522, cost=3000, n=0.8, BM=3.1, kW=0.37285)
@cost('Flow rate', 'Agitator', S=1393, units='kg/hr',
CE=522, cost=21200, n=0.5, BM=1.5, kW=7.457)
class CSLStorageTank(Unit): pass
[docs]
@cost('Flow rate', 'Tank', S=163, units='kg/hr',
CE=522, cost=102000, n=0.7, BM=1.8)
@cost('Flow rate', 'Pump', S=163, units='kg/hr',
CE=522, cost=3000, n=0.8, BM=3.1, kW=0.37735)
@cost('Flow rate', 'Agitator', S=163, units='kg/hr',
CE=522, cost=9800, n=0.5, BM=1.5, kW=4.10135)
@cost('Flow rate', 'Bag unloader', S=163, units='kg/hr',
CE=522, cost=30000, n=0.6, BM=1.7)
class DAPStorageTank(Unit): pass
[docs]
@cost('Flow rate', 'System', S=94697, units='kg/hr',
CE=522, cost=13329690, n=0.6, BM=1.7, kW=783)
class FeedStockHandling(Unit): pass
[docs]
@cost('Flow rate', 'Tank', S=252891, units='kg/hr',
CE=522, cost=511000, n=0.7, BM=2)
@cost('Flow rate', 'Pump', S=252891, units='kg/hr',
CE=522, cost=30000, n=0.8, BM=1.7, kW=55.9)
@cost('Flow rate', 'Agitator', S=252891, units='kg/hr',
CE=522, cost=90000, n=0.5, BM=1.5, kW=170)
class PretreatmentFlash(bst.Flash): pass
[docs]
@cost('Duty', 'Heat exchanger', S=8, units='Gcal/hr',
CE=522, cost=85000, n=0.7, BM=2.2, magnitude=True)
class HydrolysateHeatExchanger(bst.HXutility): pass
[docs]
@cost('Duty', 'Heat Exchanger', S=8, units='Gcal/hr',
CE=551, cost=92000, n=0.7, BM=2.2, magnitude=True)
class PretreatmentWasteHeater(bst.HXutility): pass
[docs]
@cost('Duty', 'Heat Exchanger', S=2, units='Gcal/hr',
CE=522, cost=34000, n=0.7, BM=2.2, magnitude=True)
class WasteVaporCondenser(bst.HXutility): pass
[docs]
@cost('Flow rate', 'Pump', S=402194, units='kg/hr',
CE=522, cost=22500, n=0.8, BM=2.3, kW=74.57)
class HydrolyzatePump(Unit): pass
[docs]
@cost('Flow rate', 'Separator', S=39000, units='kg/hr',
CE=522, cost=35000000, n=0.7, BM=1.7)
class HydrolyzateSolidLiquidSeparator(Unit): pass
[docs]
@cost('Flow rate', 'Tank', S=410369, units='kg/hr',
CE=522, cost=236000, n=0.7, BM=2)
@cost('Flow rate', 'Agitator', S=410369, units='kg/hr',
CE=522, cost=21900, n=0.5, BM=1.5, kW=7.457)
class AmmoniaAdditionTank(bst.MixTank): pass
[docs]
@cost('Flow rate', 'Mixer', S=157478, units='kg/hr',
CE=522, cost=5000, n=0.5, BM=1)
class AmmoniaMixer(bst.Mixer): pass
[docs]
@cost('Flow rate', 'Mixer', S=380000, units='kg/hr',
CE=522, cost=109000, n=0.5, BM=1.7, kW=74.57)
class EnzymeHydrolysateMixer(bst.Mixer): pass
[docs]
@cost('Flow rate', 'Mixer', S=136260, units='kg/hr',
CE=522, cost=6000, n=0.5, BM=1)
class SulfuricAcidMixer(bst.Mixer): pass
[docs]
@cost('Flow rate', 'Tank', S=264116, units='kg/hr',
CE=522, cost=203000, n=0.7, BM=2)
@cost('Flow rate', 'Pump', S=264116, units='kg/hr',
CE=551, cost=17480, n=0.8, BM=1.7, kW=55.9)
@cost('Flow rate', 'Agitator', S=264116, units='kg/hr',
CE=522, cost=90000, n=0.5, BM=1.5, kW=170)
class OligomerConversionTank(Unit): pass
[docs]
@cost('Flow rate', 'Pump', S=402195, units='kg/hr',
CE=522, cost=None, n=0.8, BM=2.3, kW=44.742)
class ReacidifiedHydrolyzatePump(Unit): pass
[docs]
@cost('Flow rate', 'Pump', S=43149, units='kg/hr',
CE=522, cost=8200, n=0.8, BM=2.3, kW=7.457)
@cost('Flow rate', 'Tank', S=40414, units='kg/hr',
CE=522, cost=439000, n=0.7, BM=1.8)
@cost('Flow rate', 'Agitator', S=40414, units='kg/hr',
CE=522, cost=31800, n=0.5, BM=1.5, kW=11.3205)
class SeedHoldTank(Unit): pass
[docs]
@cost('Flow rate', 'Tank', S=1981, units='kg/hr',
CE=522, cost=96000, n=0.7, BM=1.5)
@cost('Flow rate', 'Pump', S=1981, units='kg/hr',
CE=522, cost=7493, n=0.8, BM=2.3, kW=0.5)
class SulfuricAcidStorageTank(Unit): pass
[docs]
@cost('Flow rate', 'Tank', S=1981, units='kg/hr',
CE=551, cost=6210, n=0.7, BM=2)
@cost('Flow rate', 'Pump', S=3720, units='kg/hr',
CE=522, cost=8000, n=0.8, BM=2.3, kW=1)
class SulfuricAcidTank(Unit): pass