Amine-Based CO2 Capture: Fundamentals and Simulation#

This notebook introduces amine-based CO2 capture, the most mature and widely deployed technology for post-combustion carbon capture. We’ll use difflow_cc to simulate a complete capture loop and understand the key process parameters.

Learning Objectives#

  1. Understand how amine absorption works for CO2 capture

  2. Set up absorber and stripper units in difflow_cc

  3. Analyze key performance metrics (capture efficiency, regeneration energy)

  4. Compare different amine solvents

1. Background: Amine Scrubbing#

The Chemistry#

Primary and secondary amines react with CO2 to form carbamates:

\[\text{CO}_2 + 2\text{RNH}_2 \rightleftharpoons \text{RNHCOO}^- + \text{RNH}_3^+\]

This reaction is:

  • Fast (rate constant k₂ ~ 5000-50000 L/(mol·s) at 25°C)

  • Exothermic (ΔH ~ -80 to -85 kJ/mol for MEA)

  • Reversible (at elevated temperature, CO2 is released)

The Process#

A typical amine capture plant consists of:

  1. Absorber: Flue gas contacts lean amine solution counter-currently. CO2 is absorbed.

  2. Rich/Lean Heat Exchanger: Rich solvent is preheated before regeneration.

  3. Stripper (Regenerator): Heated rich solvent releases CO2. Steam provides stripping.

  4. Reboiler: Supplies heat for regeneration (the main energy consumer).

Key Metrics#

  • Capture Efficiency: Fraction of CO2 removed from flue gas (typically 90%+)

  • Specific Regeneration Energy: GJ of heat per tonne CO2 captured (3-4 GJ/t for MEA)

  • Solvent Circulation Rate: L/G ratio (liquid to gas molar ratio)

2. Setup#

import jax
import jax.numpy as jnp

# Enable 64-bit precision for numerical accuracy
jax.config.update("jax_enable_x64", True)

# Import difflow components
from difflow.streams import make_stream, get_flows, total_flow

# Import carbon capture components
from difflow_cc import (
    # Solvent database
    get_solvent, list_solvents,
    # Unit operations
    AbsorberParams, AmineAbsorber,
    StripperParams, AmineStripper,
)

print("Available solvents:", list_solvents())
Available solvents: ['MEA', 'DEA', 'MDEA', 'PZ', 'AMP', 'Glycine', 'Sarcosine']

3. Exploring the Solvent Database#

Let’s examine the properties of MEA (monoethanolamine), the benchmark solvent.

# Get MEA properties
mea = get_solvent("MEA")

print(f"Solvent: {mea.full_name}")
print(f"Formula: {mea.formula}")
print(f"Type: {mea.solvent_type}")
print()
print("Thermodynamic Properties:")
print(f"  Molecular weight: {mea.MW:.2f} g/mol")
print(f"  Heat of absorption: {mea.heat_of_absorption:.1f} kJ/mol CO2")
print(f"  Max loading capacity: {mea.loading_capacity:.2f} mol CO2/mol amine")
print()
print("Kinetic Properties:")
print(f"  Reaction mechanism: {mea.kinetics['mechanism']}")
print(f"  Rate constant k2 (25°C): {mea.kinetics['k2_25C']:.0f} L/(mol·s)")
print()
print("Operating Conditions:")
print(f"  Typical concentration: {mea.typical_concentration:.0f} wt%")
print(f"  Regeneration temperature: {mea.regen_temperature - 273.15:.0f} °C")
print(f"  Expected regen energy: {mea.regen_energy:.1f} GJ/tonne CO2")
Solvent: Monoethanolamine
Formula: C2H7NO
Type: primary

Thermodynamic Properties:
  Molecular weight: 61.08 g/mol
  Heat of absorption: 82.0 kJ/mol CO2
  Max loading capacity: 0.50 mol CO2/mol amine

Kinetic Properties:
  Reaction mechanism: zwitterion
  Rate constant k2 (25°C): 5900 L/(mol·s)

Operating Conditions:
  Typical concentration: 30 wt%
  Regeneration temperature: 120 °C
  Expected regen energy: 3.8 GJ/tonne CO2

4. Setting Up the Absorber#

The absorber is modeled using the Kremser equation with Murphree stage efficiency:

\[\phi = \frac{A - 1}{A^{N+1} - 1}\]

where:

  • \(\phi\) = fraction of CO2 remaining in gas

  • \(A\) = absorption factor = L/(mG), the ratio of liquid capacity to gas loading

  • \(N\) = number of theoretical stages

# Define flue gas feed (typical coal power plant composition)
# 100 mol/s total, 15% CO2
flue_gas = make_stream(
    flows={"CO2": 15.0, "N2": 85.0},
    T=313.15,  # 40°C
    P=101325.0,  # 1 atm
)

