Skip to content

Two-bed VSA with pressure equalisation ​

This example builds a process of two beds that work in turn: while one bed is pressurised and fed, the other is evacuated. Between the two halves of the cycle, a pressure equalisation joins the tops of the beds, so that the bed that has just been fed passes some of its gas to the bed that has just been evacuated, instead of sending it to the vacuum pump.

feed ──V_feed_A──┐               ┌──V_product_A── raffinate
                 bottom  A  top ─┤
CO2 ◄─V_vacuum_A─┘               │
                                V_eq
feed ──V_feed_B──┐               │
                 bottom  B  top ─┤
CO2 ◄─V_vacuum_B─┘               └──V_product_B── raffinate

The cycle has six stages:

StageBed ABed B
1pressurisationevacuation
2adsorptionevacuation
3equalisation, A to Bequalisation, A to B
4evacuationpressurisation
5evacuationadsorption
6equalisation, B to Aequalisation, B to A

The beds and gas are those of Haghpanah et al. (2013), as in Getting started. The same process is ready-made as Mocca.two_bed_vsa_process().

julia
import Mocca
using Mocca.Units

1. The flowsheet ​

Two beds, each with a feed, a product and an extract device, and a link between their tops. A link is a valve with a bed at each end; the equalisation steps find it by the beds it joins.

julia
gas = Mocca.haghpanah_2013_gas()
feed = Mocca.Feed(gas; composition = (CO2 = 0.15, N2 = 0.85), temperature = 298.15, pressure = 1bar)
adsorbent = Mocca.load_adsorbent("zeolite_13x_haghpanah_2013")
column = Mocca.haghpanah_2013_column()

fs = Mocca.Flowsheet(gas, feed)
for b in (:A, :B)
    Mocca.add_bed!(fs, b, adsorbent, column; ncells = 20)
    Mocca.add_device!(fs, Symbol(:V_feed_, b), :feed; at = (b, :bottom))
    Mocca.add_device!(fs, Symbol(:V_product_, b), :product; at = (b, :top))
    Mocca.add_device!(fs, Symbol(:V_vacuum_, b), :extract; at = (b, :bottom))
end
Mocca.add_device!(fs, :V_eq, :link; from = (:A, :top), to = (:B, :top))
fs
Flowsheet with 2 units and 7 devices for CO2, N2
  A: fixed bed of zeolite 13X, 20 cells
    bottom V_feed_A (feed), from boundary at 1.0 bar; V_vacuum_A (extract), to boundary at 1.0 bar
    top    V_product_A (product), to boundary at 1.0 bar; V_eq (link) to B top
  B: fixed bed of zeolite 13X, 20 cells
    bottom V_feed_B (feed), from boundary at 1.0 bar; V_vacuum_B (extract), to boundary at 1.0 bar
    top    V_product_B (product), to boundary at 1.0 bar; V_eq (link) to A top

2. The cycle ​

With several beds, each step names its bed, and Concurrent runs steps on different beds at the same time, as one stage. Equalise opens the link between two beds, and gas flows from the one at the higher pressure. The size of the link valve, its conductance [mol/(s·Pa)], sets how far the pressures come together in the time given.

Pressures carry over from step to step within each bed. After an equalisation both beds are taken to be at the mean of their pressures, which is where the next pressurisation and evacuation ramps start. The evacuation of each bed runs through two stages, the second carrying on along the ramp of the first.

julia
t_press, t_ads, t_eq = 15.0, 15.0, 10.0
P_eq = 0.55bar
evacuate(b, t, from) = Mocca.Evacuate(t; bed = b, from = from, to = 0.1bar)
P_mid = 0.1bar + (P_eq - 0.1bar) * exp(-0.5 * t_press)   # the ramp at 0.5 s⁻¹ after the first stage
cycle = Mocca.Cycle([
    Mocca.Concurrent(Mocca.Pressurise(t_press; bed = :A, from = P_eq), evacuate(:B, t_press, P_eq)),
    Mocca.Concurrent(Mocca.Adsorb(t_ads; bed = :A, velocity = 1.0), evacuate(:B, t_ads, P_mid)),
    Mocca.Equalise(t_eq; beds = (:A, :B), conductance = 1e-6),
    Mocca.Concurrent(Mocca.Pressurise(t_press; bed = :B, from = P_eq), evacuate(:A, t_press, P_eq)),
    Mocca.Concurrent(Mocca.Adsorb(t_ads; bed = :B, velocity = 1.0), evacuate(:A, t_ads, P_mid)),
    Mocca.Equalise(t_eq; beds = (:B, :A), conductance = 1e-6),
])
Cycle of 10 steps
  pressurisation      15.0 s  A
  evacuation          15.0 s  B
  adsorption          15.0 s  A
  evacuation          15.0 s  B
  equalisation A to B    10.0 s  A, B
  pressurisation      15.0 s  B
  evacuation          15.0 s  A
  adsorption          15.0 s  B
  evacuation          15.0 s  A
  equalisation B to A    10.0 s  B, A

The process lists each stage and the devices it opens:

julia
process = Mocca.Process(fs, cycle; name = "two-bed VSA")
Process two-bed VSA: CO2/N2 feed at 1.0 bar, 298.15 K, capturing CO2
  A: fixed bed of zeolite 13X, 20 cells
    bottom V_feed_A (feed), from boundary at 1.0 bar; V_vacuum_A (extract), to boundary at 0.1 bar
    top    V_product_A (product), to boundary at 1.0 bar; V_eq (link) to B top
  B: fixed bed of zeolite 13X, 20 cells
    bottom V_feed_B (feed), from boundary at 1.0 bar; V_vacuum_B (extract), to boundary at 0.1 bar
    top    V_product_B (product), to boundary at 1.0 bar; V_eq (link) to A top
  cycle of 6 stages, 80.0 s:
    A pressurisation, B evacuation        15.0 s   V_feed_A, V_vacuum_B
    A adsorption, B evacuation            15.0 s   V_feed_A, V_product_A, V_vacuum_B
    equalisation A to B                   10.0 s   V_eq
    B pressurisation, A evacuation        15.0 s   V_vacuum_A, V_feed_B
    B adsorption, A evacuation            15.0 s   V_vacuum_A, V_feed_B, V_product_B
    equalisation B to A                   10.0 s   V_eq

3. Cyclic steady state and performance ​

Two beds approach their steady state more slowly than one when the cycle is simply repeated, but after three plain cycles Newton's method needs only about ten iterations.

julia
result = Mocca.cyclic_steady_state(process)
perf = Mocca.performance(result)
println("converged: $(result.converged) after $(result.iterations) cycles")
println("purity $(round(100perf.purity, digits = 1))%, recovery $(round(100perf.recovery, digits = 1))%, ",
    "$(round(perf.specific_energy, digits = 1)) kWh/t")
converged: true after 14 cycles
purity 57.4%, recovery 60.6%, 93.4 kWh/t

The streams through the devices over the cycle. The link carries gas from A to B in the first equalisation (positive flow, from its inlet at A) and back in the second:

julia
f_streams = Mocca.plot_flowsheet_streams(result.states, process.model, result.timesteps;
    stages = Mocca.stages(process), devices = [:V_feed_A, :V_vacuum_A, :V_product_A, :V_eq])

The top of each bed through the cycle. The beds run half a cycle apart, and their pressures meet at about half a bar in each equalisation:

julia
f_beds = Mocca.plot_cell_comparison(["A" => ([s[:A] for s in result.states], result.timesteps),
    "B" => ([s[:B] for s in result.states], result.timesteps)], fs[:A], 20)

4. The stream table of each stage ​

write_results writes the stream table of each stage to stage_streams.csv. It shows how much gas moves between the beds during equalisation, and what it is:

julia
dir = joinpath(mktempdir(), "two_bed")
Mocca.write_results(dir, result)
lines = readlines(joinpath(dir, "stage_streams.csv"))
header = split(lines[1], ',')
columns = indexin(["Stage", "Device", "From", "To", "CO2 [mol%]", "Total [mol/stage]"], header)
for line in filter(contains("equalisation"), lines)
    println(join(split(line, ',')[columns], ", "))
end
equalisation A to B, V_eq, A top, B top, 9.7833015, 1.309432
equalisation B to A, V_eq, A top, B top, 9.7833015, -1.309432

About 1.3 mol of gas, with 10% CO₂, moves from A to B in the first equalisation, and the same back in the second (a negative amount: against the direction of the link).

Stage names with a comma, such as "A adsorption, B evacuation", are quoted in the file, so only the equalisation rows are split this simply here. A spreadsheet or a CSV reader such as CSV.jl reads them all.

The mass balance over the cycle in summary.csv closes over both beds:

julia
foreach(println, filter(startswith("Mass balance"), readlines(joinpath(dir, "summary.csv"))))
Mass balance,CO2 in,4.9519554,mol/cycle
Mass balance,CO2 out,4.9519554,mol/cycle
Mass balance,CO2 accumulated in beds,-4.9283244e-10,mol/cycle
Mass balance,CO2 imbalance,1.3035499e-8,% of in
Mass balance,N2 in,28.06108,mol/cycle
Mass balance,N2 out,28.06108,mol/cycle
Mass balance,N2 accumulated in beds,6.8973272e-11,mol/cycle
Mass balance,N2 imbalance,-2.3004803e-9,% of in
Mass balance,Total in,33.013036,mol/cycle
Mass balance,Total out,33.013036,mol/cycle
Mass balance,Total accumulated in beds,-4.2385917e-10,mol/cycle
Mass balance,Total imbalance,-8.8782771e-14,% of in

5. What the equalisation is worth ​

A design variable that sets the conductance of the link in both equalisation steps. Nearly closing it leaves the gas of the fed bed to the vacuum pump. The steady state for the new setting starts from the one already found.

julia
Mocca.add_variable!(process, :C_eq, ("equalisation A to B", :conductance), ("equalisation B to A", :conductance))
process[:C_eq] = 1e-9
result_no_eq = Mocca.cyclic_steady_state(process)
println("converged: $(result_no_eq.converged) after $(result_no_eq.iterations) cycles")
no_eq = Mocca.performance(result_no_eq)
for (label, p) in ("with equalisation" => perf, "without" => no_eq)
    println(rpad(label, 20), "purity $(round(100p.purity, digits = 1))%, recovery $(round(100p.recovery, digits = 1))%, ",
        "$(round(p.specific_energy, digits = 1)) kWh/t")
end
converged: true after 9 cycles
with equalisation   purity 57.4%, recovery 60.6%, 93.4 kWh/t
without             purity 41.3%, recovery 59.2%, 101.3 kWh/t

Equalisation sends the nitrogen-rich gas left in the fed bed to the other bed, rather than into the CO₂ product through the vacuum line, which raises the purity by about 16 percentage points and saves pump work.

Example on GitHub ​

If you would like to run this example yourself, it can be downloaded from the Mocca.jl GitHub repository.


This page was generated using Literate.jl.