Skip to content

Results and plots ​

simulate and cyclic_steady_state return a ProcessResult. It holds the state of every bed and device after each time step the solver took:

FieldContents
result.statesOne state per time step; result.states[k][:Bed] is the state of unit :Bed
result.timestepsLength of each time step [s]; cumsum(result.timesteps) gives the times
result.cyclesNumber of cycles simulated
result.converged, result.iterations, result.historyFor a cyclic steady state: whether it converged, in how many iterations, and the change per iteration

A result of simulate covers every cycle simulated, from time 0. A result of cyclic_steady_state covers only the steady cycle, with its times measured from the start of that cycle.

Following the approach to steady state ​

In the REPL, cyclic_steady_state prints each cycle as it runs. What it prints, and how it finds the steady state, are on the Cyclic steady state page.

Performance ​

performance returns a NamedTuple; the formulas are on the Model equations page.

FieldMeaningUnit
purityMole fraction of the key component in the extract–
recoveryFraction of the key component fed that leaves in the extract–
specific_energyPump and compressor work per tonne of key component in the extractkWh/t
productivityKey component in the extract per m³ of adsorbent per secondmol/(m³·s)
energyPump and compressor work over the cycleJ
cycle_timeDuration of the cycles
feed, extract, productMoles of each component through the feed, extract and light product devices over the cyclemol
adsorbent_volume, adsorbent_massAdsorbent in all bedsm³, kg
julia
perf = performance(result)
perf.purity, perf.recovery
perf.extract[1] * 44.01e-3       # kg of CO₂ captured per cycle

Variables of beds and devices ​

The state of a fixed bed holds these variables. Values per cell are ordered from the bottom of the bed (cell 1,  ) to the top (cell ncells), and values per component are in the order of the Gas.

VariableSizeMeaningUnit
:PressurencellsGas pressurePa
:TemperaturencellsGas and sorbent temperatureK
:WallTemperaturencellsWall temperature (WithWall beds only)K
:ycomponents × ncellsGas mole fractions–
:AdsorbedConcentrationcomponents × ncellsLoading mol/m³ adsorbent

To convert a loading to mol/kg, divide it by the adsorbent density. The cell centres are at fs[:Bed].data_domain[:cell_centroids][1, :] [m], for a flowsheet fs (process.flowsheet).

The state of a flow device holds the flow through it and the gas at each of its two ports:

VariableSizeMeaningUnit
:MolarFlow1Molar flow, positive from inlet to outletmol/s
:InletPressure, :OutletPressure1Pressure at each portPa
:InletTemperature, :OutletTemperature1Temperature at each portK
:InletComposition, :OutletCompositioncomponents × 1Mole fractions at each port–

For example, the CO₂ mole fraction leaving the top of the bed, and the flow through the vacuum line, through time:

julia
t = cumsum(result.timesteps)
y_CO2_top = [s[:Bed][:y][1, end] for s in result.states]
F_vacuum = [s[:V_vacuum][:MolarFlow][1] for s in result.states]

stream_totals adds up the moles of each component that passed through a device:

julia
n = stream_totals(result.states, result.timesteps, :V_vacuum)   # mol of CO₂, mol of N₂

Export to a spreadsheet ​

write_results writes a result to a folder of CSV files, in bara, °C, mol%, kg/h, Nm³/h and mol/kg, with the units in the column headers:

julia
result = cyclic_steady_state(process)
write_results("results/base_case", result)

For the four-step VSA of Mocca's library, some of the columns of the stream table streams.csv:

DeviceCO2 [mol%]N2 [mol%]Mean flow [kg/h]Total [mol/cycle]
V_feed15.085.019.4517.77
V_product10.289.817.6416.54
V_vacuum79.5120.491.8031.229
FileContents
summary.csvFeed, stage durations, purity, recovery, productivity, specific energy, the mass balance of each component over the cycle, and the beds
streams.csvStream table: one row per device, with its composition, mean flow and amount per cycle
stage_streams.csvThe stream table for each stage
stream_history.csvFlow, composition, temperature and pressures of every device at every time step
bed_history.csvPressure, temperatures, gas composition and loading at both ends of each bed at every time step
bed_profiles.csvThe same along each bed, at the start of the cycle and the end of each stage
process.jsonThe process, to read back with read_process

The time series have one row per time step and device (or bed end), with the cycle and stage it belongs to, so a pivot table or a filter picks out one device or stage. For a spreadsheet set up with a decimal comma, use write_results(dir, result; delim = ';', decimal = ','). The Getting started example writes these files and reads some of them back.

