Membrane-Based CO2 Separation#
This notebook explores membrane-based gas separation for CO2 capture. Membranes offer advantages over absorption:
No solvent regeneration needed
Modular and scalable
Lower energy for moderate purity requirements
Learning Objectives#
Understand the solution-diffusion model for gas permeation
Compare different membrane materials
Design single and multi-stage membrane systems
Analyze trade-offs between recovery, purity, and area
1. Background: Membrane Gas Separation#
The Solution-Diffusion Model#
Gas permeates through dense polymer membranes by:
Dissolving into the membrane at the high-pressure side
Diffusing through the membrane material
Desorbing at the low-pressure side
The flux of component \(i\) is:
where:
\(P_i\) = permeability (Barrer = 10⁻¹⁰ cm³(STP)·cm/(cm²·s·cmHg))
\(\delta\) = membrane thickness
\(p_i\) = partial pressure of component \(i\)
Selectivity#
The selectivity determines separation quality:
For CO2/N2 separation:
Commercial polymers: α = 20-40
High-performance materials: α = 40-100+
Robeson Upper Bound#
There’s a fundamental trade-off: high permeability membranes tend to have low selectivity. The Robeson upper bound (2008) defines the empirical limit:
2. Setup#
import jax
import jax.numpy as jnp
jax.config.update("jax_enable_x64", True)
from difflow.streams import make_stream, get_flows, total_flow
from difflow_cc import (
get_membrane, list_membranes,
MembraneParams, MembraneSeparator,
)
from difflow_cc.units.membrane import MultistageMembrane
print("Available membranes:", list_membranes())
Available membranes: ['Matrimid', 'PDMS', 'Cellulose_Acetate', 'PIM_1', 'ZIF8_Matrimid', 'MOF74_Polymer', 'PVAm_Carrier', 'IL_SIL_Membrane', 'CMS', 'Zeolite_DDR']
3. Exploring the Membrane Database#
# Get Matrimid properties (common commercial polyimide)
matrimid = get_membrane("Matrimid")
print(f"Membrane: {matrimid.full_name}")
print(f"Type: {matrimid.membrane_type}")
print()
print("Permeability (Barrer):")
for gas, perm in matrimid.permeability.items():
print(f" {gas}: {perm}")
print()
print("Selectivity:")
for pair, sel in matrimid.selectivity.items():
print(f" {pair}: {sel}")
print()
print(f"Typical thickness: {matrimid.typical_thickness} μm")
print(f"Max temperature: {matrimid.max_temperature - 273.15:.0f} °C")
print(f"Cost: ${matrimid.cost_usd_m2}/m²")
Membrane: Matrimid 5218 (polyimide)
Type: polymeric
Permeability (Barrer):
CO2: 10.0
N2: 0.32
CH4: 0.28
O2: 2.1
H2: 28.0
Selectivity:
CO2_N2: 31.0
CO2_CH4: 36.0
O2_N2: 6.5
Typical thickness: 0.1 μm
Max temperature: 250 °C
Cost: $50.0/m²
# Compare all membrane materials
print(f"{'Membrane':<18} {'Type':<12} {'P_CO2 (Barrer)':<16} {'α (CO2/N2)':<12}")
print("-" * 58)
for mem_name in list_membranes():
mem = get_membrane(mem_name)
p_co2 = mem.permeability.get('CO2', 0)
alpha = mem.selectivity.get('CO2_N2', mem.selectivity.get('CO2_CH4', 0))
print(f"{mem_name:<18} {mem.membrane_type:<12} {p_co2:<16.1f} {alpha:<12.1f}")
Membrane Type P_CO2 (Barrer) α (CO2/N2)
----------------------------------------------------------
Matrimid polymeric 10.0 31.0
PDMS polymeric 3200.0 11.4
Cellulose_Acetate polymeric 6.0 29.0
PIM_1 polymeric 2300.0 25.0
ZIF8_Matrimid mixed_matrix 25.0 38.5
MOF74_Polymer mixed_matrix 800.0 44.0
PVAm_Carrier facilitated_transport 50.0 50.0
IL_SIL_Membrane facilitated_transport 1000.0 40.0
CMS ceramic 180.0 51.0
Zeolite_DDR ceramic 100.0 67.0
4. Single-Stage Membrane Separation#
Key design parameters:
Area: Larger area → higher recovery, but more cost
Pressure ratio: Higher ratio → better driving force
Stage cut: Fraction of feed that permeates (θ = F_permeate/F_feed)
# Define feed gas (flue gas at elevated pressure)
feed = make_stream(
flows={"CO2": 1.5, "N2": 8.5}, # 15% CO2
T=298.15, # 25°C
P=1000000.0, # 10 bar
)
print("Feed Gas:")
print(f" Total flow: {total_flow(feed):.1f} mol/s")
print(f" CO2: {float(feed['F_CO2']):.1f} mol/s ({float(feed['F_CO2']/total_flow(feed)):.0%})")
print(f" Pressure: {float(feed['P'])/1e5:.1f} bar")
Feed Gas:
Total flow: 10.0 mol/s
CO2: 1.5 mol/s (15%)
Pressure: 10.0 bar
# Configure membrane separator
params = MembraneParams(
membrane_type="Matrimid",
area=1000.0, # m²
pressure_ratio=10.0, # Feed/permeate pressure
feed_pressure=1000000.0, # 10 bar
T_operation=298.15,
)
membrane = MembraneSeparator(params)
# Run separation
retentate, permeate, info = membrane(feed)
print("Membrane Separation Results:")
print(f" Stage cut: {float(info['stage_cut']):.2%}")
print(f" CO2 recovery: {float(info['CO2_recovery']):.1%}")
print(f" CO2 purity in permeate: {float(info['CO2_purity']):.1%}")
print()
print("Permeate (CO2-enriched):")
perm_flows = get_flows(permeate)
print(f" Flow: {float(info['permeate_flow']):.3f} mol/s")
print(f" Pressure: {float(permeate['P'])/1e5:.1f} bar")
print()
print("Retentate (treated gas):")
ret_flows = get_flows(retentate)
print(f" Flow: {float(info['retentate_flow']):.3f} mol/s")
print(f" CO2 remaining: {float(ret_flows['CO2']):.3f} mol/s")
Membrane Separation Results:
Stage cut: 18.23%
CO2 recovery: 59.1%
CO2 purity in permeate: 48.6%
Permeate (CO2-enriched):
Flow: 1.823 mol/s
Pressure: 1.0 bar
Retentate (treated gas):
Flow: 8.177 mol/s
CO2 remaining: 0.614 mol/s
5. Effect of Membrane Area#
Larger membrane area increases recovery but has diminishing returns.
areas = [100, 250, 500, 1000, 2000, 5000]
print(f"{'Area (m²)':<12} {'Stage Cut':<12} {'CO2 Recovery':<14} {'CO2 Purity':<12}")
print("-" * 50)
for area in areas:
params = MembraneParams(
membrane_type="Matrimid",
area=float(area),
pressure_ratio=10.0,
feed_pressure=1000000.0,
)
membrane = MembraneSeparator(params)
_, _, info = membrane(feed)
print(f"{area:<12} {float(info['stage_cut']):.2%} "
f"{float(info['CO2_recovery']):.1%} "
f"{float(info['CO2_purity']):.1%}")
Area (m²) Stage Cut CO2 Recovery CO2 Purity
--------------------------------------------------
100 3.00% 14.0% 70.1%
250 6.50% 28.1% 64.9%
500 11.04% 42.8% 58.2%
1000 18.23% 59.1% 48.6%
2000 30.17% 74.9% 37.2%
5000 61.62% 92.1% 22.4%
6. Comparing Membrane Materials#
membranes = ["Matrimid", "PDMS", "CA", "PIM1", "ZIF8_Matrimid"]
print(f"{'Membrane':<18} {'Stage Cut':<12} {'CO2 Recovery':<14} {'CO2 Purity':<12}")
print("-" * 56)
for mem_name in membranes:
try:
params = MembraneParams(
membrane_type=mem_name,
area=1000.0,
pressure_ratio=10.0,
feed_pressure=1000000.0,
)
membrane = MembraneSeparator(params)
_, _, info = membrane(feed)
print(f"{mem_name:<18} {float(info['stage_cut']):.2%} "
f"{float(info['CO2_recovery']):.1%} "
f"{float(info['CO2_purity']):.1%}")
except Exception as e:
print(f"{mem_name:<18} Error: {e}")
Membrane Stage Cut CO2 Recovery CO2 Purity
--------------------------------------------------------
Matrimid 18.23% 59.1% 48.6%
PDMS 100.00% 100.0% 15.0%
CA Error: "Unknown membrane: CA. Available: ['Matrimid', 'PDMS', 'Cellulose_Acetate', 'PIM_1', 'ZIF8_Matrimid', 'MOF74_Polymer', 'PVAm_Carrier', 'IL_SIL_Membrane', 'CMS', 'Zeolite_DDR']"
PIM1 Error: "Unknown membrane: PIM1. Available: ['Matrimid', 'PDMS', 'Cellulose_Acetate', 'PIM_1', 'ZIF8_Matrimid', 'MOF74_Polymer', 'PVAm_Carrier', 'IL_SIL_Membrane', 'CMS', 'Zeolite_DDR']"
ZIF8_Matrimid 18.78% 61.5% 49.1%
7. Multi-Stage Membrane Cascades#
Single-stage membranes face a trade-off: high recovery means low purity.
Multi-stage configurations can achieve both:
Series: Each stage treats the retentate from the previous
Permeate recycle: Second stage enriches the permeate further
# Single stage baseline
single_params = MembraneParams(
membrane_type="Matrimid",
area=500.0,
pressure_ratio=10.0,
feed_pressure=1000000.0,
)
single = MembraneSeparator(single_params)
_, _, single_info = single(feed)
# Two-stage series
series_cascade = MultistageMembrane(
single_params,
n_stages=2,
configuration="series"
)
_, _, series_info = series_cascade(feed)
# Two-stage permeate recycle
recycle_cascade = MultistageMembrane(
single_params,
n_stages=2,
configuration="permeate_recycle"
)
_, _, recycle_info = recycle_cascade(feed)
print(f"{'Configuration':<25} {'CO2 Recovery':<15} {'CO2 Purity':<15}")
print("-" * 55)
print(f"{'Single stage (500 m²)':<25} {float(single_info['CO2_recovery']):.1%} "
f"{float(single_info['CO2_purity']):.1%}")
print(f"{'Two-stage series':<25} {float(series_info['overall_CO2_recovery']):.1%} "
f"{float(series_info['overall_CO2_purity']):.1%}")
print(f"{'Two-stage w/ recycle':<25} {float(recycle_info['overall_CO2_recovery']):.1%} "
f"{float(recycle_info['overall_CO2_purity']):.1%}")
Configuration CO2 Recovery CO2 Purity
-------------------------------------------------------
Single stage (500 m²) 42.8% 58.2%
Two-stage series 66.0% 51.5%
Two-stage w/ recycle 42.8% 58.2%
8. Key Takeaways#
Membrane separation is governed by permeability and selectivity
Trade-offs exist:
Recovery vs purity (single stage)
Permeability vs selectivity (Robeson bound)
Area vs cost
Material selection depends on application:
PDMS: High flux, moderate selectivity (natural gas)
Polyimides: Good selectivity (post-combustion)
Mixed-matrix: Approaching upper bound
Multi-stage configurations overcome single-stage limitations