Simulate inhomogeneous diffusion in a CaCO\f$_3\f$ particle packed bed
In this example, we simulate inhomogeneous diffusion in the fluid phase of a CaCO \(_3\) particle packed bed. For the complete image-based simulation pipeline and performance of this simulation, we refer to J. Stark, I. F. Sbalzarini "An open-source pipeline for solving continuous reaction-diffusion models in image-based geometries of porous media", Journal of Computational Science (2023).
The geometry of the fluid phase is reconstructed based on $\upmu$CT images provided by kindly provided by Prof. Jörg Petrasch (Michigan State University, College of Engineering) (see: S. Haussener et al., “Tomographic characterization of a semitransparent-particle packed bed and determination of its thermal radiative properties”. In: Journal of Heat Transfer 131.7 (2009)).
For image based reconstruction and redistancing, see Images 3D.
Include
We start with inluding header files, setting some paths and indices:
#include <iostream>
#include <typeinfo>
#include "CSVReader/CSVReader.hpp"
#include "RemoveLines.hpp"
#include "level_set/redistancing_Sussman/AnalyticalSDF.hpp"
#include "FiniteDifference/FD_simple.hpp"
#include "Decomposition/Distribution/BoxDistribution.hpp"
#include "timer.hpp"
#include "include/DiffusionSpace_sparseGrid.hpp"
#include "include/HelpFunctions_diffusion.hpp"
const std::string path_to_redistancing_result =
"/INPUT_PATH/benchmarks/CaCO3/sussman_redistancing/build/output_sussman_maxIter6e3_CaCO3_fluidPhase_531x531x531/";
const std::string redistancing_filename = "grid_CaCO3_post_redistancing.hdf5";
const std::string path_to_size = path_to_redistancing_result;
const std::string output_name = "output_inhomogDiffusion_CaCO3_fluid_phase";
const size_t PHI_FULL = 0;
const size_t PHI_PHASE = 0;
const size_t CONC_N = 1;
const size_t CONC_NPLUS1 = 2;
const size_t DIFFUSION_COEFFICIENT = 3;
"CONC_N",
"CONC_NPLUS1",
"DIFFUSION_COEFFICIENT"};
constexpr size_t x = 0, y = 1, z = 2;
const size_t dims = 3;
Header file containing help-functions that perform on OpenFPM-grids.
Header file containing functions for loading pixel values from 2D image or 3D image stack (volume) st...
Header file containing functions for creating files and folders.
Class for reinitializing a level-set function into a signed distance function using Sussman redistanc...
Implementation of 1-D std::vector like structure.
aggregate of properties, from a list of object if create a struct that follow the OPENFPM native stru...
Initialization and output folder
We start with
- Initializing OpenFPM
- Setting the output path and creating an output folder
int main(int argc, char* argv[])
{
openfpm_init(&argc, &argv);
auto & v_cl = create_vcluster();
timer t_iterative_diffusion_total;
const std::string path_output = cwd + "/" + output_name + "/";
if(v_cl.rank()==0) std::cout << "Redistancing result will be loaded from " << path_to_redistancing_result << redistancing_filename << std::endl;
if(v_cl.rank()==0) std::cout << "Outputs will be saved to " << path_output << std::endl;
static void create_directory_if_not_exist(std::string path, bool silent=0)
Creates a directory if not already existent.
static std::string get_cwd()
Gets the current working directory and returns path as string.
Class for cpu time benchmarking.
void start()
Start the timer.
Create a dense grid and load redistancing result
size_t m, n;
read_csv_to_vector(path_to_size + "/N.csv", v_sz, m, n);
read_csv_to_vector(path_to_size + "/L.csv", v_L, m, n);
const float Lx_low = 0.0;
const float Lx_up = v_L.get(x);
const float Ly_low = 0.0;
const float Ly_up = v_L.get(y);
const float Lz_low = 0.0;
const float Lz_up = v_L.get(z);
size_t sz[dims] = {v_sz.get(x), v_sz.get(y), v_sz.get(z)};
grid_in_type g_dist(sz, box, ghost);
g_dist.load(path_to_redistancing_result + "/" + redistancing_filename);
This class represent an N-dimensional box.
This class decompose a space into sub-sub-domains and distribute them across processors.
This is a distributed grid.
Create a sparse grid of fluid phase
Obtain sparse grid by placing grid points inside the fluid phase only using the signed distance function
std::cout << "Rank " << v_cl.rank() << " starts creating sparse grid." << std::endl;
typedef sgrid_dist_id_gpu<dims, float, props_sparse, CudaMemory, Dec> sparse_grid_type;
sparse_grid_type g_sparse(sz, box, ghost);
g_sparse.setPropNames(prop_names_sparse);
const float d_low = 0.0, d_up = Lx_up;
get_diffusion_domain_sparse_grid<PHI_FULL, PHI_PHASE>(g_dist, g_sparse, d_low, d_up);
std::cout << "Rank " << v_cl.rank() << " finished creating sparse grid." << std::endl;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Defining the inhomogeneous diffusion coefficient
The inhomogeneous diffusion coefficient is smoothed around the interface by a sigmoidal function that depends on the signed distance function
const float min_porosity = 0.0;
const float max_porosity = 1.0;
float D_molecular = 1.0;
float D_min = D_molecular * min_porosity;
float D_max = D_molecular * max_porosity;
float min_sdf_calcite = get_min_val<PHI_PHASE>(g_sparse);
float x_stretch = 4.0 * g_sparse.spacing(x);
float x_shift = min_sdf_calcite * x_stretch;
float y_min = D_min;
float y_max = D_max - D_min;
const float c0 = 10.0;
auto dom = g_sparse.getDomainIterator();
while(dom.isNext())
{
auto key = dom.get();
if(coords.
get(x) <= 0.05 * Lx_up)
{
g_sparse.template insertFlush<CONC_N>(key) = c0;
}
else g_sparse.template insertFlush<CONC_N>(key) = 0.0;
g_sparse.template insertFlush<DIFFUSION_COEFFICIENT>(key) =
get_smooth_sigmoidal(
g_sparse.template getProp<PHI_PHASE>(key) - min_sdf_calcite,
x_shift,
x_stretch,
y_min,
y_max);
++dom;
}
This class implement the point shape in an N-dimensional space.
__device__ __host__ const T & get(unsigned int i) const
Get coordinate.
Defining the functor for the inhomogeneous diffusion
The functor contains the forward time central space discretization of the inhomogeneous diffusion equation. The stencil size is 1. It will be passed to the GPU and convolved with the sparse grid.
typedef typename GetCpBlockType<
decltype(g_sparse),0,1>::type CpBlockType;
const float dx = g_sparse.spacing(x), dy = g_sparse.spacing(y), dz = g_sparse.spacing(z);
const float dt = diffusion_time_step(g_sparse, D_max);
std::cout << "dx = " << dx << "dy = " << dy << "dz = " << dz << std::endl;
std::cout << "dt = " << dt << std::endl;
auto func_inhomogDiffusion = [dx, dy, dz, dt, d_low] __device__ (
float & u_out,
float & D_out,
float & phi_out,
CpBlockType & u,
CpBlockType & phi,
auto & block,
int offset,
int i,
int j,
int k
)
{
float u_c = u(i, j, k);
float u_px = u(i+1, j, k);
float u_mx = u(i-1, j, k);
float u_py = u(i, j+1, k);
float u_my = u(i, j-1, k);
float u_pz = u(i, j, k+1);
float u_mz = u(i, j, k-1);
float phi_c = phi(i, j, k);
float phi_px = phi(i+1, j, k);
float phi_mx = phi(i-1, j, k);
float phi_py = phi(i, j+1, k);
float phi_my = phi(i, j-1, k);
float phi_pz = phi(i, j, k+1);
float phi_mz = phi(i, j, k-1);
float D_px =
D(i+1, j, k);
float D_mx =
D(i-1, j, k);
float D_py =
D(i, j+1, k);
float D_my =
D(i, j-1, k);
float D_pz =
D(i, j, k+1);
float D_mz =
D(i, j, k-1);
if (phi_px <= d_low + std::numeric_limits<float>::epsilon()) {u_px = u_c; D_px = D_c;}
if (phi_mx <= d_low + std::numeric_limits<float>::epsilon()) {u_mx = u_c; D_mx = D_c;}
if (phi_py <= d_low + std::numeric_limits<float>::epsilon()) {u_py = u_c; D_py = D_c;}
if (phi_my <= d_low + std::numeric_limits<float>::epsilon()) {u_my = u_c; D_my = D_c;}
if (phi_pz <= d_low + std::numeric_limits<float>::epsilon()) {u_pz = u_c; D_pz = D_c;}
if (phi_mz <= d_low + std::numeric_limits<float>::epsilon()) {u_mz = u_c; D_mz = D_c;}
float D_half_px = (D_c + D_px)/2.0;
float D_half_mx = (D_c + D_mx)/2.0;
float D_half_py = (D_c + D_py)/2.0;
float D_half_my = (D_c + D_my)/2.0;
float D_half_pz = (D_c + D_pz)/2.0;
float D_half_mz = (D_c + D_mz)/2.0;
u_out = u_c + dt *
(1/(dx*dx) * (D_half_px * (u_px - u_c) - D_half_mx * (u_c - u_mx)) +
1/(dy*dy) * (D_half_py * (u_py - u_c) - D_half_my * (u_c - u_my)) +
1/(dz*dz) * (D_half_pz * (u_pz - u_c) - D_half_mz * (u_c - u_mz)));
D_out = D_c;
phi_out = phi_c;
};
Derivative second order on h (spacing)
get the type of the block
Run inhomogeneous diffusion
std::string path_time_filewct = path_output + "/time_wct_incl_ghostCommunication" + std::to_string(v_cl.rank()) + ".txt";
std::ofstream out_wct_file(path_time_filewct, std::ios_base::app);
std::string path_time_filegpu = path_output + "/time_GPU" + std::to_string(v_cl.rank()) + ".txt";
std::ofstream out_GPUtime_file(path_time_filegpu, std::ios_base::app);
const size_t iterations = 103;
const size_t number_write_outs = 10;
const size_t interval_write = iterations / number_write_outs;
size_t iter = 0;
float t = 0;
g_sparse.template hostToDevice<CONC_N, CONC_NPLUS1, DIFFUSION_COEFFICIENT, PHI_PHASE>();
g_sparse.template ghost_get<DIFFUSION_COEFFICIENT, PHI_PHASE>(RUN_ON_DEVICE | SKIP_LABELLING);
t_iterative_diffusion_total.
start();
while(iter <= iterations)
{
if (iter % 2 == 0)
{
g_sparse.template ghost_get<CONC_N>(RUN_ON_DEVICE | SKIP_LABELLING);
t_GPU.startGPU();
g_sparse.template conv3_b<
CONC_N,
DIFFUSION_COEFFICIENT,
PHI_PHASE,
CONC_NPLUS1,
DIFFUSION_COEFFICIENT,
PHI_PHASE, 1>
({0, 0, 0},
{(long int) sz[x]-1, (long int) sz[y]-1, (long int) sz[z]-1},
func_inhomogDiffusion);
t_GPU.stopGPU();
out_wct_file << t_iteration_wct.
getwct() <<
",";
out_GPUtime_file << t_GPU.getwctGPU() << ",";
}
else
{
g_sparse.template ghost_get<CONC_NPLUS1>(RUN_ON_DEVICE | SKIP_LABELLING);
t_GPU.startGPU();
g_sparse.template conv3_b<
CONC_NPLUS1,
DIFFUSION_COEFFICIENT,
PHI_PHASE,
CONC_N,
DIFFUSION_COEFFICIENT,
PHI_PHASE, 1>
({0, 0, 0},
{(
long int) sz[x]-1, (
long int) sz[y]-1, (
long int) sz[z]-1},
func_inhomogDiffusion);
t_GPU.stopGPU();
out_wct_file << t_iteration_wct.
getwct() <<
",";
out_GPUtime_file << t_GPU.getwctGPU() << ",";
}
if (iter % interval_write == 0)
{
g_sparse.template deviceToHost<CONC_N, CONC_NPLUS1, DIFFUSION_COEFFICIENT, PHI_PHASE>();
g_sparse.write_frame(path_output + "g_sparse_diffuse", iter, FORMAT_BINARY);
g_sparse.save(path_output + "g_sparse_diffuse_" + std::to_string(iter) + ".hdf5");
if (iter % 2 == 0)
{
monitor_total_concentration<CONC_N>(g_sparse, t, iter, path_output,"total_conc.csv");
}
else
{
monitor_total_concentration<CONC_NPLUS1>(g_sparse, t, iter, path_output,"total_conc.csv");
}
}
t += dt;
iter += 1;
}
t_iterative_diffusion_total.
stop();
out_GPUtime_file.close();
std::cout <<
"Rank " << v_cl.rank() <<
" total time for the whole programm : " << t_total.
getwct() << std::endl;
void stop()
Stop the timer.
double getwct()
Return the elapsed real time.
KeyT const ValueT ValueT OffsetIteratorT OffsetIteratorT int
[in] The number of segments that comprise the sorting data
Terminate
We end with terminating OpenFPM.
openfpm_finalize();
return 0;
}
Full code
#include <iostream>
#include <typeinfo>
#include "CSVReader/CSVReader.hpp"
#include "RemoveLines.hpp"
#include "level_set/redistancing_Sussman/AnalyticalSDF.hpp"
#include "FiniteDifference/FD_simple.hpp"
#include "Decomposition/Distribution/BoxDistribution.hpp"
#include "timer.hpp"
#include "include/DiffusionSpace_sparseGrid.hpp"
#include "include/HelpFunctions_diffusion.hpp"
const std::string path_to_redistancing_result =
"/INPUT_PATH/benchmarks/CaCO3/sussman_redistancing/build/output_sussman_maxIter6e3_CaCO3_fluidPhase_531x531x531/";
const std::string redistancing_filename = "grid_CaCO3_post_redistancing.hdf5";
const std::string path_to_size = path_to_redistancing_result;
const std::string output_name = "output_inhomogDiffusion_CaCO3_fluid_phase";
const size_t PHI_FULL = 0;
const size_t PHI_PHASE = 0;
const size_t CONC_N = 1;
const size_t CONC_NPLUS1 = 2;
const size_t DIFFUSION_COEFFICIENT = 3;
"CONC_N",
"CONC_NPLUS1",
"DIFFUSION_COEFFICIENT"};
constexpr size_t x = 0, y = 1, z = 2;
const size_t dims = 3;
int main(int argc, char* argv[])
{
openfpm_init(&argc, &argv);
auto & v_cl = create_vcluster();
timer t_iterative_diffusion_total;
const std::string path_output = cwd + "/" + output_name + "/";
if(v_cl.rank()==0) std::cout << "Redistancing result will be loaded from " << path_to_redistancing_result << redistancing_filename << std::endl;
if(v_cl.rank()==0) std::cout << "Outputs will be saved to " << path_output << std::endl;
size_t m, n;
read_csv_to_vector(path_to_size + "/N.csv", v_sz, m, n);
read_csv_to_vector(path_to_size + "/L.csv", v_L, m, n);
const float Lx_low = 0.0;
const float Lx_up = v_L.get(x);
const float Ly_low = 0.0;
const float Ly_up = v_L.get(y);
const float Lz_low = 0.0;
const float Lz_up = v_L.get(z);
size_t sz[dims] = {v_sz.get(x), v_sz.get(y), v_sz.get(z)};
grid_in_type g_dist(sz, box, ghost);
g_dist.load(path_to_redistancing_result + "/" + redistancing_filename);
std::cout << "Rank " << v_cl.rank() << " starts creating sparse grid." << std::endl;
typedef sgrid_dist_id_gpu<dims, float, props_sparse, CudaMemory, Dec> sparse_grid_type;
sparse_grid_type g_sparse(sz, box, ghost);
g_sparse.setPropNames(prop_names_sparse);
const float d_low = 0.0, d_up = Lx_up;
get_diffusion_domain_sparse_grid<PHI_FULL, PHI_PHASE>(g_dist, g_sparse, d_low, d_up);
std::cout << "Rank " << v_cl.rank() << " finished creating sparse grid." << std::endl;
const float min_porosity = 0.0;
const float max_porosity = 1.0;
float D_molecular = 1.0;
float D_min = D_molecular * min_porosity;
float D_max = D_molecular * max_porosity;
float min_sdf_calcite = get_min_val<PHI_PHASE>(g_sparse);
float x_stretch = 4.0 * g_sparse.spacing(x);
float x_shift = min_sdf_calcite * x_stretch;
float y_min = D_min;
float y_max = D_max - D_min;
const float c0 = 10.0;
auto dom = g_sparse.getDomainIterator();
while(dom.isNext())
{
auto key = dom.get();
if(coords.
get(x) <= 0.05 * Lx_up)
{
g_sparse.template insertFlush<CONC_N>(key) = c0;
}
else g_sparse.template insertFlush<CONC_N>(key) = 0.0;
g_sparse.template insertFlush<DIFFUSION_COEFFICIENT>(key) =
get_smooth_sigmoidal(
g_sparse.template getProp<PHI_PHASE>(key) - min_sdf_calcite,
x_shift,
x_stretch,
y_min,
y_max);
++dom;
}
typedef typename GetCpBlockType<
decltype(g_sparse),0,1>::type CpBlockType;
const float dx = g_sparse.spacing(x), dy = g_sparse.spacing(y), dz = g_sparse.spacing(z);
const float dt = diffusion_time_step(g_sparse, D_max);
std::cout << "dx = " << dx << "dy = " << dy << "dz = " << dz << std::endl;
std::cout << "dt = " << dt << std::endl;
auto func_inhomogDiffusion = [dx, dy, dz, dt, d_low] __device__ (
float & u_out,
float & D_out,
float & phi_out,
CpBlockType & u,
CpBlockType & phi,
auto & block,
int offset,
int i,
int j,
int k
)
{
float u_c = u(i, j, k);
float u_px = u(i+1, j, k);
float u_mx = u(i-1, j, k);
float u_py = u(i, j+1, k);
float u_my = u(i, j-1, k);
float u_pz = u(i, j, k+1);
float u_mz = u(i, j, k-1);
float phi_c = phi(i, j, k);
float phi_px = phi(i+1, j, k);
float phi_mx = phi(i-1, j, k);
float phi_py = phi(i, j+1, k);
float phi_my = phi(i, j-1, k);
float phi_pz = phi(i, j, k+1);
float phi_mz = phi(i, j, k-1);
float D_px =
D(i+1, j, k);
float D_mx =
D(i-1, j, k);
float D_py =
D(i, j+1, k);
float D_my =
D(i, j-1, k);
float D_pz =
D(i, j, k+1);
float D_mz =
D(i, j, k-1);
if (phi_px <= d_low + std::numeric_limits<float>::epsilon()) {u_px = u_c; D_px = D_c;}
if (phi_mx <= d_low + std::numeric_limits<float>::epsilon()) {u_mx = u_c; D_mx = D_c;}
if (phi_py <= d_low + std::numeric_limits<float>::epsilon()) {u_py = u_c; D_py = D_c;}
if (phi_my <= d_low + std::numeric_limits<float>::epsilon()) {u_my = u_c; D_my = D_c;}
if (phi_pz <= d_low + std::numeric_limits<float>::epsilon()) {u_pz = u_c; D_pz = D_c;}
if (phi_mz <= d_low + std::numeric_limits<float>::epsilon()) {u_mz = u_c; D_mz = D_c;}
float D_half_px = (D_c + D_px)/2.0;
float D_half_mx = (D_c + D_mx)/2.0;
float D_half_py = (D_c + D_py)/2.0;
float D_half_my = (D_c + D_my)/2.0;
float D_half_pz = (D_c + D_pz)/2.0;
float D_half_mz = (D_c + D_mz)/2.0;
u_out = u_c + dt *
(1/(dx*dx) * (D_half_px * (u_px - u_c) - D_half_mx * (u_c - u_mx)) +
1/(dy*dy) * (D_half_py * (u_py - u_c) - D_half_my * (u_c - u_my)) +
1/(dz*dz) * (D_half_pz * (u_pz - u_c) - D_half_mz * (u_c - u_mz)));
D_out = D_c;
phi_out = phi_c;
};
std::string path_time_filewct = path_output + "/time_wct_incl_ghostCommunication" + std::to_string(v_cl.rank()) + ".txt";
std::ofstream out_wct_file(path_time_filewct, std::ios_base::app);
std::string path_time_filegpu = path_output + "/time_GPU" + std::to_string(v_cl.rank()) + ".txt";
std::ofstream out_GPUtime_file(path_time_filegpu, std::ios_base::app);
const size_t iterations = 103;
const size_t number_write_outs = 10;
const size_t interval_write = iterations / number_write_outs;
size_t iter = 0;
float t = 0;
g_sparse.template hostToDevice<CONC_N, CONC_NPLUS1, DIFFUSION_COEFFICIENT, PHI_PHASE>();
g_sparse.template ghost_get<DIFFUSION_COEFFICIENT, PHI_PHASE>(RUN_ON_DEVICE | SKIP_LABELLING);
t_iterative_diffusion_total.
start();
while(iter <= iterations)
{
if (iter % 2 == 0)
{
g_sparse.template ghost_get<CONC_N>(RUN_ON_DEVICE | SKIP_LABELLING);
t_GPU.startGPU();
g_sparse.template conv3_b<
CONC_N,
DIFFUSION_COEFFICIENT,
PHI_PHASE,
CONC_NPLUS1,
DIFFUSION_COEFFICIENT,
PHI_PHASE, 1>
({0, 0, 0},
{(
long int) sz[x]-1, (
long int) sz[y]-1, (
long int) sz[z]-1},
func_inhomogDiffusion);
t_GPU.stopGPU();
out_wct_file << t_iteration_wct.
getwct() <<
",";
out_GPUtime_file << t_GPU.getwctGPU() << ",";
}
else
{
g_sparse.template ghost_get<CONC_NPLUS1>(RUN_ON_DEVICE | SKIP_LABELLING);
t_GPU.startGPU();
g_sparse.template conv3_b<
CONC_NPLUS1,
DIFFUSION_COEFFICIENT,
PHI_PHASE,
CONC_N,
DIFFUSION_COEFFICIENT,
PHI_PHASE, 1>
({0, 0, 0},
{(
long int) sz[x]-1, (
long int) sz[y]-1, (
long int) sz[z]-1},
func_inhomogDiffusion);
t_GPU.stopGPU();
out_wct_file << t_iteration_wct.
getwct() <<
",";
out_GPUtime_file << t_GPU.getwctGPU() << ",";
}
if (iter % interval_write == 0)
{
g_sparse.template deviceToHost<CONC_N, CONC_NPLUS1, DIFFUSION_COEFFICIENT, PHI_PHASE>();
g_sparse.write_frame(path_output + "g_sparse_diffuse", iter, FORMAT_BINARY);
g_sparse.save(path_output + "g_sparse_diffuse_" + std::to_string(iter) + ".hdf5");
if (iter % 2 == 0)
{
monitor_total_concentration<CONC_N>(g_sparse, t, iter, path_output,"total_conc.csv");
}
else
{
monitor_total_concentration<CONC_NPLUS1>(g_sparse, t, iter, path_output,"total_conc.csv");
}
}
t += dt;
iter += 1;
}
t_iterative_diffusion_total.
stop();
out_GPUtime_file.close();
std::cout <<
"Rank " << v_cl.rank() <<
" total time for the whole programm : " << t_total.
getwct() << std::endl;
openfpm_finalize();
return 0;
}