23. Bioproduct separation#

The separation and purification of bioproducts from fermentation broths often require a combination of liquid-liquid exatraction (large, non-polar molecules), distillation (small, polar molecules), and adsorption (dilute non-polar products). In this case study, we will build a typical separation process for acetic acid and review BioSTEAM’s capabilities for extraction and distillation.

23.1. Thermodynamic considerations#

Before creating the process, it can be useful to study the physical phenomena being used in the separation. Let’s first plot the phase envelope of acetic acid and water to see if distillation is possible:

[1]:
import biosteam as bst
bst.nbtutorial() # For light-mode diagrams, ignore warnings
[2]:
bst.plot_vle_binary_phase_envelope(['Water', 'AceticAcid'], P=101325)
../_images/tutorial_Glacial_acetic_acid_separation_4_0.png

Water and acetic acid have an azeotrope, so volatility based separation cannot completely separate acetic acid. Now let’s check whether a solvent such as ethyl acetate can be used for liquid-liquid extraction:

[3]:
bst.plot_lle_ternary_diagram(
    carrier='Water', solvent='EthylAcetate', solute='AceticAcid', T=298.15, P=101325,
)
../_images/tutorial_Glacial_acetic_acid_separation_6_0.png

Ethyl acetate is a good solvent. Now let’s confirm that we can separate acetic acid from the solvent more easily:

[4]:
bst.plot_vle_binary_phase_envelope(['EthylAcetate', 'AceticAcid'], P=101325)
../_images/tutorial_Glacial_acetic_acid_separation_8_0.png

Confirmed, a liquid-liquid extraction separation process is feasible. Let’s check the volatility of the components to understand which should be the distillate and bottoms products:

[5]:
for i in bst.Chemicals(['Water', 'EthylAcetate', 'AceticAcid']): print(i, round(i.Tb, 2), 'K')
Water 373.12 K
EthylAcetate 350.25 K
AceticAcid 391.05 K

23.2. Glacial acetic acid production#

Ethyl acetate will need to be recoved from the raffinate as the more volatile component (light key). Acetic acid can be recoved from the extact as a the heavy key. Based on this knowledge, we can construct the following flowsheet (adapted from Seader’s Separation Process Principles, 3rd Edition) glacial acetic acid production from dilute acetic acid:

GAA

23.3. Preliminary design#

Let’s start by creating a preliminary system using shortcut columns for speed and flexibility:

[6]:
# Define chemicals used in the process
bst.settings.set_thermo(['Water', 'AceticAcid', 'EthylAcetate'])

# Amount of ethyl-acetate to fermentation broth
solvent_feed_ratio = 1.5

# Fermentation broth with dilute acetic acid
acetic_acid_broth = bst.Stream(ID='acetic_acid_broth', AceticAcid=1000, Water=9000, units='kg/hr')

# Solvent
ethyl_acetate = bst.Stream(ID='ethyl_acetate',  EthylAcetate=1)

# Products
glacial_acetic_acid = bst.Stream(ID='glacial_acetic_acid')
wastewater = bst.Stream(ID='wastewater')

# Recycles
solvent_recycle = bst.Stream('solvent_rich')
water_rich = bst.Stream('water_rich')
distillate = bst.Stream('raffinate_distillate')

# System and unit operations
with bst.System('AAsep') as sys:
    extractor = bst.MultiStageMixerSettlers(
        'extractor',
        ins=(acetic_acid_broth, ethyl_acetate, solvent_recycle),
        outs=('extract', 'raffinate'),
        top_chemical='EthylAcetate',
        feed_stages=(0, -1, -1),
        N_stages=12,
        use_cache=True,
    )

    @extractor.add_specification(run=True)
    def adjust_fresh_solvent_flow_rate():
        broth = acetic_acid_broth.F_mass
        EtAc_recycle = solvent_recycle.imass['EthylAcetate']
        EtAc_required = broth * solvent_feed_ratio
        if EtAc_required < EtAc_recycle:
            solvent_recycle.F_mass *= EtAc_required / EtAc_recycle
            EtAc_recycle = solvent_recycle.imass['EthylAcetate']
        EtAc_fresh = EtAc_required - EtAc_recycle
        ethyl_acetate.imass['EthylAcetate'] = max(
            0, EtAc_fresh
        )

    HX = bst.HXutility(
        'extract_heater',
        ins=(extractor.extract),
        outs=('hot_extract'),
        rigorous=True,
        V=0,
    )
    ED = bst.ShortcutColumn(
        'extract_distiller',
        ins=HX-0,
        outs=['', 'acetic_acid'],
        LHK=('Water', 'AceticAcid'),
        Lr=0.95,
        Hr=0.95,
        k=1.4,
        partial_condenser=False,
    )
    ED2 = bst.ShortcutColumn(
        'acetic_acid_purification',
        ins=ED-1,
        outs=('', glacial_acetic_acid),
        LHK=('EthylAcetate', 'AceticAcid'),
        Lr=0.999,
        Hr=0.999,
        k=1.4,
        partial_condenser=False
    )
    ED.check_LHK = ED2.check_LHK = False
    mixer = bst.Mixer(
        ins=(ED-0, ED2-0, distillate)
    )
    HX = bst.HXutility(ins=mixer-0, T=310)
    settler = bst.MixerSettler(
        'settler',
        ins=HX-0,
        outs=(solvent_recycle, water_rich),
        top_chemical='EthylAcetate',
    )
    mixer = bst.Mixer(ins=[extractor.raffinate, water_rich])
    RD = bst.ShortcutColumn(
        'raffinate_distiller',
        LHK=('EthylAcetate', 'Water'),
        ins=mixer-0,
        outs=[distillate, wastewater],
        partial_condenser=False,
        Lr=0.99,
        Hr=0.99,
        k=1.5,
    )

