############################################################################### Representative Elementary Volume (REV) Analysis (lbpm_rev_analysis) ############################################################################### The degree of heterogeneity observed in digital rocks is an important factor in characterizing porous media. Advances in the resolution and sample size achievable with X-ray microtomography have made it increasingly feasible to digitally investigate larger volumes that capture the intrinsic heterogeneities of porous structures. These developments enable the identification of representative elementary volumes (REVs), beyond which the influence of local heterogeneities becomes negligible and specific effective properties of the porous medium can be reliably characterized. The ``lbpm_rev_analysis`` routine provides an automated REV analysis for a range of geometrical and hydrodynamic properties, as summarized in :numref:`Table %s `. By combining deterministic and statistical approaches, the routine enables a comprehensive characterization of these properties, providing valuable insights for assessing the representativeness of microtomography images and estimating effective sample volumes for subsequent numerical analyses. .. _rev-properties: .. list-table:: Properties available for REV analysis. :widths: 25 25 25 :header-rows: 1 :align: center * - - Property 1 - Property 2 * - Geometrical - Porosity (:math:`\phi`) - Specific Surface Area (:math:`A_{s}`) * - Hydrodynamic - Absolute Permeability (:math:`K_{z}`) - Hydraulic Tortuosity (:math:`\tau_{h}`) **************************** Methodology **************************** The implemented REV routine combines deterministic and statistical approaches into a **hybrid methodology**, as illustrated in the workflow diagram presented in :numref:`rev-workflow`. The deterministic stage provides an initial estimate of the REV size at a relatively low computational cost. This estimate is subsequently used to initialize the statistical stage, which evaluates the spatial variability of the property over randomly distributed subvolumes. The deterministic analysis uses a default convergence threshold of :math:`\gamma = 0.1`. The statistical stage reports coefficients of variation and retains the first subvolume sizes at which the coefficients for porosity and permeability fall below this tolerance. For a detailed explanation of the steps illustrated in :numref:`rev-workflow`, expand the toggle sections below for the deterministic and statistical analyses. .. list-table:: :widths: 50 50 :class: borderless :align: center * - .. figure:: ../../../_static/images/rev-workflow.svg :width: 70% :align: center :name: rev-workflow Workflow of the hybrid REV analysis, combining deterministic (steps 1–5) and statistical (steps 6–9) convergence assessments. - .. figure:: ../../../_static/images/REVHybridAnimation.gif :width: 100% :align: center :name: rev-GIF GIF animation illustrating the hybrid REV analysis for porosity. ---------------------- Deterministic Analysis ---------------------- .. raw:: html
Click here to open: Deterministic Analysis The **deterministic approach** evaluates a property of interest within a subvolume centered in the computational domain. The analysis is repeated for progressively larger subvolumes until the measured property becomes approximately independent of the subvolume edge length. The smallest subvolume satisfying the prescribed convergence criteria is identified as the deterministic REV. The main advantage of this approach is its relatively low computational cost, as only one subvolume is evaluated for each edge length. However, the results may be sensitive to local fluctuations, including maxima, minima, and inflection points, potentially leading to false convergence. The deterministic procedure, illustrated in the upper part of the workflow diagram, consists of the following steps: #. **Initialize the subvolume size:** The initial edge length is defined as twice the average pore diameter, .. math:: \ell_0 = 2\overline{d}_p. #. **Evaluate the domain-size constraint:** Before each evaluation, the subvolume dimensions are checked against the available computational domain. If the prescribed maximum size is exceeded before convergence, no deterministic REV is identified. #. **Measure the property:** The property of interest, :math:`q_i`, is evaluated within the centered subvolume. #. **Compute the convergence criteria:** Two complementary metrics are calculated to quantify the variation of the measured property with increasing subvolume size. The first metric is the relative error (RE), defined as .. math:: :label: rev-relative-error RE_i = \left|\frac{q_i-q_{i-1}} {q_i+q_{i-1}} \frac{V_{i-1}}{V_i} \right| < \gamma, where :math:`RE_i` is the relative error at iteration :math:`i`, :math:`q_i` represents the measured property in the :math:`i`-th subvolume, and :math:`V_i` denotes the volume of that sampled subvolume. This metric quantifies the local variation of the property as the evaluated subvolume increases in size. The second metric is the convergence criterion (CC), introduced by :cite:t:`Marafini2020`: .. math:: :label: rev-convergence-criterion CC_i = \frac{\sigma(q_{i-m+1},\ldots,q_i)}{\overline{q}(q_{i-m+1},\ldots,q_i) } < \gamma, where :math:`\sigma` denotes the standard deviation, :math:`\overline{q}` represents the arithmetic mean, and :math:`m` is the number of consecutive measurements considered. By evaluating the dispersion of the most recent measurements, this metric reduces the likelihood of false convergence associated with local fluctuations. The default value is :math:`m=10`. #. **Assess convergence and update the subvolume size:** The first subvolume size for which both convergence criteria satisfy the prescribed tolerance is recorded as the deterministic REV for each property. Sizes continue to increase up to the maximum admissible domain size. The proportional growth is controlled by ``SizeStep``; for example, ``SizeStep = 0.06`` gives the nominal update .. math:: :label: rev-edge-growth \ell_{i+1}=1.06\ell_i. The implementation also applies a minimum increment based on the average pore diameter and rounds the edge length upward to whole voxels, so the actual increase can be larger. The default ``SizeStep`` is ``0.06`` (6%). .. raw:: html
.. raw:: html
-------------------- Statistical Analysis -------------------- .. raw:: html
Click here to open: Statistical Analysis The **statistical approach** evaluates the spatial variability of a property by sampling multiple subvolumes distributed throughout the computational domain. For a given edge length, the property is measured in a set of randomly selected subvolumes, and the dispersion of the resulting measurements is assessed. The current implementation repeats this sampling for increasing subvolume sizes and reports the dispersion at each size. These results can be compared with the prescribed tolerance to identify sufficiently consistent measurements. Compared with the deterministic approach, statistical analysis is computationally more expensive because multiple subvolumes must be evaluated at each edge length. Nevertheless, it provides a more robust assessment of spatial heterogeneity and reduces the risk of identifying false convergence caused by localized features. An important limitation arises as the subvolume size approaches the dimensions of the computational domain. In this situation, increasing overlap between sampled subvolumes can artificially reduce the measured dispersion, potentially leading to an overestimation of convergence. The statistical procedure, represented in the lower part of the workflow diagram, follows these steps: 7. **Initialize the statistical analysis:** The edge length corresponding to the deterministic REV is adopted as the initial subvolume size, avoiding the repeated evaluation of smaller volumes that have already been assessed. 8. **Verify the edge length:** If the edge length is larger than the maximum admissible edge length, the analysis stops and no REV is found. If it is smaller, the analysis continues. 9. **Select random subvolumes:** For the prescribed edge length, :math:`N` subvolumes are randomly selected within the computational domain. The number of sampled subvolumes is specified by ``StatSamples`` and defaults to 50 at each size. The current routine does not adapt this count based on the stability of the estimated CV. 10. **Measure the property for each subvolume:** The property of interest is evaluated for all subvolumes selected in the previous step. 11. **Compute the convergence criterion:** The dispersion of the measurements is quantified using the coefficient of variation (CV), following :cite:t:`Zhang2000`: .. math:: :label: rev-coefficient-variation CV = \frac{\sigma(q)}{\overline{q}}, where :math:`\sigma(q)` is the standard deviation of the property measurements and :math:`\overline{q}` is their arithmetic mean. Normalization by the mean provides a dimensionless measure of relative dispersion. 12. **Evaluate statistical convergence:** The computed coefficient of variation is compared with the prescribed tolerance :math:`\gamma`. Statistical convergence is achieved when .. math:: 0 \leq CV < \gamma. If the criterion is satisfied, the current edge length is identified as the statistical REV, and the procedure terminates. Otherwise, the analysis proceeds to the next step. 13. **Increase the edge length:** If statistical convergence has not been achieved, the edge length is increased according to ``SizeStep`` and the prescribed minimum increment. The procedure then returns to step 8 to verify whether the new size remains admissible. .. raw:: html
.. raw:: html
************************************ Run REV Analysis ************************************ REV analysis can be performed either alongside a single-phase permeability simulation using ``lbpm_rev_permeability_simulator`` or independently using ``lbpm_rev_analysis``. Currently, neither routine supports parallel execution across multiple MPI processes. Therefore, both routines must be executed using a single MPI process. To ensure compatibility, set ``nproc = 1, 1, 1`` in the ``Domain`` configuration block. --------------------------------------- lbpm_rev_permeability_simulator --------------------------------------- The ``lbpm_rev_permeability_simulator`` executable runs the single-phase flow simulation, computes the velocity field, and then performs deterministic and statistical REV analyses. A typical command for a single-process run is: .. code-block:: bash mpirun -np 1 lbpm_rev_permeability_simulator input.db The standard ``lbpm_rev_permeability_simulator`` executable does not invoke the REV routines. REV analysis is activated by the presence of a ``REV`` block in ``input.db``, alongside the usual ``Domain`` and ``MRT`` blocks. For example: .. code-block:: text REV { Geometrical = True Hydraulic = True SubdomainSelect = True subdomain_size = 64 save_subdomain_raw = false gamma = 0.1 SizeStep = 0.06 CCInterval = 10 StatSamples = 50 Seed_Anchor = True } To disable REV analysis in ``lbpm_rev_permeability_simulator``, the entire REV block must be omitted. If the block is present but no parameters are specified, the REV analysis will run using the default parameter values. -------------------------------- lbpm_rev_analysis -------------------------------- The routine ``lbpm_rev_analysis`` performs REV analysis from an input geometry. Geometrical analysis requires the geometry alone; hydraulic analysis also requires velocity fields saved by an earlier single-phase flow simulation. .. code-block:: bash mpirun -np 1 lbpm_rev_analysis input.db For geometrical analysis alone, the geometry is sufficient. ``Hydraulic`` defaults to ``False`` in this routine, so porosity and specific surface area can be evaluated without loading velocity fields. This default can also be set explicitly: .. code-block:: text REV { Geometrical = True Hydraulic = False } An ``MRT`` block is not required for this configuration. Set ``Hydraulic = True`` explicitly to enable hydraulic analysis when previously saved flow results are available. However, this analysis requires velocity fields obtained from a prior single-phase flow simulation. Currently, the routine supports only MRT simulations performed with ``BC = 0``, where the pressure gradient is imposed through an external body force (e.g., ``F = 0.0, 0.0, 1.0e-5``). For example, the following configuration block enables both property groups: .. code-block:: text REV { Geometrical = True Hydraulic = True Seed_Anchor = True SimulationParams = "/path/to/simulation/input.db" VelocityPath = "/path/to/simulation/results" VelocityTimestep = "vis3000" } When ``Hydraulic = True``, the three settings ``SimulationParams``, ``VelocityPath``, and ``VelocityTimestep`` must be explicitly supplied and nonempty. If any are missing or blank, ``lbpm_rev_analysis`` prints a message listing them and exits with a nonzero status before loading the saved files or geometry. These settings are not required for geometrical analysis alone. Note that, in addition to the velocity fields saved in the ``vis*`` folder, it is also necessary to provide the path to the ``input.db`` file used in the previous single-phase flow simulation. This file is required to retrieve the pressure drop and fluid viscosity used to calculate the absolute permeability. The supported file formats for the velocity fields stored in the ``vis*`` folder are ``silo`` and ``hdf5``. ------------------------------ Model parameters ------------------------------ For a detailed description of the REV analysis parameters, expand the section below using the toggle button. .. raw:: html
Click here to open: REV Parameters ``Geometrical`` and ``Hydraulic`` select the property groups evaluated in both analysis stages. Setting ``Geometrical = True`` enables porosity and specific surface area analysis, while ``Hydraulic = True`` enables permeability and hydraulic tortuosity analysis. Setting either parameter to ``False`` skips its corresponding group. ``Geometrical`` defaults to ``True`` in both routines. ``Hydraulic`` defaults to ``False`` in ``lbpm_rev_analysis`` and to ``True`` in ``lbpm_rev_permeability_simulator``. An explicit setting overrides the default. Setting both parameters to ``False`` skips both REV analyses. ``gamma`` is the dimensionless convergence tolerance, with a default value of ``0.1``. A deterministic REV is identified for a property when both its relative error (RE) and convergence criterion (CC) are valid and smaller than ``gamma``. The statistical analysis independently retains the first porosity and permeability sizes with a valid coefficient of variation (CV) below ``gamma``, together with their respective means and window dimensions. For example, ``gamma = 0.1`` requires both metrics to be below 10%. A smaller value imposes a stricter convergence requirement and may lead to a larger REV estimate or prevent convergence within the available domain. ``SizeStep`` controls the proportional increase in subvolume edge length in both analysis stages. The example value ``0.06`` specifies a 6% increase in edge length between successive subvolume sizes, corresponding to an approximate 19% increase in volume. The implementation also enforces a minimum increment equal to the larger of one voxel and the average pore diameter, and rounds the resulting edge length upward to an integer number of voxels. Consequently, the actual increase can exceed 6%, especially for small subvolumes. If omitted, ``SizeStep`` defaults to ``0.06``. It must be positive. ``CCInterval`` is the number of recent subvolume-size measurements used to calculate CC in the deterministic analysis. With ``CCInterval = 10``, CC measures the relative dispersion of up to the ten most recent measurements; the available measurements are used before the window is full. Increasing this value assesses convergence over a longer history of subvolume sizes. It must be a positive integer and defaults to ``10``. ``StatSamples`` is the number of randomly positioned subvolumes evaluated at each size during the statistical analysis. With ``StatSamples = 50``, each property is sampled in 50 subvolumes to calculate its mean, minimum, maximum, and coefficient of variation (CV). Increasing this value provides more spatial samples at a higher computational cost. It must be a positive integer and defaults to ``50``. ``Seed_Anchor`` controls the random number generator used to position subvolumes in the statistical analysis. It defaults to ``True`` in both routines, which initializes ``std::mt19937`` with the fixed seed ``0`` and makes sampling reproducible for the same input and configuration. Set ``Seed_Anchor = False`` to initialize the generator with a seed from ``std::random_device``, allowing the sampled locations to vary between runs. This parameter does not affect the deterministic analysis. ``SubdomainSelect`` defaults to ``False``. Set it to ``True`` to select representative subdomains after the statistical analysis, using its retained REV sizes and means. See :ref:`rev-subdomain-selection` for the numerical workflow, input restrictions, and outputs. ``subdomain_size`` optionally sets the selection window dimensions in voxels. A positive integer, such as ``64``, specifies a cube; three positive integers, such as ``64, 48, 32``, specify the exact x, y, and z dimensions. These dimensions apply to both geometric and hydraulic selection and must fit within the domain. If ``subdomain_size`` is omitted, each property uses its own statistical REV window dimensions. The statistical reference sizes and target means remain unchanged. ``save_subdomain_raw`` defaults to ``false``. Set it to ``true`` to save the selected geometric and hydraulic windows as raw crops. The selection CSV and domain databases are written independently of this setting. ``SimulationParams`` identifies the input database used for the flow simulation. The referenced database must contain an ``MRT`` block, from which the routine reads the relaxation time ``tau`` and body-force vector ``F``. A relative path is resolved against the directory containing the analysis input database. The referenced file supplies only these flow parameters; keep the geometry and ``REV`` settings in the analysis database. ``SimulationParams`` is required when hydraulic analysis is enabled in ``lbpm_rev_analysis``. ``VelocityPath`` is the directory containing the saved LBPM output. Relative paths are resolved from the current working directory. ``VelocityTimestep`` selects a timestep directory within this path and must be supplied as a quoted string. Both settings are required when hydraulic analysis is enabled in ``lbpm_rev_analysis``. Use ``VelocityPath = "."`` to select the current working directory explicitly. The selected timestep must contain ``LBM.summary`` and a ``domain`` mesh with the scalar volume fields ``Velocity_x``, ``Velocity_y``, and ``Velocity_z``. The saved output must contain a single MPI domain whose dimensions match the analysis geometry. Use the same geometry and voxel length as the original simulation. For an MRT simulation, enable ``write_silo = True`` in its ``Visualization`` block to save the required velocity fields in LBPM output. .. raw:: html
.. raw:: html
The property-selection, convergence, sampling, and subdomain-selection parameters are optional. The three saved-flow settings are required for standalone hydraulic analysis. An empty ``REV`` block activates geometrical analysis in ``lbpm_rev_analysis`` and both property groups in ``lbpm_rev_permeability_simulator``, with ``gamma = 0.1``, ``SizeStep = 0.1``, ``CCInterval = 10``, ``StatSamples = 50``, and ``Seed_Anchor = True``. Omitting the entire block disables REV analysis. .. _rev-subdomain-selection: **************************** Assessing Output Data **************************** As an example of output data analysis, we perform REV analyses for the Berea sample (also analyzed in :doc:`../../../examples/SinglePhasePoreScale/3D-DigitalRocks/3D-DigitalRocks-Cases`) using both ``lbpm_rev_permeability_simulator`` and ``lbpm_rev_analysis``. We set ``Seed_Anchor = True`` to ensure reproducibility of the results. The example includes both geometrical and hydraulic REV analyses: -------------------------------- lbpm_rev_permeability_simulator -------------------------------- Use the following configuration to run REV analysis with ``lbpm_rev_permeability_simulator``: .. code-block:: text MRT { tau = 0.9 F = 0.0, 0.0, 1.0e-4 timestepMax = 200000 tolerance = 0.00001 } Domain { Filename = "Berea.raw" ReadType = "8bit" // data type N = 400, 400, 400 // size of original image nproc = 1, 1, 1 // process grid n = 400, 400, 400 // sub-domain size offset = 0, 0, 0 // offset to read sub-domain InletLayers = 0, 0, 10 // specify 10 layers along the z-inlet OutletLayers = 0, 0, 10 // specify 10 layers along the z-outlet voxel_length = 5.345 // voxel length (in microns) ReadValues = 0, 1 // labels within the original image WriteValues = 1, 0 // associated labels to be used by LBPM BC = 0 // boundary condition type (0 for periodic) } REV { Geomtrical = True Hydraulic = True gamma = 0.1 SizeStep = 0.06 CCInterval = 10 StatSamples = 50 } Visualization { write_silo = true // write SILO databases with assigned variables save_velocity = true // save velocity field within SILO database } Results are written to the current working directory. Enabled geometrical analysis produces ``det_rev_geometric.csv`` and ``stat_rev_geometric.csv``; enabled hydraulic analysis produces ``det_rev_hydraulic.csv`` and ``stat_rev_hydraulic.csv``. The routine also writes ``pore_size_distribution.csv`` when an analysis group is enabled. For a detailed description of the output data printed in ``det_rev_geometric.csv``, ``stat_rev_geometric.csv``, ``det_rev_hydraulic.csv``, and ``stat_rev_hydraulic.csv``, expand the section below using the toggle button. .. raw:: html
Click here to open: .csv Output Data All four REV files are plain-text tables with a header row. Despite the ``.csv`` extension, fields are separated by spaces, so use a whitespace delimiter when importing them. Results are appended to existing files, and the header is written only when a file is empty. Separate runs therefore accumulate in the same table without a run or timestep identifier. The suffixes ``_vx`` and ``_um`` indicate lengths in voxels and micrometres, respectively. With :math:`h` equal to ``Domain.voxel_length`` in micrometres, ``x_size_um = x_size_vx * h``. These columns report the reference size in the :math:`x` direction; the other subvolume dimensions follow the domain aspect ratio, subject to integer rounding. .. rubric:: Deterministic geometrical output: ``det_rev_geometric.csv`` Each row describes one centered subvolume at a successive size. The columns appear in the following order: .. list-table:: :widths: auto :align: center * - ``iter_step`` - ``x_size_um`` - ``x_size_vx`` - ``porosity`` - ``rev_poro_um`` - ``rev_poro_vx`` - ``RE_poro`` - ``CC_poro`` - ``surface`` - ``rev_surf_um`` - ``rev_surf_vx`` - ``RE_surf`` - ``CC_surf`` .. list-table:: Columns in the deterministic geometrical output. :header-rows: 1 :widths: 35 65 :align: center * - Column(s) - Meaning * - ``iter_step`` - Subvolume-size iteration, starting at zero for each analysis run. * - ``x_size_um``, ``x_size_vx`` - Current reference subvolume size along the :math:`x` direction, expressed in micrometres and voxels. * - ``porosity`` - Pore-voxel count divided by the total voxel count in the subvolume; a dimensionless fraction, not a percentage. * - ``rev_poro_um``, ``rev_poro_vx`` - First subvolume size accepted as the deterministic porosity REV, in micrometres and voxels. * - ``RE_poro``, ``CC_poro`` - Relative change between successive porosity measurements and dispersion over the recent size history, as defined below. * - ``surface`` - Specific pore-solid interfacial area per total subvolume volume, in inverse micrometres. The implementation estimates it by counting voxel faces separating opposite signs of the distance field inside the subvolume; it is not the total interface area. * - ``rev_surf_um``, ``rev_surf_vx`` - First subvolume size accepted as the deterministic specific-surface REV, in micrometres and voxels. * - ``RE_surf``, ``CC_surf`` - Relative change and recent-history dispersion for specific surface area. .. rubric:: Deterministic hydraulic output: ``det_rev_hydraulic.csv`` This file uses the same centered subvolumes and iteration numbering as the deterministic geometrical file when both groups are enabled: .. list-table:: :widths: auto :align: center * - ``iter_step`` - ``x_size_um`` - ``x_size_vx`` - ``permeability`` - ``rev_perm_um`` - ``rev_perm_vx`` - ``RE_perm`` - ``CC_perm`` - ``tortuosity`` - ``rev_tort_um`` - ``rev_tort_vx`` - ``RE_tort`` - ``CC_tort`` .. list-table:: Columns in the deterministic hydraulic output. :header-rows: 1 :widths: 35 65 :align: center * - Column(s) - Meaning * - ``iter_step`` - Subvolume-size iteration, starting at zero. * - ``x_size_um``, ``x_size_vx`` - Current reference subvolume size along the :math:`x` direction, expressed in micrometres and voxels. * - ``permeability`` - Permeability estimated from the existing velocity field within the subvolume, in millidarcy (mD). The pore-averaged velocity is projected onto the applied body-force direction and weighted by porosity; a separate flow simulation is not run for each subvolume. * - ``rev_perm_um``, ``rev_perm_vx`` - First subvolume size accepted as the deterministic permeability REV, in micrometres and voxels. * - ``RE_perm``, ``CC_perm`` - Relative change and recent-history dispersion for permeability. * - ``tortuosity`` - Dimensionless hydraulic tortuosity, calculated as the sum of pore velocity magnitudes divided by the sum of pore :math:`z` velocities. * - ``rev_tort_um``, ``rev_tort_vx`` - First subvolume size accepted as the deterministic tortuosity REV, in micrometres and voxels. * - ``RE_tort``, ``CC_tort`` - Relative change and recent-history dispersion calculated from ``tortuosity - 1``, rather than the displayed tortuosity. To interpret the values written to the deterministic tables, let :math:`q_i` denote the property measured at size iteration :math:`i`. The implemented relative-change calculation is .. math:: RE_i = \left| \frac{q_i-q_{i-1}}{q_i+q_{i-1}} \frac{V_{i-1}}{V_i} \right|. Here, :math:`V_i` is the actual sampled volume, calculated from the inclusive voxel bounds after clipping to the domain. The implementation uses the ratio of sampled voxel counts because the common voxel volume cancels in :math:`V_{i-1}/V_i`. ``CC`` is the population standard deviation divided by the arithmetic mean of the most recent ``CCInterval`` measurements, using the available history before that window is full and excluding invalid values: .. math:: CC_i =\frac{\sigma(q_{i-m+1},\ldots,q_i)}{\overline{q}(q_{i-m+1},\ldots,q_i)}. For tortuosity, :math:`q_i = \tau_{h,i}-1` in both calculations. A property's ``rev_*`` columns are set when both ``RE`` and ``CC`` first satisfy :math:`0 \le RE < \gamma` and :math:`0 \le CC < \gamma`. The accepted size is retained in subsequent rows even if the coefficients later increase. The analysis continues through the full domain, so the last row's ``x_size_*`` need not equal the identified REV size. Before a property converges, ``rev_*_vx`` is ``-1`` and ``rev_*_um`` is ``-h`` because the voxel sentinel is multiplied by the voxel length. If these values persist to the last row, no deterministic REV was found for that property. Unavailable ``RE`` or ``CC`` values are written as ``-1``; both are unavailable on the first row. A zero normalization denominator (described by NaN) also leaves the corresponding coefficient at ``-1``. .. rubric:: Statistical geometrical output: ``stat_rev_geometric.csv`` Each row summarizes ``StatSamples`` randomly positioned subvolumes at one reference size. It contains aggregate statistics, not individual sample measurements or locations. The columns are: .. list-table:: :widths: auto :align: center * - ``x_size_um`` - ``x_size_vx`` - ``stat_poro`` - ``stat_poro_mean`` - ``stat_poro_max`` - ``stat_poro_min`` - ``stat_surf`` - ``stat_surf_mean`` - ``stat_surf_max`` - ``stat_surf_min`` .. list-table:: Columns in the statistical geometrical output. :header-rows: 1 :widths: 35 65 :align: center * - Column(s) - Meaning * - ``x_size_um``, ``x_size_vx`` - Reference subvolume size along the :math:`x` direction, shared by all sampled subvolumes and expressed in micrometres and voxels. * - ``stat_poro`` - Dimensionless coefficient of variation (CV) of sampled porosities. * - ``stat_poro_mean``, ``stat_poro_max``, ``stat_poro_min`` - Arithmetic mean, maximum, and minimum porosity across the samples, all expressed as dimensionless fractions. * - ``stat_surf`` - Dimensionless CV of sampled specific surface areas. * - ``stat_surf_mean``, ``stat_surf_max``, ``stat_surf_min`` - Arithmetic mean, maximum, and minimum specific surface area, in inverse micrometres, using the same voxel-face estimate as the deterministic analysis. .. rubric:: Statistical hydraulic output: ``stat_rev_hydraulic.csv`` This file summarizes the same random subvolumes as the statistical geometrical file when both groups are enabled: .. list-table:: :widths: auto :align: center * - ``x_size_um`` - ``x_size_vx`` - ``stat_perm`` - ``stat_perm_mean`` - ``stat_perm_max`` - ``stat_perm_min`` - ``stat_tort`` - ``stat_tort_mean`` - ``stat_tort_max`` - ``stat_tort_min`` .. list-table:: Columns in the statistical hydraulic output. :header-rows: 1 :widths: 35 65 :align: center * - Column(s) - Meaning * - ``x_size_um``, ``x_size_vx`` - Reference subvolume size along the :math:`x` direction, shared by all sampled subvolumes and expressed in micrometres and voxels. * - ``stat_perm`` - Dimensionless CV of sampled permeabilities. * - ``stat_perm_mean``, ``stat_perm_max``, ``stat_perm_min`` - Arithmetic mean, maximum, and minimum permeability, in mD. * - ``stat_tort`` - Dimensionless CV calculated from sampled ``tortuosity - 1`` values. It is not the CV of the displayed tortuosity values. * - ``stat_tort_mean``, ``stat_tort_max``, ``stat_tort_min`` - Arithmetic mean, maximum, and minimum hydraulic tortuosity. The routine adds one back after computing the statistics, so these dimensionless columns report tortuosity itself. For each statistical property, CV is the population standard deviation divided by the mean over valid samples; internal values equal to ``-1`` are excluded. The population standard deviation uses the number of valid samples as its divisor. A CV of ``0.05`` means 5% relative dispersion. ``-1`` means that CV is unavailable, either because no samples are valid or because the mean used for normalization is zero. The statistical size sequence begins at the smallest deterministic REV found among the enabled properties. If none was found, it begins at twice the average pore diameter. Sizes then increase while remaining smaller than the full domain's :math:`x` extent. Consequently, the statistical and deterministic files can contain different size sequences and row counts; compare them using ``x_size_vx`` or ``x_size_um``. If the initial statistical size already reaches the domain limit, the file has no data rows for that run. The current tortuosity calculation always uses the :math:`z` velocity component, even if the body force points in another direction. A zero summed :math:`z` velocity gives an invalid internal tortuosity value, which is printed as ``0`` in the deterministic file and excluded from statistical calculations. If all tortuosity samples at a size are invalid, ``stat_tort`` is ``-1`` and the reported mean, maximum, and minimum are all ``0``. These zeros indicate unavailable tortuosity statistics, not physical measurements. Falar do NaN .. raw:: html
.. raw:: html
The REV results for the Berea sandstone sample are presented in :numref:`rev-plots`, including porosity, specific surface area, absolute permeability, and hydraulic tortuosity. The Python code used to generate these plots is available in the toggle section below the figure. .. figure:: ../../../_static/images/rev_combined.svg :width: 100% :align: center :name: rev-plots Evolution of the measured properties with subvolume size for the Berea sandstone sample. Connected points represent the range of deterministic measurements; isolated points represent the averages of the statistical measurements; vertical bars represent the standard deviation of the statistical measurements; dashed lines represent the REVs identified by the selected approach; and the black line represents the upper limit of 71% for the REV size. .. raw:: html
Click here to open: Plot Code .. code-block:: python import numpy as np import matplotlib.pyplot as plt # -------------------- Configuration -------------------- rev_threshold = 0.10 TAMANHO_BASE = 25 # Options: "statistical" or "deterministic" rev_method = "statistical" # rev_method = "deterministic" colors = {"poro": "#0077b6","kabs": "#d62728","tort": "#2ca02c","surf": "#9467bd"} # -------------------- Read CSV files -------------------- det_geo = np.genfromtxt("det_rev_geometric.csv", names=True) det_hyd = np.genfromtxt("det_rev_hydraulic.csv", names=True) stat_geo = np.genfromtxt("stat_rev_geometric.csv", names=True) stat_hyd = np.genfromtxt("stat_rev_hydraulic.csv", names=True) # -------------------- Deterministic data -------------------- tamanho_um = det_geo["x_size_um"] tamanho_vx = det_geo["x_size_vx"] porovec = det_geo["porosity"] kabsvec = det_hyd["permeability"] tortvec = det_hyd["tortuosity"] surfvec = det_geo["surface"] # -------------------- Statistical data -------------------- tamanho_stat_um = stat_geo["x_size_um"] poroavg = stat_geo["stat_poro_mean"] kabsavg = stat_hyd["stat_perm_mean"] tortavg = stat_hyd["stat_tort_mean"] surfavg = stat_geo["stat_surf_mean"] # Statistical coefficients of variation cv_poro = stat_geo["stat_poro"] cv_kabs = stat_hyd["stat_perm"] cv_tort = stat_hyd["stat_tort"] cv_surf = stat_geo["stat_surf"] # Convert CV to standard deviation porostd = cv_poro * poroavg kabsstd = cv_kabs * kabsavg tortstd = cv_tort * tortavg surfstd = cv_surf * surfavg # -------------------- Final reference values -------------------- poro_final = porovec[-1] kabs_final = kabsvec[-1] tort_final = tortvec[-1] surf_final = surfvec[-1] # -------------------- Normalization -------------------- poro_norm = porovec / poro_final kabs_norm = kabsvec / kabs_final tort_norm = tortvec / tort_final surf_norm = surfvec / surf_final poroavg_norm = poroavg / poro_final kabsavg_norm = kabsavg / kabs_final tortavg_norm = tortavg / tort_final surfavg_norm = surfavg / surf_final porostd_norm = porostd / abs(poro_final) kabsstd_norm = kabsstd / abs(kabs_final) tortstd_norm = tortstd / abs(tort_final) surfstd_norm = surfstd / abs(surf_final) # -------------------- REV determination -------------------- def first_rev_size(sizes, cv, threshold): valid = np.where( np.isfinite(cv) & (cv >= 0) & (cv < threshold) )[0] return sizes[valid[0]] if valid.size else None def reported_rev_size(data, column): values = data[column] valid = values[np.isfinite(values) & (values > 0)] return valid[-1] if valid.size else None if rev_method == "statistical": rev_poro = first_rev_size( tamanho_stat_um, cv_poro, rev_threshold ) rev_kabs = first_rev_size( tamanho_stat_um, cv_kabs, rev_threshold ) rev_tort = first_rev_size( tamanho_stat_um, cv_tort, rev_threshold ) rev_surf = first_rev_size( tamanho_stat_um, cv_surf, rev_threshold ) elif rev_method == "deterministic": rev_poro = reported_rev_size( det_geo, "rev_poro_um" ) rev_kabs = reported_rev_size( det_hyd, "rev_perm_um" ) rev_tort = reported_rev_size( det_hyd, "rev_tort_um" ) rev_surf = reported_rev_size( det_geo, "rev_surf_um" ) else: raise ValueError("Unknown REV method.") print(f"Porosity REV: {rev_poro} um") print(f"Permeability REV: {rev_kabs} um") print(f"Tortuosity REV: {rev_tort} um") print(f"Surface area REV: {rev_surf} um") # -------------------- Plot configuration -------------------- plt.style.use("seaborn-v0_8-whitegrid") plt.rcParams.update({ "font.size": TAMANHO_BASE, "axes.titlesize": TAMANHO_BASE, "axes.labelsize": TAMANHO_BASE, "xtick.labelsize": TAMANHO_BASE - 4, "ytick.labelsize": TAMANHO_BASE - 4, "legend.fontsize": TAMANHO_BASE - 6, "mathtext.fontset": "cm" }) # -------------------- Plotting function -------------------- def plot_rev_properties(ax, properties, title): # First valid statistical subvolume size transition_size = np.min(tamanho_stat_um[np.isfinite(tamanho_stat_um)]) # Deterministic results stop before statistical results begin mask_det = tamanho_um < transition_size for prop in properties: # -------------------- Deterministic curves -------------------- x_det = tamanho_um[mask_det] y_det = prop["det"][mask_det] # Find the first statistical point idx_stat = np.where(np.isfinite(prop["avg"]))[0] if idx_stat.size > 0: i = idx_stat[0] x_combined = np.append(x_det, tamanho_stat_um[i]) y_combined = np.append(y_det, prop["avg"][i]) else: x_combined = x_det y_combined = y_det ax.plot(x_combined,y_combined,marker=prop["marker"],linestyle="-",color=prop["color"],label=prop["label"],fillstyle='none') # -------------------- Statistical error bars -------------------- ax.errorbar(tamanho_stat_um,prop["avg"],yerr=prop["std"],fmt=prop["marker"],linestyle="none",color=prop["color"],capsize=4,alpha=0.75,fillstyle='none') # -------------------- REV vertical lines -------------------- if prop["rev"] is not None: ax.axvline(prop["rev"],linestyle="--",color=prop["color"],linewidth=2) # -------------------- Reference horizontal line -------------------- ax.axhline(y=1.0,color="black",linestyle=":",linewidth=2) # -------------------- Additional reference lengths -------------------- lmax_media = tamanho_um[-1] * (16 + 6*np.sqrt(3))/37 # lmax_obrig = tamanho_um[-1] * (4 + np.sqrt(2))/7 ax.axvline(lmax_media, color="k", linewidth=1.5) # ax.axvline(lmax_obrig, color="k", linewidth=1.5) # -------------------- Axis labels -------------------- ax.set_title(title, pad=20) ax.set_xlabel(r"Aresta do subvolume [$\mu$m]") ax.set_ylabel("Propriedade normalizada [-]") ax.set_xlim(tamanho_um[0], tamanho_um[-1]) # -------------------- Top axis: voxel units -------------------- ax2 = ax.twiny() ax2.set_xlim(ax.get_xlim()) voxel_ticks = np.linspace(tamanho_vx[0], tamanho_vx[-1], 6) um_ticks = np.interp(voxel_ticks, tamanho_vx, tamanho_um) ax2.set_xticks(um_ticks) ax2.set_xticklabels([f"{v:.0f}" for v in voxel_ticks]) ax2.set_xlabel("Aresta do subvolume [voxels]") # -------------------- Legend -------------------- ax.legend(loc="upper right",frameon=True,facecolor="white",edgecolor="black") # -------------------- Geometric properties -------------------- geometric_properties = [ {"det": poro_norm,"avg": poroavg_norm,"std": porostd_norm,"rev": rev_poro,"color": colors["poro"],"marker": "o","label": rf"$\phi$ (final: {poro_final:.3f})"}, {"det": surf_norm,"avg": surfavg_norm,"std": surfstd_norm,"rev": rev_surf,"color": colors["surf"],"marker": "D","label": rf"$S_v$ (final: {surf_final:.4f})"} ] # -------------------- Hydraulic properties -------------------- hydraulic_properties = [ {"det":kabs_norm,"avg":kabsavg_norm,"std":kabsstd_norm,"rev":rev_kabs,"color":colors["kabs"],"marker":"s","label":rf"$k$ (final:{kabs_final:.2f} mD)"}, {"det":tort_norm,"avg":tortavg_norm,"std":tortstd_norm,"rev":rev_tort,"color":colors["tort"],"marker":"^","label":rf"$\tau$(final:{tort_final:.3f})"} ] # ===================Create side-by-side plots================ fig, axes = plt.subplots(1, 2,figsize=(22, 8),sharex=True,sharey=True) plot_rev_properties(axes[0],geometric_properties,title="(a) Geometric properties") # Left: geometric REV plot_rev_properties(axes[1],hydraulic_properties,title="(b) Hydraulic properties") # Right: hydraulic REV axes[1].set_ylabel("") # Remove duplicate Y-axis label plt.tight_layout(w_pad=3) # Layout plt.savefig("/home/ricardolmb/Desktop/REV-Code/LBPM_fork/docs-sphinx/source/_static/images/rev_combined.svg", bbox_inches="tight") .. raw:: html
.. raw:: html
-------------------------------- lbpm_rev_analysis -------------------------------- The same analysis performed by ``lbpm_rev_permeability_simulator`` can be performed by ``lbpm_rev_analysis`` if the velocity fields have already been obtained. Running ``lbpm_rev_analysis`` with the ``input.db`` file below produces the same results as those presented in :numref:`rev-plots`. .. code-block:: text Domain { Filename = "Berea.raw" ReadType = "8bit" // data type N = 400, 400, 400 // size of original image nproc = 1, 1, 1 // process grid n = 400, 400, 400 // sub-domain size offset = 0, 0, 0 // offset to read sub-domain InletLayers = 0, 0, 10 // specify 10 layers along the z-inlet OutletLayers = 0, 0, 10 // specify 10 layers along the z-outlet voxel_length = 5.345 // voxel length (in microns) ReadValues = 0, 1 // labels within the original image WriteValues = 1, 0 // associated labels to be used by LBPM BC = 0 // boundary condition type (0 for periodic) } REV { Geomtrical = True Hydraulic = True gamma = 0.1 SizeStep = 0.06 CCInterval = 10 StatSamples = 50 Seed_Anchor = True SimulationParams = "input.db" VelocityPath = "./" VelocityTimestep = "vis35000" } The ``lbpm_rev_analysis`` routine can also perform geometrical analysis alone without velocity fields. Running it with the ``input.db`` file below produces the same results as those presented in :numref:`rev-plots` (a). .. code-block:: text Domain { Filename = "Berea.raw" ReadType = "8bit" // data type N = 400, 400, 400 // size of original image nproc = 1, 1, 1 // process grid n = 400, 400, 400 // sub-domain size offset = 0, 0, 0 // offset to read sub-domain InletLayers = 0, 0, 10 // specify 10 layers along the z-inlet OutletLayers = 0, 0, 10 // specify 10 layers along the z-outlet voxel_length = 5.345 // voxel length (in microns) ReadValues = 0, 1 // labels within the original image WriteValues = 1, 0 // associated labels to be used by LBPM BC = 0 // boundary condition type (0 for periodic) } REV { Geomtrical = True Hydraulic = False gamma = 0.1 SizeStep = 0.06 CCInterval = 10 StatSamples = 50 Seed_Anchor = True } ************************************ Representative subdomain selection ************************************ Enable ``SubdomainSelect`` in ``input.db`` to select representative windows in either REV executable. For example, select a ``64 x 64 x 128`` geometric window without hydraulic input, using the statistical REV properties as selection targets: .. code-block:: text REV { Geometrical = True Hydraulic = False SubdomainSelect = True subdomain_size = 64, 64, 128 save_subdomain_raw = false gamma = 0.1 StatSamples = 50 Seed_Anchor = True } ``save_subdomain_raw`` defaults to ``false`` in both REV executables. Set ``save_subdomain_raw = true`` in the ``REV`` block to export the selected ``.raw`` crops for both geometric and hydraulic selection. With raw export disabled, selection still writes its CSV and domain databases, leaves existing raw crops and temporary raw files untouched, and leaves the CSV's ``raw_file`` field blank. For permeability selection, set ``Hydraulic = True`` and supply the saved-flow settings described above. Porosity and permeability use their independently retained statistical REV sizes and means, without repeating REV analysis. The statistical mean specific surface area is retained at the porosity REV size, and the mean hydraulic tortuosity is retained at the permeability REV size. The two targets in each pair are measured using the same statistical window dimensions. The independently assessed surface-area and tortuosity REVs do not determine the selection window sizes. ``subdomain_size`` optionally overrides the selection window dimensions in voxels. A single positive integer, such as ``subdomain_size = 64``, specifies a ``64 x 64 x 64`` cube. Alternatively, ``subdomain_size = 64, 64, 128`` specifies the exact x, y, and z dimensions. The configured dimensions apply to both geometric and hydraulic selection and must fit within the analyzed ``Domain.n``. If ``subdomain_size`` is omitted, each property uses its own statistical REV window dimensions: ``L x L x L`` for a cubic image, or the statistical analysis's proportionally scaled and rounded dimensions for a rectangular image. The override changes only the selection window. The statistical REV sizes and all retained target means, including specific surface area and hydraulic tortuosity, remain unchanged. Candidates at the requested size are compared against those targets; REV analysis is not repeated at the custom size. A converged statistical REV and valid target properties are still required. A custom size does not supply missing reference values or enable selection for an unconverged property. For a detailed description of the subdomain selection procedure, expand the toggle section below. .. raw:: html
Click here to open: Subdomain Selection .. code-block:: python The selection scans every valid position, moving one voxel along each axis and including the last window at each boundary. For the actual geometric selection window dimensions, it minimizes the following weighted score: .. math:: E_{\phi} &= \frac{|\phi_{\mathrm{local}}-\overline{\phi}_{\mathrm{REV}}|} {|\overline{\phi}_{\mathrm{REV}}|}, \\ E_{S_v} &= \frac{|S_{v,\mathrm{local}}-\overline{S_v}_{\mathrm{REV}}|} {|\overline{S_v}_{\mathrm{REV}}|}, \\ \mathrm{selection\_score} &= 0.6 E_{\phi} + 0.4 E_{S_v}. For the actual hydraulic selection window dimensions, hydraulic selection minimizes the following score: .. math:: E_k &= \frac{|k_{\mathrm{local}}-\overline{k}_{\mathrm{REV}}|} {|\overline{k}_{\mathrm{REV}}|}, \\ E_T &= \frac{|T_{\mathrm{local}}-\overline{T}_{\mathrm{REV}}|} {|\overline{T}_{\mathrm{REV}}|}, \\ \mathrm{selection\_score} &= 0.6 E_k + 0.4 E_T. Lower scores indicate a closer match. The fixed weights are 60% porosity and 40% specific surface area for the geometric region, and 60% permeability and 40% hydraulic tortuosity for the hydraulic region. Nonzero ``Domain.InletLayers`` and ``Domain.OutletLayers`` are supported. Selection then rereads the original source geometry to build the candidate pore mask before synthetic layer modifications. Raw input uses the existing ``ReadValues``/``WriteValues`` convention, including the signed-byte narrowing performed by the current ``16bit`` geometry reader. This classification does not change the bytes exported below. ``ID.00000`` labels are already mapped and are used directly. The layer settings, full-domain analysis, saved velocities, and retained statistical REV sizes and means remain unchanged. The statistical targets can therefore include synthetic layer effects; deviations from candidates measured on the original rock can be larger. Selection writes the following files in the current working directory: * When ``save_subdomain_raw = true``, ``subdomain_porosity_xx.raw`` and ``subdomain_permeability_xx.raw`` contain the independently selected crops. Here ``nx``, ``ny``, and ``nz`` are the selected window dimensions in voxels along x, y, and z, respectively. For example, a ``64 x 64 x 64`` geometric window is saved as ``subdomain_porosity_64x64x64.raw``. Each property's filename uses its own selected dimensions. Input specified by ``Domain.Filename`` with ``ReadType = "8bit"`` or ``"16bit"`` is copied byte for byte, preserving the original labels and data type before relabeling. * ``subdomain_selection.csv`` uses comma separators and records each property's status, REV size in voxels and micrometres, dimensions and physical lengths, statistical mean, selected value, relative deviation, number of candidates, raw filename in ``raw_file`` (blank when raw export is disabled), and source layout. The raw filename includes the selected window dimensions. ``rev_size_vx`` and ``rev_size_um`` retain the statistical reference size, even when ``subdomain_size`` is set. ``nx,ny,nz``, physical lengths, coordinates, candidate count, and the raw filename describe the actual selection window. Coordinates use zero-based indices and exclusive upper bounds. ``x0,y0,z0`` and ``x1,y1,z1`` refer to the halo-free analysis image. The ``source_*`` coordinates include ``Domain.offset`` or the input halos of ``ID.00000``; source dimensions and bytes per voxel allow the crop to be reproduced directly from the original file. The geometric row retains ``statistical_mean``, ``selected_value``, and ``relative_deviation`` for porosity, and adds ``statistical_mean_surface``, ``selected_surface``, ``relative_deviation_surface``, ``porosity_weight``, and ``surface_weight``. These fields are blank for the hydraulic row. The hydraulic row retains ``statistical_mean``, ``selected_value``, and ``relative_deviation`` for permeability, and adds ``statistical_mean_tortuosity``, ``selected_tortuosity``, ``relative_deviation_tortuosity``, ``permeability_weight``, and ``tortuosity_weight``. These fields are blank for the geometric row. ``selection_score`` records the applicable weighted score for either selected region. * ``geometrical-rev-domain.db`` locates the region with the lowest weighted geometric score, and ``hydraulic-rev-domain.db`` locates the region with the lowest weighted hydraulic score. Each contains only a ``Domain`` block referencing the original full sample. ``Filename`` is the absolute path to that sample, ``N`` is the full source size, ``n`` is the actual selected window size (including any ``subdomain_size`` override), and ``offset`` is its starting coordinate in the source, including any original analysis offset. The ``domain_file`` column in the CSV identifies the corresponding database. For example, the following ``Domain`` block from a ``geometrical-rev-domain.db`` output file describes a selected subvolume within a sample measuring ``400 x 400 x 400`` voxels: .. code-block:: text Domain { Filename = "/path/to/Berea.raw" ReadType = "8bit" ReadValues = 0, 1 WriteValues = 1, 0 N = 400, 400, 400 n = 64, 64, 64 offset = 100, 120, 80 nproc = 1, 1, 1 voxel_length = 5.345 BC = 0 InletLayers = 0, 0, 0 OutletLayers = 0, 0, 0 } Reuse this block in a subsequent input database to load the selected region from the full sample. A disabled property or one without a converged statistical REV receives a status row and produces no raw crop or domain database. .. raw:: html
.. raw:: html
**************************** Benchmark Cases **************************** .. list-table:: :header-rows: 1 :widths: 40 :align: center * - .. centered:: :doc:`../../../examples/SinglePhasePoreScale/3D-DigitalRocks/3D-DigitalRocks-Cases` * - .. image:: ../../../_static/images/REVHybridAnimation.gif :width: 400px :align: center