# Geophysics: compute the permeability of a 3D Porous Medium¶

Main author: Wim Degruyter

This tutorial illustrates, step-by-step, how to compute the permeability of a given porous media. This process consists of three major steps:

1. Read the geometry, defined by a stack of binary (black and white) images. For this tutorial, we use an artificial geometry, consisting of two hemispheres which partially overlap. This represents a simple media with just a single tube trhough which the fluid flows from the inlet to the outlet. It is however esaily replaced by any complex media, by substituting the bitmap images with user-supplied data. A typical example would be a porous media, with a structure obtained from experimental data.
2. Simulate a stationary (time-independent), pressure-driven flow through this media, by imposing a constant pressure at the inlet, and a constant, lower pressure at the outlet. All physical quantities (velocity, pressure, and viscosity) are, for convenience, expressed in a system of lattice units. The final quantity of interest, the permability, has dimensions of length squared. Therefore, the actual permeability is the lattice permeability times the spatial resolution squared. If you would like to convert other physical quantities into different units, you should read the tutorial on unit conversion.
3. Compute the permeability which, according to Darcy’s law, is proportional to the ratio between the flow rate through the media and the applied pressure gradient. To be able to apply Darcy’s law one has to make sure the flow is laminar. Therefore it is recommended to run each simulation several times at differnt pressure differnces. The permeability should stay a constant.

The code developed in this tutorial can be found in the directory palabos/examples/tutorial/permeability. This Palabos code has been developed as part of a permeability study of vesicular volcanic rocks [1].

 [1] Degruyter W., Bachmann O., Burgisser A., Malaspinas O. A synchrotron perspective on gas flow through pumices. submitted to Geosphere

## Pre-processing¶

### Requirements¶

We assume you already have downloaded and installed Palabos. Installation support is provided in the user’s guide. We also recommend to go through the other Palabos tutorials to get acquainted with compiling and running of Palabos programs.

The input required for permeability are a series of black and white images in .bmp format, with filename namexxxx.bmp with xxxx numbers starting from 0001. In the directory twoSpheres one can find a series of bw images defining two connected hemispheres. To inspect the stack of images one can use e.g. ImageJ. Here are two example slices, inlet (left) and halfway (right):

### Convert image stack to .dat file¶

The first step is to create an input file from the images, so the code is able to read in the geometry. This conversion is done by a Matlab script called createDAT.m. Open Matlab and go to the directory of createDAT.m. At the prompt type createDAT(number-of-files, path/to/inputprefix, path/to/output.dat) e.g. try for the twoSpheres files and type createDAT(48, 'twoSpheres/', 'twoSpheres', 'twoSpheres.dat'). Every voxel is given a value: 0 for a fluid voxel (blue), 1 for a material voxel that touches (26-connected) a pore voxel (green), and 2 for an interior material voxel (red) illustrated again by the inlet slice (left) and the halfway slice (right):

The Matlab script visualizes the conversion fo illustration purposes. If you are converting a large number of files this process can take some time and it is better to switch of the display by commenting out lines 113-115, 258-260, and 372-374.

Once the .dat file is created we can start a simulation.

## Simulation¶

### Brief outline of the code¶