sys.simulate()
sys.diagram(kind='cluster', format='png')
sys.show()
../_images/tutorial_Glacial_acetic_acid_separation_15_0.png
System: AAsep
Highest convergence error among components in recycle
stream H1-0 after 2 loops:
- flow rate   4.90e-01 kmol/hr (1.2%)
- temperature 0.00e+00 K (0%)
ins...
[0] acetic_acid_broth
    phase: 'l', T: 298.15 K, P: 101325 Pa
    flow (kmol/hr): Water       500
                    AceticAcid  16.7
[1] ethyl_acetate
    phase: 'l', T: 298.15 K, P: 101325 Pa
    flow (kmol/hr): EthylAcetate  0.225
outs...
[0] wastewater
    phase: 'l', T: 372.37 K, P: 101325 Pa
    flow (kmol/hr): Water         499
                    AceticAcid    2.75
                    EthylAcetate  0.0853
[1] glacial_acetic_acid
    phase: 'l', T: 390.75 K, P: 101325 Pa
    flow (kmol/hr): Water         0.000126
                    AceticAcid    13.9
                    EthylAcetate  0.0352
[7]:
sys.operating_hours = 330 * 24
print('CAPEX', round(sys.installed_equipment_cost / 1e6, 3), 'MMUSD')
print('OPEX', round(sys.material_cost + sys.utility_cost / 1e6, 4), 'MMUSD/yr')
CAPEX 1.752 MMUSD
OPEX 0.8838 MMUSD/yr

23.4. Rigorous design#

To model side draws and feeds at multiple stages, we will need to use rigorous MESH-based (Mass, Equilibrium, Summation, and entHalpy) distillation models. We can look at the design results for the ShortcutColumn to get heuristic design specifications for the rigorous column:

[8]:
print(ED.results())
outlet = ED.reboiler.outs[0]
boilup = outlet['g'].F_mol / outlet['l'].F_mol
distillate, condensate = ED.top_split.outs
split = condensate.F_mol / ED.condenser.outs[0].F_mol # Or from ED.design_results['Reflux']
N_stages = int(ED.design_results['Theoretical stages'])
feed_stage = int(ED.design_results['Theoretical feed stage'])
Distillation Column                              Units  extract_distiller
Electricity         Power                           kW               1.62
                    Cost                        USD/hr              0.127
Cooling water       Duty                         kJ/hr          -7.78e+06
                    Flow                       kmol/hr           5.31e+03
                    Cost                        USD/hr               2.59
Low pressure steam  Duty                         kJ/hr           8.21e+06
                    Flow                       kmol/hr                212
                    Cost                        USD/hr               50.4
Design              Theoretical feed stage                              7
                    Theoretical stages                                 10
                    Minimum reflux               Ratio              0.268
                    Reflux                       Ratio              0.376
                    Actual stages                                      20
                    Height                          ft               40.6
                    Diameter                        ft               4.49
                    Wall thickness                  in              0.312
                    Weight                          lb           8.01e+03
Purchase cost       Trays                          USD            1.8e+04
                    Tower                          USD           5.41e+04
                    Platform and ladders           USD           1.72e+04
                    Condenser - Floating head      USD           3.56e+04
                    Pump - Pump                    USD           4.34e+03
                    Pump - Motor                   USD                415
                    Reboiler - Floating head       USD           2.26e+04
Total purchase cost                                USD           1.52e+05
Utility cost                                    USD/hr               53.2
[9]:
reflux = bst.Stream('reflux')