print("Flue Gas Feed:")
print(f"  Total flow: {total_flow(flue_gas):.1f} mol/s")
print(f"  CO2 flow: {flue_gas['F_CO2']:.1f} mol/s")
print(f"  CO2 fraction: {float(flue_gas['F_CO2'] / total_flow(flue_gas)):.1%}")
print(f"  Temperature: {float(flue_gas['T']) - 273.15:.1f} °C")
Flue Gas Feed:
  Total flow: 100.0 mol/s
  CO2 flow: 15.0 mol/s
  CO2 fraction: 15.0%
  Temperature: 40.0 °C
# Configure absorber
absorber_params = AbsorberParams(
    solvent="MEA",
    n_stages=10,           # Number of theoretical stages
    solvent_conc=30.0,     # 30 wt% MEA solution
    L_G_ratio=3.0,         # Liquid/gas molar ratio
    T_liquid_in=313.15,    # Solvent inlet at 40°C
    lean_loading=0.2,      # mol CO2 / mol MEA (from stripper)
    stage_efficiency=0.25, # Murphree efficiency
)

# Create absorber unit
absorber = AmineAbsorber(absorber_params)

print("Absorber Configuration:")
for key, value in absorber_params.items():
    if key in ['solvent', 'n_stages', 'solvent_conc', 'L_G_ratio', 'lean_loading']:
        print(f"  {key}: {value}")
Absorber Configuration:
  solvent: MEA
  n_stages: 10
  solvent_conc: 30.0
  L_G_ratio: 3.0
  lean_loading: 0.2
# Run absorber simulation
treated_gas, rich_solvent, absorber_info = absorber(flue_gas)

print("Absorber Results:")
print(f"  Capture efficiency: {float(absorber_info['capture_efficiency']):.1%}")
print(f"  CO2 captured: {float(absorber_info['CO2_captured']):.2f} mol/s")
print(f"  Rich loading: {float(absorber_info['rich_loading']):.3f} mol CO2/mol amine")
print(f"  Absorption factor A: {float(absorber_info['absorption_factor']):.2f}")
print()
print(f"Treated Gas:")
print(f"  CO2 remaining: {float(treated_gas['F_CO2']):.2f} mol/s")
print(f"  CO2 fraction: {float(treated_gas['F_CO2'] / (treated_gas['F_CO2'] + treated_gas['F_N2'])):.2%}")
Absorber Results:
  Capture efficiency: 67.3%
  CO2 captured: 10.09 mol/s
  Rich loading: 0.500 mol CO2/mol amine
  Absorption factor A: 51.43

Treated Gas:
  CO2 remaining: 4.91 mol/s
  CO2 fraction: 5.46%

5. Setting Up the Stripper#

The stripper regenerates the rich solvent by heating. The reboiler duty consists of:

  1. Sensible heat: Heating the solvent to reboiler temperature

  2. Heat of reaction: Reversing the CO2-amine reaction (endothermic)

  3. Heat of vaporization: Generating stripping steam

# Configure stripper
stripper_params = StripperParams(
    solvent="MEA",
    n_stages=8,
    T_reboiler=393.15,        # 120°C
    P_stripper=200000.0,       # 2 bar
    target_lean_loading=0.2,   # Target lean loading
    reflux_ratio=0.3,
    cross_exchanger_approach=10.0,  # 10 K approach in heat exchanger
)

stripper = AmineStripper(stripper_params)

# Run stripper
lean_solvent, co2_product, stripper_info = stripper(rich_solvent)

print("Stripper Results:")
print(f"  Lean loading: {float(stripper_info['lean_loading']):.3f} mol CO2/mol amine")
print(f"  CO2 stripped: {float(stripper_info['CO2_stripped']):.2f} mol/s")
print(f"  CO2 purity: {float(stripper_info['CO2_purity']):.1%}")
print()
print("Energy Breakdown:")
print(f"  Reboiler duty: {float(stripper_info['reboiler_duty'])/1000:.1f} kW")
print(f"    - Sensible heat: {float(stripper_info['Q_sensible'])/1000:.1f} kW")
print(f"    - Heat of reaction: {float(stripper_info['Q_reaction'])/1000:.1f} kW")
print(f"    - Vaporization: {float(stripper_info['Q_vaporization'])/1000:.1f} kW")
print()
print(f"  Specific energy: {float(stripper_info['specific_energy']):.2f} GJ/tonne CO2")
Stripper Results:
  Lean loading: 0.227 mol CO2/mol amine
  CO2 stripped: 9.18 mol/s
  CO2 purity: 41.7%

Energy Breakdown:
  Reboiler duty: 1772.5 kW
    - Sensible heat: 274.0 kW
    - Heat of reaction: 752.5 kW
    - Vaporization: 746.1 kW

  Specific energy: 4.39 GJ/tonne CO2