The permeability.cpp code is listed below:

 ``` 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201``` ```#include "palabos3D.h" #include "palabos3D.hh" #include #include using namespace plb; using namespace std; typedef double T; #define DESCRIPTOR descriptors::D3Q19Descriptor // This function object returns a zero velocity, and a pressure which decreases // linearly in x-direction. It is used to initialize the particle populations. class PressureGradient { public: PressureGradient(T deltaP_, plint nx_) : deltaP(deltaP_), nx(nx_) { } void operator() (plint iX, plint iY, plint iZ, T& density, Array& velocity) const { velocity.resetToZero(); density = 1. - deltaP*DESCRIPTOR::invCs2 / (T)(nx-1) * (T)iX; } private: T deltaP; plint nx; }; void porousMediaSetup( MultiBlockLattice3D& lattice, OnLatticeBoundaryCondition3D* boundaryCondition, MultiScalarField3D& geometry, T deltaP) { const plint nx = lattice.getNx(); const plint ny = lattice.getNy(); const plint nz = lattice.getNz(); pcout << "Definition of inlet/outlet." << endl; Box3D inlet (0,0, 1,ny-2, 1,nz-2); boundaryCondition->addPressureBoundary0N(inlet, lattice); setBoundaryDensity(lattice, inlet, 1.); Box3D outlet(nx-1,nx-1, 1,ny-2, 1,nz-2); boundaryCondition->addPressureBoundary0P(outlet, lattice); setBoundaryDensity(lattice, outlet, 1. - deltaP*DESCRIPTOR::invCs2); pcout << "Definition of the geometry." << endl; // Where "geometry" evaluates to 1, use bounce-back. defineDynamics(lattice, geometry, new BounceBack(), 1); // Where "geometry" evaluates to 2, use no-dynamics (which does nothing). defineDynamics(lattice, geometry, new NoDynamics(), 2); pcout << "Initilization of rho and u." << endl; initializeAtEquilibrium( lattice, lattice.getBoundingBox(), PressureGradient(deltaP, nx) ); lattice.initialize(); delete boundaryCondition; } void writeGifs(MultiBlockLattice3D& lattice, plint iter) { const plint nx = lattice.getNx(); const plint ny = lattice.getNy(); const plint nz = lattice.getNz(); const plint imSize = 600; ImageWriter imageWriter("leeloo"); // Write velocity-norm at x=0. imageWriter.writeScaledGif( createFileName("ux_inlet", iter, 6), *computeVelocityNorm(lattice, Box3D(0,0, 0,ny-1, 0,nz-1)), imSize, imSize ); // Write velocity-norm at x=nx/2. imageWriter.writeScaledGif( createFileName("ux_half", iter, 6), *computeVelocityNorm(lattice, Box3D(nx/2,nx/2, 0,ny-1, 0,nz-1)), imSize, imSize ); } void writeVTK(MultiBlockLattice3D& lattice, plint iter) { VtkImageOutput3D vtkOut(createFileName("vtk", iter, 6), 1.); vtkOut.writeData(*computeVelocityNorm(lattice), "velocityNorm", 1.); vtkOut.writeData<3,float>(*computeVelocity(lattice), "velocity", 1.); } T computePermeability ( MultiBlockLattice3D& lattice, T nu, T deltaP, Box3D domain ) { pcout << "Computing the permeability." << endl; // Compute only the x-direction of the velocity (direction of the flow). plint xComponent = 0; plint nx = lattice.getNx(); T meanU = computeAverage ( *computeVelocityComponent (lattice, domain, xComponent ) ); pcout << "Average velocity = " << meanU << endl; pcout << "Lattice viscosity nu = " << nu << endl; pcout << "Grad P = " << deltaP/(T)(nx-1) << endl; pcout << "Permeability = " << nu*meanU / (deltaP/(T)(nx-1)) << endl; return meanU; } int main(int argc, char **argv) { plbInit(&argc, &argv); if (argc!=7) { pcout << "Error missing some input parameter\n"; pcout << "The structure is :\n"; pcout << "1. Input file name.\n"; pcout << "2. Output directory name.\n"; pcout << "3. number of cells in X direction.\n"; pcout << "4. number of cells in Y direction.\n"; pcout << "5. number of cells in Z direction.\n"; pcout << "6. Delta P .\n"; exit (EXIT_FAILURE); } std::string fNameIn = argv[1]; std::string fNameOut = argv[2]; const plint nx = atoi(argv[3]); const plint ny = atoi(argv[4]); const plint nz = atoi(argv[5]); const T deltaP = atof(argv[6]); global::directories().setOutputDir(fNameOut+"/"); const T omega = 1.0; const T nu = ((T)1/omega-0.5)/DESCRIPTOR::invCs2; pcout << "Creation of the lattice." << endl; MultiBlockLattice3D lattice(nx,ny,nz, new BGKdynamics(omega)); // Switch off periodicity. lattice.periodicity().toggleAll(false); pcout << "Reading the geometry file." << endl; MultiScalarField3D geometry(nx,ny,nz); plb_ifstream geometryFile(fNameIn.c_str()); if (!geometryFile.is_open()) { pcout << "Error: could not open geometry file " << fNameIn << endl; return -1; } geometryFile >> geometry; pcout << "nu = " << nu << endl; pcout << "deltaP = " << deltaP << endl; pcout << "omega = " << omega << endl; pcout << "nx = " << lattice.getNx() << endl; pcout << "ny = " << lattice.getNy() << endl; pcout << "nz = " << lattice.getNz() << endl; porousMediaSetup(lattice, createLocalBoundaryCondition3D(), geometry, deltaP); // The value-tracer is used to stop the simulation once it has converged. // 1st parameter:velocity // 2nd parameter:size // 3rd parameters:threshold // 1st and second parameters ae used for the length of the time average (size/velocity) util::ValueTracer converge(1.0,1000.0,1.0e-4); pcout << "Simulation begins" << endl; plint iT=0; const plint maxT = 30000; for (;iT0) { writeGifs(lattice,iT); } lattice.collideAndStream(); converge.takeValue(getStoredAverageEnergy(lattice),true); if (converge.hasConverged()) { break; } } pcout << "End of simulation at iteration " << iT << endl; pcout << "Permeability:" << endl << endl; computePermeability(lattice, nu, deltaP, lattice.getBoundingBox()); pcout << endl; pcout << "Writing VTK file ..." << endl << endl; writeVTK(lattice,iT); pcout << "Finished!" << endl << endl; } ```