# Amount of ethyl-acetate to fermentation broth
solvent_feed_ratio = 1.5

# System and unit operations
with bst.System('AAsep') as sys:
    extractor = bst.MultiStageMixerSettlers(
        'extractor',
        ins=(acetic_acid_broth, ethyl_acetate, solvent_recycle),
        outs=('extract', 'raffinate'),
        top_chemical='EthylAcetate',
        feed_stages=(0, -1, -1),
        N_stages=12,
        use_cache=True,
    )

    @extractor.add_specification(run=True)
    def adjust_fresh_solvent_flow_rate():
        broth = acetic_acid_broth.F_mass
        EtAc_recycle = solvent_recycle.imass['EthylAcetate']
        EtAc_required = broth * solvent_feed_ratio
        if EtAc_required < EtAc_recycle:
            solvent_recycle.F_mass *= EtAc_required / EtAc_recycle
            EtAc_recycle = solvent_recycle.imass['EthylAcetate']
        EtAc_fresh = EtAc_required - EtAc_recycle
        ethyl_acetate.imass['EthylAcetate'] = max(
            0, EtAc_fresh
        )

    HX = bst.HXutility(
        'extract_heater',
        ins=(extractor.extract),
        outs=('hot_extract'),
        rigorous=True,
        V=0,
    )
    ED = bst.MESHDistillation(
        'extract_distiller',
        ins=(HX-0, reflux),
        outs=('', 'acetic_acid', 'distillate'),
        feed_stages=[feed_stage-2, 1],
        N_stages=N_stages,
        full_condenser=True,
        boilup=boilup,
        LHK=('Water', 'AceticAcid'),
        use_cache=True,
    )
    ED2 = bst.ShortcutColumn(
        'acetic_acid_purification',
        ins=ED-1,
        outs=('', glacial_acetic_acid),
        LHK=('EthylAcetate', 'AceticAcid'),
        Lr=0.999,
        Hr=0.999,
        k=1.4,
        partial_condenser=False
    )
    ED.check_LHK = ED2.check_LHK = False
    mixer = bst.Mixer(
        ins=(ED-2, ED2-0, distillate)
    )
    HX = bst.HXutility(ins=mixer-0, T=310)
    settler = bst.MixerSettler(
        'settler',
        ins=HX-0,
        outs=('', water_rich),
        top_chemical='EthylAcetate',
    )
    splitter = bst.Splitter(
        'splitter',
        ins=settler-0,
        outs=(reflux, solvent_recycle),
        split=split,
    )
    mixer = bst.Mixer(ins=[extractor.raffinate, water_rich])
    RD = bst.ShortcutColumn(
        'raffinate_distiller',
        LHK=('EthylAcetate', 'Water'),
        ins=mixer-0,
        outs=[distillate, wastewater],
        partial_condenser=False,
        Lr=0.99,
        Hr=0.99,
        k=1.5,
    )
sys.set_tolerance(rmol=1e-3, mol=1e-3, subsystems=True)
sys.simulate()
sys.diagram(format='png')
sys.show()
../_images/tutorial_Glacial_acetic_acid_separation_19_0.png
System: AAsep
Highest convergence error among components in recycle
streams {extract_distiller-2, acetic_acid_purification-0, splitter-1, settler-1} after 10 loops:
- flow rate   1.55e-02 kmol/hr (0.05%)
- temperature 1.76e-05 K (5.1e-06%)
ins...
[0] acetic_acid_broth
    phase: 'l', T: 298.15 K, P: 101325 Pa
    flow (kmol/hr): Water       500
                    AceticAcid  16.7
[1] ethyl_acetate
    phase: 'l', T: 298.15 K, P: 101325 Pa
    flow (kmol/hr): EthylAcetate  0.152
outs...
[0] wastewater
    phase: 'l', T: 372.38 K, P: 101325 Pa
    flow (kmol/hr): Water         500
                    AceticAcid    2.47
                    EthylAcetate  0.085
[1] glacial_acetic_acid
    phase: 'l', T: 390.68 K, P: 101325 Pa
    flow (kmol/hr): Water         1.68e-05
                    AceticAcid    14.2
                    EthylAcetate  0.0453
[2] s9
    phase: 'g', T: 344.79 K, P: 101325 Pa
    flow: 0
[10]:
sys.operating_hours = 330 * 24
print('CAPEX', round(sys.installed_equipment_cost / 1e6, 3), 'MMUSD')
print('OPEX', round(sys.material_cost + sys.utility_cost / 1e6, 3), 'MMUSD/yr')
CAPEX 1.807 MMUSD
OPEX 0.963 MMUSD/yr