6. Comparing Amine Solvents#

Different amines offer trade-offs:

  • Primary amines (MEA): Fast kinetics, high absorption, but high regen energy

  • Tertiary amines (MDEA): Slower, but lower regen energy

  • Sterically hindered (AMP): Balance of properties

  • Cyclic diamines (PZ): Very fast, high capacity

def simulate_capture_loop(solvent_name, flue_gas, l_g_ratio=3.0):
    """Simulate complete capture loop for a given solvent."""
    solvent = get_solvent(solvent_name)
    
    # Absorber
    abs_params = AbsorberParams(
        solvent=solvent_name,
        n_stages=10,
        solvent_conc=solvent.typical_concentration,
        L_G_ratio=l_g_ratio,
        lean_loading=0.2,
    )
    absorber = AmineAbsorber(abs_params)
    treated_gas, rich_solvent, abs_info = absorber(flue_gas)
    
    # Stripper
    strip_params = StripperParams(
        solvent=solvent_name,
        T_reboiler=solvent.regen_temperature,
        target_lean_loading=0.2,
    )
    stripper = AmineStripper(strip_params)
    lean_solvent, co2_product, strip_info = stripper(rich_solvent)
    
    return {
        'capture_efficiency': float(abs_info['capture_efficiency']),
        'rich_loading': float(abs_info['rich_loading']),
        'specific_energy': float(strip_info['specific_energy']),
        'heat_of_absorption': solvent.heat_of_absorption,
    }

# Compare solvents
solvents = ['MEA', 'DEA', 'MDEA', 'PZ', 'AMP']

print(f"{'Solvent':<8} {'Type':<12} {'Capture':<10} {'Rich Load':<12} {'ΔH_abs':<10} {'Energy':<10}")
print(f"{'':8} {'':12} {'(%)':10} {'(mol/mol)':12} {'(kJ/mol)':10} {'(GJ/t)':10}")
print("-" * 62)

for s in solvents:
    solvent_data = get_solvent(s)
    result = simulate_capture_loop(s, flue_gas)
    print(f"{s:<8} {solvent_data.solvent_type:<12} {result['capture_efficiency']:.1%}      "
          f"{result['rich_loading']:.3f}        "
          f"{result['heat_of_absorption']:.1f}       "
          f"{result['specific_energy']:.2f}")
Solvent  Type         Capture    Rich Load    ΔH_abs     Energy    
                      (%)        (mol/mol)    (kJ/mol)   (GJ/t)    
--------------------------------------------------------------
MEA      primary      67.3%      0.500        82.0       4.39
DEA      secondary    50.6%      0.500        72.0       4.49
MDEA     tertiary     0.4%      0.201        55.0       179.29
PZ       cyclic_diamine 99.9%      0.609        75.0       4.08
AMP      sterically_hindered 9.9%      0.262        65.0       8.12

7. Sensitivity Analysis: L/G Ratio#

The liquid-to-gas ratio (L/G) is a key operating parameter:

  • Higher L/G → Higher capture, but more solvent to regenerate

  • Lower L/G → Lower capture, but lower energy consumption

l_g_ratios = [2.0, 2.5, 3.0, 3.5, 4.0, 5.0]

print(f"{'L/G Ratio':<12} {'Capture (%)':<14} {'Rich Loading':<14}")
print("-" * 40)

for lg in l_g_ratios:
    result = simulate_capture_loop('MEA', flue_gas, l_g_ratio=lg)
    print(f"{lg:<12.1f} {result['capture_efficiency']:.1%}          {result['rich_loading']:.3f}")
L/G Ratio    Capture (%)    Rich Loading  
----------------------------------------
2.0          44.9%          0.500
2.5          56.1%          0.500
3.0          67.3%          0.500
3.5          78.5%          0.500
4.0          89.7%          0.500
5.0          99.9%          0.467

8. Key Takeaways#

  1. MEA is the benchmark but has high regeneration energy (~3.5-4 GJ/t)

  2. Trade-offs exist between:

    • Capture efficiency vs energy consumption

    • Reaction kinetics vs regeneration energy

    • Solvent capacity vs corrosivity

  3. Operating parameters matter:

    • L/G ratio directly affects capture and circulation costs

    • Lean loading affects both capture and regen energy

    • Reboiler temperature must balance stripping vs degradation

  4. difflow_cc enables:

    • Rapid screening of solvents and conditions

    • Sensitivity analysis

    • Gradient-based optimization (in next notebooks)

Next Steps#

  • 02_membrane_separation.ipynb: Membrane-based CO2 capture

  • 03_adsorption_processes.ipynb: PSA, TSA, VSA cycles

  • 04_optimization.ipynb: Gradient-based optimization