Line 1-12
General definitions and includes are discussed in the other tutorials of Palabos. Here we use the D3Q19 lattice, but other lattices (e.g. D3Q15 or D3Q27) are also possible.
Line 16-29
This functional defines the initial conditions. It is used in function porousMediaSetup: the fluid has initially zero velocity, with a linear pressure gradient in the x-direction.
Line 31-59
Here, the boundary conditions are set, i.e. all voxels read in as 1 are defined to have bounce back boundary conditions, and all voxels read in with value two are set to carry no dynamics. The inlet and outlet are defined to have a fixed pressure difference.
Line 61-109
Various outputs are created. During the simulation .gif files showing the velocity distribution within a slice are made (line 61-80). A .vti file with the velocity distribution is written, which can be read by Paraview (line 83-88). The permeability is computed by applying Darcy’s law to the simulated velocity data (line 90-109).
Line 111-201
Main part of the code. The code requires 6 input values from the user: the input.dat file, the output directory, the size of the file in each orthogonal drection, and finally the pressure difference between the in- and outlet. From these input values, the whole run is instantiated and the simulation starts (line 174). The ValueTracer (line 168) is used to stop the simulation when steady state is reached. A maximum of number of iterations is defined (line 173). Every 500 steps a .gif file is written to the output directory (line 179). Once converged the permeability of the medium is written to the screen and a .vti file is created.

### Running a simulation¶

First the code needs to be compiled. Check if the Makefile is in order. Open a terminal and type make, once in the permeability directory. To run a simulation type ./permeability path/to/input.dat path/to/outputdir/ nx ny nz deltaP in a terminal in the permeability directory. Let’s test it on our example by typing ./permeability twoSpheres.dat tmp/ 48 64 64 0.00005. The progress of the simulation is written to the screen. One can find the .gif and .vti files in the tmp subdirectory.

## Post-processing¶

Use our favorite image program to visualize the .gif files. Here we see the velocity distribution at the inlet (left) and halfway (right) after 4500 iterations:

Paraview allows you to visualize the .vti file in 3D. Here are some examples:

### Table Of Contents

#### Previous topic

Tutorial 2: Understanding the multi-block structure