For other variables, write the CSV file yourself:

julia
t = cumsum(result.timesteps)
open("bed_top.csv", "w") do io
    println(io, "time_s,pressure_Pa,temperature_K,y_CO2,q_CO2_mol_per_m3")
    for (tk, s) in zip(t, result.states)
        b = s[:Bed]
        println(io, join((tk, b[:Pressure][end], b[:Temperature][end], b[:y][1, end],
            b[:AdsorbedConcentration][1, end]), ","))
    end
end

Plots ​

Mocca draws its plots with Makie. Each plot function returns a figure, which shows in VS Code's plot pane or a notebook. From a terminal, save it to a file instead: add CairoMakie to your environment (see Installation) and use save:

julia
using CairoMakie
bed = process.flowsheet[:Bed]
bed_states = [s[:Bed] for s in result.states]
f = plot_cell(bed_states, bed, result.timesteps, 30)   # history of cell 30, the top of a 30-cell bed
save("bed_top.png", f)
FunctionShows
plot_cellEvery variable of one cell through time, such as the outlet
plot_stateEvery variable along the bed at one time
plot_profilesMole fractions and loadings along the bed at several times, such as the end of each step
plot_flowsheet_streamsFlow and composition through each device, with the steps shaded
plot_cell_comparison, plot_state_comparisonSeveral simulations of the same bed overlaid

The figures below are for the steady cycle of the four-step VSA of Mocca's library, read with process = read_process("haghpanah_2013_four_stage_vsa") and run with result = cyclic_steady_state(process).

One cell through time ​

plot_cell shows every variable of one cell through the cycle. At the top of the bed (cell 30 of 30), the light product end:

julia
Mocca.plot_cell(bed_states, bed, result.timesteps, 30)

Along the bed at one time ​

plot_state shows every variable along the bed, here at the end of the cycle, after evacuation:

julia
Mocca.plot_state(bed_states[end], bed)

Profiles at the end of each step ​

plot_profiles shows mole fractions and loadings along the bed at several times, here the end of each step of the cycle:

julia
step_ends = cumsum([st.duration for st in stages])
Mocca.plot_profiles(result.states, result.timesteps, bed, step_ends;
    labels = [st.name for st in stages], unit = :Bed)

Streams through the devices ​

plot_flowsheet_streams shows the flow through every device and the mole fraction of the key component in it, with the steps shaded. Flow is positive from a device's inlet to its outlet:

julia
Mocca.plot_flowsheet_streams(result.states, process.model, result.timesteps; stages = stages)

Comparing simulations ​

plot_cell_comparison and plot_state_comparison overlay several simulations of the same bed. Here the evacuation pressure is lowered from 0.1 to 0.05 bar, and the feed end of the bed (cell 1) compared through the cycle:

julia
low = Mocca.read_process("haghpanah_2013_four_stage_vsa")
low[:P_L] = 0.05bar
result_low = Mocca.cyclic_steady_state(low; progress = false)
runs = ["P_L = 0.1 bar" => (bed_states, result.timesteps),
        "P_L = 0.05 bar" => ([s[:Bed] for s in result_low.states], result_low.timesteps)]
Mocca.plot_cell_comparison(runs, bed, 1)

and along the bed at the end of the cycle:

julia
Mocca.plot_state_comparison([label => first(r)[end] for (label, r) in runs], bed)

Mocca.Interface.ProcessResult Type

Result of simulating a Process: the states and timesteps of every step the solver took, the number of cycles they span, the initial state state0 of the last of them, and for a cyclic steady state whether it converged, the change per iteration or cycle in history and the cycle Jacobian if Newton's method found it.

source
Mocca.Interface.write_results Function
julia
write_results(dir, r::ProcessResult; sigdigits = 8, delim = ',', decimal = '.') → Vector{String}

Write result r to CSV files in directory dir (created if needed, files overwritten), and the process to process.json (see write_process) to rerun it. Returns the paths written.

The files hold, with the units in the column headers:

FileOne row perContents
summary.csvquantityFeed, cycle, purity, recovery, productivity, energy, mass balance and beds, as Section, Quantity, Value, Unit
streams.csvdeviceStream table over the last cycle: composition, mean flow, amount per cycle, temperature and pressures
stage_streams.csvstage and deviceThe same, for each stage of the last cycle, for devices with flow
stream_history.csvtime step and deviceFlow, composition, temperature and pressures through time
bed_history.csvtime step, bed and endPressure, temperatures, gas composition and loadings at the bottom and top of each bed
bed_profiles.csvsnapshot, bed and cellThe same along each bed, at the start of the last cycle and the end of each of its stages

