Post processing with the python interface to OpenFOAM
This tutorial demonstrates how to post process a case with the python interface to OpenFOAM provided in BiRD.
The tutorial assumes you have created and activated the bird
environment and setup path variables as
conda activate bird
BIRD_HOME=`python -c "import bird; print(bird.BIRD_DIR)"`
DCM_CASE=${BIRD_HOME}/postprocess/data_conditional_mean/
cd $DCM_CASE
The case and the data correspond to a coflowing bubble column.
This tutorial is code-along, meaning to that you can execute the commands listed in the code-blocks. You can also execute the entirety of the tutorial in $DCM_CASE/python_interface_tut.py
Reading fields
The mesh manipulation tools are located in bird.utilities.ofio
Before starting to manipulate the fields with the python interface, we need mesh descriptor object files, in particular meshCellCentres_1.obj
. This file contains the coordinates of the cell centers which is the location where the internal fields values are. The folder $DCM_CASE
contains pre-generated mesh descriptor object files. The user can generate these files with writeMeshObj -case {case_folder}
. This command will not work for this tutorial as it would require activating your OpenFOAM environment and would require a constant
and system
folders that we did not add because of space constraints.
To read the cells centers
from bird.utilities.ofio import *
cell_centers = readMesh("meshCellCentres_1.obj")
cell_centers
is a \((N,3)\) numpy array that contains the cell center coordinates (\(N\) is the number of cells in the domain)
If we want to read the gas volume fraction alpha.gas
, the mass fraction of CO2 in the gas phase CO2.gas
, and the liquid velocity at time 80/
we can run
co2_gas = readOF("80/CO2.gas")
alpha_gas = readOF("80/alpha.gas")
u_liq = readOF("80/U.liquid")
print("cell CO2 gas shape = ", co2_gas["field"].shape)
print("cell alpha gas shape = ", alpha_gas["field"].shape)
print("cell u liq shape = ", u_liq["field"].shape)
The function readOF
generates a dictionary of values and automatically detects whether the field is a vector or a scalar field.
Plot conditional means
With cells center locations and the internal fields, one can use python functions to post process the case. For example, we might want to compute conditional averages over a spatial direction, say y
from bird.utilities.mathtools import conditional_average
y_co2_gas_cond, co2_gas_cond = conditional_average(
cell_centers[:, 1], co2_gas["field"], nbins=32
)
y_alpha_gas_cond, alpha_gas_cond = conditional_average(
cell_centers[:, 1], alpha_gas["field"], nbins=32
)
from prettyPlot.plotting import *
fig = plt.figure()
plt.plot(y_co2_gas_cond, co2_gas_cond, color="k", label=r"$Y_{CO_2}$ [-]")
plt.plot(
y_alpha_gas_cond, alpha_gas_cond, color="b", label=r"$\alpha_{g}$ [-]"
)
pretty_labels("Y [m]", "", fontsize=20, grid=False, fontname="Times")
pretty_legend(fontname="Times")
plt.show()
This will show the following plot
Compute reactor properties
The python interface is also useful to compute reactor averaged properties. We usually like to compute volume averaged properties, which requires access to the cell volume. A cell volume field V
can be written using OpenFOAM utilities (postProcess -func writeCellVolumes -time {time_folder} -case {case_folder}
). Running this command would again require activating the OpenFOAM environment and we already provide a volume field in the 1/
folder here.
A typical example is that one would want to compute at time 80
gas hold up (
gh
)superficial velocity (
sup_vel
)reactor volume averaged mass fraction of CO2 in the liquid phase (
y_ave_co2
)reactor volume averaged concentration of CO2 in the liquid phase (
c_ave_co2
)Reactor averaged bubble diameter (
diam
)
Several of these quantities, will require reading and processing the same fields. For example, both y_ave_co2
and c_ave_co2
require to read CO2.liquid
. To avoid re-reading the same fields, we store the fields in field_dict
that allows to reuse fields when possible.
from bird.postprocess.post_quantities import *
# Compute Gas hold up
kwargs = {"case_folder": ".", "time_folder": "80"}
gh, field_dict = compute_gas_holdup(
volume_time="1", field_dict={"cell_centers": cell_centers}, **kwargs
)
print("fields stored = ", list(field_dict.keys()))
print(f"Gas Holdup = {gh:.4g}")
# Compute superficial velocity
sup_vel, field_dict = compute_superficial_velocity(
volume_time="1", field_dict=field_dict, **kwargs
)
print("fields stored = ", list(field_dict.keys()))
print(f"Superficial velocity = {sup_vel:.4g} m/s")
# Compute reactor-averaged CO2 mass fraction
y_ave_co2, field_dict = compute_ave_y_liq(
volume_time="1", spec_name="CO2", field_dict=field_dict, **kwargs
)
print("fields stored = ", list(field_dict.keys()))
print(f"Reactor averaged YCO2 = {y_ave_co2:.4g}")
# Compute reactor-averaged CO2 concentration
c_ave_co2, field_dict = compute_ave_conc_liq(
volume_time="1",
spec_name="CO2",
mol_weight=0.04401,
rho_val=1000,
field_dict=field_dict,
**kwargs,
)
print("fields stored = ", list(field_dict.keys()))
print(f"Reactor averaged [CO2] = {c_ave_co2:.4g} mol/m3")
# Compute reactor-averaged bubble diameter
diam, field_dict = compute_ave_bubble_diam(
volume_time="1", field_dict=field_dict, **kwargs
)
print("fields stored = ", list(field_dict.keys()))
print(f"Reactor averaged bubble diameter = {diam:.4g} m")
This should generate the following
fields stored = ['cell_centers', 'alpha.liquid', 'V']
Gas Holdup = 0.3041
fields stored = ['cell_centers', 'alpha.liquid', 'V', 'alpha.gas', 'U.gas']
Superficial velocity = 0.08241 m/s
fields stored = ['cell_centers', 'alpha.liquid', 'V', 'alpha.gas', 'U.gas', 'CO2.liquid', 'ind_liq']
Reactor averaged YCO2 = 0.0002948
fields stored = ['cell_centers', 'alpha.liquid', 'V', 'alpha.gas', 'U.gas', 'CO2.liquid', 'ind_liq', 'rho_liq']
Reactor averaged [CO2] = 6.698 mol/m3
fields stored = ['cell_centers', 'alpha.liquid', 'V', 'alpha.gas', 'U.gas', 'CO2.liquid', 'ind_liq', 'rho_liq', 'd.gas']
Reactor averaged bubble diameter = 0.008497 m
The fields stored
print shows what fields are read to compute each quantity. Between the calculation of y_ave_co2
and c_ave_co2
, only rho_liq
was added to the list of fields read. In other terms, the parser recycled CO2.liquid
instead of re-reading it. Obviously, this approach trades input/output operations for memory use and it is up to the user to decide what is the right approach. In this case, reusing the fields reduces the computational cost by about 50% (0.26s when not reusing the fields, 0.17s when reusing the fields on an M1 Mac).