27. Oleochemical Fermentation Process#
The oleochemical industry is focusing on renewable feedstocks and more sustainable pathways of production. Microbial pathways struggle to be market competitive for the production of commodity oleochemicals, but technological breakthroughs in metabolic engineering are making microbial pathways a promising platform. In this case study, we discuss how to model an aerobic fermentation system for producing microbial oil, which can be further upgraded to sustainable aviation fuel or other oleochemicals. The aerobic fermentation system (including the compressor, reactor, agitator, and heat exchange equipment) constitutes the major cost driver in the minimum oil selling price.
The goals of this case study are to:
Understand the design basis of sizing an aerated bioreactor in BioSTEAM.
Put in practice BioSTEAM’s design algorithms for microbial oil production.
Quantify the economic impact of key fermentation performance parameters.
27.1. Design basis#
27.1.1. Configuration#
In this case study, we implement an industry-relevant configuration consisting of a jacketed stirred tank reactor and a compressor (Figure 1). If you feedstock is in the form of diluted sugars (e.g., lignocellusic hydrolysate), you may need to concentrated your feed to achieve the a specified titer. This can be done efficiently through multi-effect evaporation, whereby the steam from the first effect is used to boil the next effect using a vacuum system. Alternatively, if you wish to operate at lower titers, you can dilute the sugars. Here we include both possibilities to be able to evaluate a spectrum of titers. Due to the high risk of bacterial contamination, we choose to operate in batch mode. Fed-batch configurations are also possible and can be modeled in a similar fashion.
27.1.2. Mass transfer and mixing#
Aerobic processes demand an oxygen uptake rate (OUR) which is satisfied by the oxygen transfer rate (OTR) under quasi steady-state. The basic mass tranfer equation is:
For tall vessels (>1 m tall) the log-mean driving force is more accurate. Due to the oxygen partial pressure gradient, the local concentration and the saturation concentration are different at the top and bottom of the reactor:
The \(k_La\) can be correlated to the superficail gas velocity, \(U_S\), and the agitator power per unit volume, \(P\), as follows:
Where A, B, and C are correlation coefficients dependent on the agitator system, broth type, and size dimensions. The detailed design of the agitator is not relevant at this stage of the analysis, but the correlation coefficients will be a function of the agitator design.
Assumptions
Assume a dissolved oxygen concentration of 50% saturation. Note that the flow rate of air (or agitation power) required to achieve an oxygen uptake rate equal to the oxygen transfer rate will depend on the oxygen saturation.
Set the power consumed by the agitator to 0.2955 kW∙m3, a heuristic value common for industrial homogeneous reactions and comparable to estimates in Aspen plus. Note that is also possible to minimize power consumption by varying flow rate, but this would also lead to a greater capital cost for the compressor.
Assume the Van’t Riet’s non-viscous mass transfer correlation is applicable to estimate the overall mass transfer coefficient, kLa (lumped together with the specific interfacial area).
[1]:
theta_O2 = 0.5 # Dissolved oxygen concentration [% saturation]
agitation_power = 0.2955 # [kW / m3]
design = 'Stirred tank' # Reactor type; Alternatively, 'Bubble column'
method = "Riet" # Name of method; Alternatively, 'Dewes' for bubble columns
27.1.3. Heat transfer#
There are two types of bioreactor heat exchanger configurations supported in BioSTEAM: jacketed and recirculation loop.
Recirculation loop
A recirculation loop is an external heat exchanger which recirculates fluid back to the bioreactor. The fluid at the inlet of the heat exchanger is at the reactor operating temperature while the outlet fluid will be at a lower temperature, which depends on the flow rate. We can specify the outlet temperature (which cannot be too low to avoid inactivation of the microbes) and solve for the recirculation flow rate:
The detailed design of the heat exchanger should follow standard design algorithms, but is not discussed here. In practice, however, recirculation loops are less commonly used due to the sheer stress on cells and gas entrainment in the pump and heat exchanger.
Jacketed vessel
The most commonly used bioreactor heat exchanger configuration is the jacketed vessel. A full jacket provides a constant heat transfer area to the medium. In order to meet the cooling requirement, the flow rate of the cooling agent (e.g., chilled water) can be varied. The greater the flow rate, the greater the overall heat transfer coefficient, and the greater the driving force as well (i.e., the temperature difference across the jacket).
Estimating the overall heat transfer coefficient can be challenging due to its dependence on detailed design of the bioreactor (e.g., agitation) and mixture properties (e.g., density, conductivity). As a preliminary estimate, we can assume that the temperature of the outlet utility is simply the maximum allowable temperature for smooth regeneration (chilled water cannot come back too hot to the cooler). Because the cost of chilled water usage is estimated based on the total heat transfered (not on the actual flow rate), this assumption does not impact our economic analysis.
Assumptions
The fermentation temperature is 32 \(^\circ\)C
Assume the aerobic fermenter has a high heat-production rate of 110 kcal per mol of O2 consumed.
[2]:
T_operation = 273.15 + 32 # [K]
Q_O2_consumption = -110 * 4184 # [kJ/kmol]
heat_exchanger_configuration = 'jacketed' # Alternatively, 'recirculation loop'
27.1.4. Compressor#
The compressor is needed to pressurize air to make up for the liquid head, and friction losses from the sparger ring and piping, and the pressure drop from the cooler. The cooler is needed because the compressor heats up the air due to thermodynamic inefficiencies. The isentropic efficiency of the compressor for a gas-fed bioreactor in BioSTEAM defaults to a heuristic value of 85%.
Currently, BioSTEAM only accounts for the liquid head and the pressure drop from the heat exchanger (not the sparger ring or piping, yet). Given a heuristic pressure drop for the cooler is 3 psi (20684 Pa), the outlet pressure of the compressor is computed as follows:
Where \(g\) is the acceleration due to gravity, \(\rho\) is the liquid density, \(h\) is the height of the fluid, and \(\Delta P_{cooler}\) is the pressure drop of the cooler.
Assumptions
Compressor isentropic efficiency is 85%.
Neglect friction losses from sparger and piping.
The pressure drop across the cooler is 3 psi.
[3]:
cooler_pressure_drop = 20684 # [Pa]
compressor_isentropic_efficiency = 0.85
27.1.5. Reaction stoichiometry#
The production of microbial oil is an aerobic process that can be modeled as the sum of 3 stoichiometric reactions: oil production from sugar, combustion of glucose (respiration), and cell growth. The substrate not converted to cell mass or oil is assumed to be consumed for respiration. Given the yield of triolein, Yp (wt %), and the specific yield of product per unit biomass, Yb (wt %,), the extent of each reaction can be calculated.
Reaction |
Stoichiometry (by mol) |
Reaction extent |
|---|---|---|
Production: |
\[Glucose \rightarrow 0.235\ H_2O + 2.53\ O_2 + 0.118\ Tripalmitin\]
|
\[X_p=\frac{Y_p}{0.527}\]
|
aGrowth: |
\[Glucose \rightarrow 1.7\ H_2O + 0.655\ CO_2 + 5.35\ Yeast\]
|
\[X_b= \frac{Y_p}{0.67Y_b}\]
|
Respiration: |
\[Glucose + 6 O_2 \rightarrow 6 H_2O + 6 CO_2\]
|
\[X_r=1-X_p-X_b\]
|
aThe molecular formula of yeast is assumed to be \(CH_{1.61}O_{0.56}\)
Assumptions
Titer is 27.4 g∙L-1
Productivity is 0.31 g∙L-1∙h-1
Yield is 18 wt %
Specific yield of product per unit biomass is 63.56 wt %
The maximum volume of each reactor vessel is 500 \(\text{m}^\text{3}\).
[4]:
V_max = 500 # [m3]
titer = 27.4 # [g / L]
productivity = 0.31 # [g / L / h]
lipid_yield = Y_p = 0.18 # [by wt]
Y_b = 0.6356 # [by wt]
27.2. Bioreactor modeling#
With an introductory understanding of the design principles, we can now model our configuration. The first step we will take is to create the chemicals and formulate the reactions:
[5]:
import biosteam as bst
bst.nbtutorial()
bst.settings.set_thermo([
bst.Chemical('Glucose', phase='l'),
bst.Chemical('Water'),
bst.Chemical('CO2'),
bst.Chemical('O2'),
bst.Chemical('N2'),
bst.Chemical('Tripalmitin', phase='l', Hf=-2468.7),
bst.Chemical('Yeast',
phase='s',
search_db=False,
formula='CH1.61O0.56',
rho=1540, # kg/m3
Cp=1.5, # J/g
default=True, # Default other properties
)
])
chemicals = bst.settings.chemicals
for alias in ('Lipid', 'Oil', 'lipid'): chemicals.set_alias('Tripalmitin', alias)
chemicals.set_alias('Yeast', 'cellmass')
chemicals.Lipid.V.method = 'HTCOSTALD'
production = bst.Reaction(
"Glucose -> H2O + O2 + Lipid", reactant='Glucose', X=1,
correct_atomic_balance=True,
)
production.product_yield('Lipid', basis='wt', product_yield=lipid_yield)
growth = bst.Reaction(
'Glucose -> H2O + CO2 + Yeast', 'Glucose', 1,
correct_atomic_balance=True
)
growth.product_yield('Yeast', basis='wt', product_yield=Y_p/Y_b / (1 - production.X))
respiration = bst.Reaction(
'Glucose + O2 -> CO2 + H2O', 'Glucose', 1. - growth.X,
correct_atomic_balance=True
)
reactions = bst.ReactionSystem(
production,
bst.ParallelReaction([growth, respiration])
)
reactions.show()
ReactionSystem:
[0] Reaction (by mol):
stoichiometry reactant X[%]
Glucose -> 0.235 Water + 2.53 O2 + 0.118 Tripalmitin Glucose 34.14
[1] ParallelReaction (by mol):
index stoichiometry reactant X[%]
[0] Glucose -> 1.7 Water + 0.655 CO2 + 5.35 Yeast Glucose 64.15
[1] Glucose + 6 O2 -> 6 Water + 6 CO2 Glucose 35.85
Now let’s create our aerated bioreactor:
[6]:
effluent = bst.Stream()
AB1 = bst.AeratedBioreactor(
ins=['feed', bst.Stream('air', phase='g')],
outs=['vent', effluent],
design='Stirred tank', method=method,
V_max=V_max, Q_O2_consumption=Q_O2_consumption,
T=T_operation, batch=True, reactions=reactions,
kW_per_m3=agitation_power,
tau=titer/productivity,
cooler_pressure_drop=cooler_pressure_drop,
compressor_isentropic_efficiency=compressor_isentropic_efficiency,
optimize_power=False,
heat_exchanger_configuration=heat_exchanger_configuration,
loading_time=None, # Will assume no upstream storage (constant loading)
)
AB1.target_titer = titer # g / L
AB1.target_productivity = productivity # g / L / h
AB1.target_yield = lipid_yield # wt %
@AB1.add_specification(run=True)
def update_reaction_time_and_yield():
AB1.tau = AB1.target_titer / AB1.target_productivity
production.product_yield('Lipid', basis='wt', product_yield=AB1.target_yield)
AB1.diagram()
The bioreactor system already comes with all the auxiliary units for aeration and heat transfer. All what is left is to add the concentration/dilution system along with an oil recovery system. We create the multi-effect evaporator assuming 5 effects. The required vapor fraction of the first effect will be solved for in a specification. We will leverage a pre-made system for mechanical microbial oil recovery:
[7]:
from flexsolve import fixed_point
from biorefineries.cane.systems import create_lipid_extraction_system
feedstock = bst.Stream(
Water=0.85, Glucose=0.15,
total_flow=1e5, units='kg/hr',
price=0.14 * 0.15,
)
E1 = bst.MultiEffectEvaporator(
ins=feedstock, outs=('evaporated_feed', 'condensate'),
V=0, V_definition='First-effect',
P=(101325, 73581, 50892, 32777, 20000),
)
dilution_water = bst.Stream()
M1 = bst.Mixer(
ins=(E1-0, dilution_water), outs=AB1.ins[0]
)
def get_titer(): # g/L or kg/m3
product_mass_flow = effluent.imass['Lipid'] # effluent.get_flow('kg / hr', 'lipid')
volumetric_flow_rate = effluent.F_vol # effluent.get_total_flow('m3/hr')
return product_mass_flow / volumetric_flow_rate
def adjust_dilution_water(water):
dilution_water.imass['Water'] = water
E1.run_until(AB1, inclusive=True)
current = get_titer()
rho = chemicals.Water.rho('l', T=T_operation, P=101325) # kg / m3
return water + (1./AB1.target_titer - 1./current) * effluent.imass['Lipid'] * rho
@E1.add_bounded_numerical_specification(x0=0, x1=0.15, ytol=1e-3, xtol=1e-6, maxiter=20)
def evaporation(V):
E1.V = V
if V == 0:
needed_water = fixed_point(adjust_dilution_water, 0, xtol=1)
if needed_water > 0: return AB1.target_titer - get_titer()
dilution_water.empty()
E1.run_until(AB1, inclusive=True)
return AB1.target_titer - get_titer()
lipid_extraction_sys = create_lipid_extraction_system(ins=effluent)
# Optimistic price for defatted biomass; https://doi.org/10.1186/s13068-021-01911-3
lipid_extraction_sys.get_outlet('cellmass').price = 0.5 * 0.68
# microbial_lipids_sys = bst.main_flowsheet.create_system('microbial_lipids_sys')
microbial_lipids_sys = bst.System.from_units(
'microbial_lipids_sys',
units=[E1, M1, AB1, *lipid_extraction_sys.units]
)
microbial_lipids_sys.set_tolerance(mol=1e-9, rmol=1e-9)
microbial_lipids_sys.simulate()
microbial_lipids_sys.diagram(auxiliaries=1)
Let’s have a look at the detailed results of the bioreactor:
[8]:
AB1.show('cwt')
print()
AB1.results(basis='SI')
AeratedBioreactor: AB1
ins...
[0] feed from Mixer-M1
phase: 'l', T: 298.15 K, P: 101325 Pa
flow (%): Glucose 14.3
Water 85.7
------- 1.05e+05 kg/hr
[1] air
phase: 'g', T: 305.15 K, P: 101325 Pa
flow (%): O2 23.3
N2 76.7
-- 2.19e+04 kg/hr
outs...
[0] vent
phase: 'g', T: 305.15 K, P: 101325 Pa
flow (%): Water 2.73
CO2 22.6
O2 13.3
N2 61.4
----- 2.74e+04 kg/hr
[1] effluent to SolidLiquidsSplitCentrifuge-U401
phase: 'l', T: 305.15 K, P: 101325 Pa
flow (%): Water 93
CO2 0.0192
O2 0.000483
N2 0.00109
Tripalmitin 2.72
Yeast 4.28
----------- 9.93e+04 kg/hr
[8]:
| Aerated bioreactor | Units | AB1 | |
|---|---|---|---|
| Electricity | Power | kW | 3.71e+03 |
| Cost | USD/hr | 290 | |
| Chilled water | Duty | kJ/hr | -2.37e+07 |
| Flow | kmol/hr | 1.59e+04 | |
| Cost | USD/hr | 119 | |
| Design | Reactor volume | m³ | 498 |
| Batch time | hr | 95.4 | |
| Loading time | hr | 3.99 | |
| Number of reactors | 24 | ||
| Residence time | hr | 88.4 | |
| Vessel type | Vertical | ||
| Length | m | 17.9 | |
| Diameter | m | 5.96 | |
| Weight | kg | 6.45e+04 | |
| Wall thickness | m | 0.0103 | |
| Jacketed diameter | m | 6.02 | |
| Vessel material | Stainless steel 316 | ||
| Purchase cost | Vertical pressure vessel (jacketed) (x24) | USD | 6.7e+06 |
| Platform and ladders (x24) | USD | 1.58e+06 | |
| Compressor - Compressor(s) (x24) | USD | 6.45e+05 | |
| Air cooler - Floating head (x24) | USD | 2.12e+04 | |
| Agitator - Agitator (x24) | USD | 1.77e+06 | |
| Total purchase cost | USD | 1.07e+07 | |
| Installed equipment cost | USD | 3.27e+07 | |
| Utility cost | USD/hr | 409 |
With our system complete, let’s proceed to perform TEA using the same preliminary assumptions used in Humbird’s 2011 report for cornstover ethanol:
[9]:
from biorefineries.tea import create_cellulosic_ethanol_tea
microbial_lipids_tea = create_cellulosic_ethanol_tea(microbial_lipids_sys)
lipid_product = lipid_extraction_sys.get_outlet('lipid')
print(f'Minimum selling price (MSP): {microbial_lipids_tea.solve_price(lipid_product):.3g} USD/kg')
Minimum selling price (MSP): 1.25 USD/kg
Now that we have a working model, let’s leverage it to evaluate a landscape of potential fermentation scenarios:
[10]:
model = bst.Model(microbial_lipids_sys) # Setup optimization model
model.parameter('P', AB1, bounds=[101325, 10 * 101325])
model.parameter('kW_per_m3', AB1, bounds=[0.1, 1])
model.parameter('length_to_diameter', AB1, bounds=[1, 12])
@model.indicator
def MSP(): return microbial_lipids_tea.solve_price(lipid_product)
print(
f"""
Heuristic design decisions
--------------------------
MSP -> {MSP():.3g} USD/Kg
Air pressure: {int(AB1.P)} Pa
Agitation power: {AB1.kW_per_m3:.3g} kW/m3
Length to diameter: {AB1.length_to_diameter:.3g}
"""
)
model.optimize(loss=MSP, method='differential evolution')
print(
f"""
Optimized design decisions
--------------------------
MSP -> {MSP():.3g} USD/Kg
Air pressure: {int(AB1.P)} Pa
Agitation power: {AB1.kW_per_m3:.3g} kW/m3
Length to diameter: {AB1.length_to_diameter:.3g}
"""
)
Heuristic design decisions
--------------------------
MSP -> 1.25 USD/Kg
Air pressure: 101325 Pa
Agitation power: 0.295 kW/m3
Length to diameter: 3
Optimized design decisions
--------------------------
MSP -> 1.21 USD/Kg
Air pressure: 101380 Pa
Agitation power: 0.141 kW/m3
Length to diameter: 4.59
Notice how the MSP decreased, but not by much. It turns out that the heuristic design decisions are actually pretty close to the optimal. This is why heuristics can be very powerful.
[11]:
import numpy as np
from matplotlib import pyplot as plt, rcParams
width = 6.6142
aspect_ratio = 0.4
rcParams['figure.figsize'] = (width, width * aspect_ratio)
def MSP_at_yield_productivity_titer(lipid_yield, productivity, titer):
AB1.target_yield = lipid_yield
AB1.target_productivity = productivity
MSP = np.zeros_like(titer)
for i, value in enumerate(titer):
AB1.target_titer = value
microbial_lipids_sys.simulate()
MSP[i] = microbial_lipids_tea.solve_price(lipid_product)
return MSP
titer_values = np.array([
0.5 * titer,
titer,
2 * titer,
])
xlim = np.array([0.5 * lipid_yield, 1.5 * lipid_yield])
ylim = np.array([0.5 * productivity, 1.5 * productivity])
X, Y, Z = bst.plots.generate_contour_data(
MSP_at_yield_productivity_titer,
xlim=xlim, ylim=ylim,
args=(titer_values,),
n=10,
)
# Plot contours
xlabel = "Yield [wt %]"
ylabel = 'Productivity [$g \cdot L^{-1} \cdot h^{-1}$]'
units = r'$g \cdot L^{-1}$'
titles = [f"{round(i, 2)} {units}" for i in titer_values]
xticks = [10, 15, 20, 25]
yticks = [0.16, 0.24, 0.32, 0.40]
metric_bar = bst.plots.MetricBar(
'MSP', '$USD \cdot kg^{-1}$', plt.cm.get_cmap('viridis_r'),
bst.plots.rounded_tickmarks_from_data(Z, 4, 0.4, expand=0, p=0.05), 15, 1
)
fig, axes, CSs, CB, other_axes = bst.plots.plot_contour_single_metric(
100 * X, Y, Z[:, :, None, :], xlabel, ylabel, xticks, yticks, metric_bar,
titles=titles, fillcolor=None, styleaxiskw=dict(xtick0=False), label=True,
)
As expected, the yield and productivity are impactful but not the titer. The minimum selling price between 1-4 USD · kg-1 is close to reported costs in literature at similar processing capacities (Karamerou et al. 2021). The bioreactor, however, was heuristically designed and costs could be reduced further. The optimal design of the bioreactor (e.g., height, operating pressure, agitation) is unique for each fermentation performance scenario (i.e., titer, rate, yield) and can significantly impact costs and performance. Let’s optimize the fermenation tank for our baseline scenario.