Pressures are absolute, in bar (bara); temperatures in °C; compositions in mol%; loadings in mol per kg of adsorbent; normal volumes (Nm³) at 0 °C and 1 atm. The composition and temperature of a stream are those of the gas upstream of the device, and are left empty while nothing flows. Flows are positive from the device's inlet (From) to its outlet (To). Amounts of a stream that flows both ways are net amounts, with the gas that flowed back under Reverse flow; the composition of net amounts is left empty if some components went each way.

The time-series files hold one row per time step of the solver, with the cycle and the stage it belongs to, so that they can be filtered or pivoted. For spreadsheets set up with a decimal comma, pass delim = ';', decimal = ','.

source
Mocca.Plotting.plot_cell Function
julia
plot_cell(states, model, timesteps, cell)

History of every variable of cell of a bed through time, one panel per variable, such as the outlet (cell = ncells, the top of the bed) or the feed end (cell = 1). states belong to the bed alone: for a process, pass [s[:Bed] for s in result.states], with model the bed (process.flowsheet[:Bed]) and the timesteps of the result.

source
Mocca.Plotting.plot_state Function
julia
plot_state(state, model)

Every variable along a bed at one time, one panel per variable. state is the state of the bed alone, such as result.states[end][:Bed] at the end of a simulation, and model the bed (process.flowsheet[:Bed]).

source
Mocca.Plotting.plot_profiles Function
julia
plot_profiles(states, timesteps, model, times; labels, unit = nothing, components = all)

Profiles along a bed at the given times [s], measured from the start of states: the gas mole fraction of each of components (top row) and its loading (bottom row). For a dual-site Langmuir isotherm the loading is shown relative to the component's saturation capacity qsb + qsd, as in the bed profile figures of Haghpanah et al. and Krishnamurthy et al. For flowsheet states, unit names the bed and model is the bed's model. Each time uses the last state at or before it.

source
Mocca.Plotting.plot_flowsheet_streams Function
julia
plot_flowsheet_streams(states, model, timesteps; stages = nothing, component = 1)

Molar flow through each flow device of a flowsheet (positive from inlet to outlet), and the mole fraction of component in each stream while it flows. With stages (as passed to setup_schedule), the stages of each cycle are shaded.

source
Mocca.Plotting.plot_cell_comparison Function
julia
plot_cell_comparison(runs, model, cell; component = 1)

Overlay the history of cell in several simulations of the same unit, one panel per primary variable. runs is a vector of label => (states, timesteps), where states belong to that unit alone (for a flowsheet, pass [s[:Bed] for s in states]). For variables with one value per component only component is shown.

source
Mocca.Plotting.plot_state_comparison Function
julia
plot_state_comparison(states, model; component = 1)

Overlay profiles along the column from several states of the same unit. states is a vector of label => state.

source
Mocca.Operation.stream_totals Function
julia
stream_totals(states, timesteps, device; window = eachindex(states)) → Vector

Moles of each component that crossed device from its inlet to its outlet over the steps in window. Components flowing backwards count as negative.

source
Mocca.Operation.vacuum_pump_energy Function
julia
vacuum_pump_energy(states, timesteps, device; discharge_pressure = 101325.0,
    efficiency = 0.72, γ = 1.4, window = eachindex(states))

Work [J] to compress the gas leaving through device from its outlet pressure up to discharge_pressure, for an ideal gas with heat capacity ratio γ compressed adiabatically with isentropic efficiency. The gas enters the pump at the device's inlet temperature. Only steps with forward flow and an outlet below the discharge pressure count.

The defaults follow the vacuum pump model of Haghpanah et al. (2013).

source
Mocca.Operation.compressor_energy Function
julia
compressor_energy(states, timesteps, device; suction_pressure = 101325.0,
    efficiency = 0.72, γ = 1.4, window = eachindex(states))

Work [J] to compress the gas entering through device from suction_pressure up to the device's outlet pressure, such as a feed compressor taking flue gas at atmospheric pressure up to the bed inlet. The gas is compressed adiabatically with isentropic efficiency, from the device's inlet temperature. Only steps with forward flow and an outlet above the suction pressure count, as in CompressorObjective.

source
Mocca.Operation.specific_energy_kwh_per_tonne Function
julia
specific_energy_kwh_per_tonne(energy, n_product, molar_mass; component = 1)

Energy per tonne of component produced, in kWh/t, given the energy [J], the product component totals [mol] and the component's molar mass [kg/mol].

source