Contents


Introduction to gprMax

gprMax is a simulation software for 3D (forward) modeling of GPR (Ground Penetrating RADAR) that uses Yee’s method (FDTD (Finite Difference Time Domain)) to simulate EM wave propagation (solving Maxwell’s equations). While the purpose intended is GPR, it can be utilized for other RADAR applications that require detailed wave propagation as well. A major advantage of gprMax is that it offers GPU (nVIDIA CUDA) support.

gprMax is written in Python with critical parts written in Cython. The source code is hosted on GitHub and there is no indication that C,C++,FORTRAN or Julia is used anywhere. Further, it supports OpenMPI for multi CPU computation. It can be combined with multi GPU support.

A major advantage of using simulation software when it comes to engineering and geoscience is that, given realistic material models, we can generate more data than someone could ever collect with field work. This allows for training better machine learning models as well as optimize engineering processes. gprMax offers scritable input files and scritable geometries which makes it super easy to scale up simulation quantities.

An additional feature of gprMax is that we can model antennas as well. This allows for antenna optimization for certain responses (e.g. finding a Nazi gold train in Poland ;)).

Installation

The installation process is pretty straight forward. The default installation process requires conda and makes the process flawless.

$ git clone https://github.com/gprmax/gprMax
$ cd gprMax
$ conda env create -f conda_env.yml
$ conda activate gprMax
(gprMax) $ python setup.py build
(gprMax) $ python setup.py install

If we want to use suitable nVIDIA GPUs, then we have to make sure that all CUDA packages are installed and we’re good to go. However, the official conda list seems to be broken. Use this one instead! The environment is named gprMaxCuda.

If you dare to run it on a Raspberry Pi (which works, then use either pip3 to install libraries required or use virtualenv.

Structure of gprMax

Let’s look at some important source code folders first.

|
|-gprMax
|-tests
|-tools
|-user_libs
 \user_models

gprMax contains the source code of the simulation software and test contains unit tests and some examples (e.g. as Jupyter notebooks). The internals are visualized here.

user_libs contains some interesting things such as the Austin Man & Women human body model, models for antennas and materials as well as functions for Taguchi optimization.

user_models contains some examples provided by the authors of gprMax. Unfortunately, they are the only “reference” I could find on geometries etc. I’m going to write something more descriptive some time this or next month (and will link it here).

Perhaps the most important folder here is tools since it contains post-processing tools for plotting and extracting results which btw are stored as h5 files but called .out.

Examples provided

Let’s have a quick look at some examples. For my current projects they are enough to get started with but for more complex stuff, there is a lack of documentation.

2D A-scan

The 2D (half-space) A-scan example shows the simplest thing we can do with gprMax. We are creating a 2D plane (half-space) that contains a metal cylinder. Let’s look at the input file to understand some of the input parameters:

#title: A-scan from a metal cylinder buried in a dielectric half-space
#domain: 0.240 0.210 0.002
#dx_dy_dz: 0.002 0.002 0.002
#time_window: 3e-9

#material: 6 0 1 0 half_space

#waveform: ricker 1 1.5e9 my_ricker
#hertzian_dipole: z 0.100 0.170 0 my_ricker
#rx: 0.140 0.170 0

#box: 0 0 0 0.240 0.170 0.002 half_space
#cylinder: 0.120 0.080 0 0.120 0.080 0.002 0.010 pec

#geometry_view: 0 0 0 0.240 0.210 0.002 0.002 0.002 0.002 cylinder_half_space n

The first line of an input file defiles the title (#title). It seems like python comments are used for defining properties since it basically is written in Python and further it allows Python functions in the input files.

#domain defines the extend of the computational domain in the unit of meters. Since we are using finite differences here, we are dealing with a structured mesh and therefore have to define discretization. It seems like #dx_dy_dz is the only thing we can use. I haven’t found any way (so far) to discretize with different precision (e.g. similar to OpenFOAM snappyHexMesh). Besides spatial discretization, we have to deal with temporal (time) discretization. We use #time_window for that. Here we have two options:

  • using an integer to define the number of iterations
  • using a floating point number to define the simulation time and iterations and time steps are calculated internally

A very important task when it comes to numerical simulation is the selection of material parameters. We define material parameters with #material. It requires 5 parameters. The first one is the relative permittivity, the second is conductivity [Siemens/meter], the third is the relative permeability, the fourth is the magnetic loss [Ohm/meter] of the material and the fith and final parameter is an identifier string. We are able to define anisotropic materials but that is part of another blog post. With #box, #cylinder and #sphere we can create volume objects. #box is defined with the lower left coordinates (x,y,z) followed by the upper right coordinates (x,y,z) followed by the name of the material assigned to it. Dielectric smoothing can be assigned by a followed y or n. #cylinder is defined by xyz coordinates of both faces followed by the radius of the cylinder. Finally, we have to assign a material. In this case it is pec which is a predefined material for metal. Another predefined material is free_space. Again, we can enable or disable dielectric smoothing.

So far we enjoyed generating materials and geometries. Since we want to simulate EM wave propagation, we have to defined a waveform and receivers. We can select from a variety of waveforms (e.g. ricker), define a scaling factor as well as the frequency it is centered around. Finally, we assign a name to it. This is how we can simulate multiple sources. #hertzian_dipole defines the location of the source (TX) and #rx the location of the receiver (RX).

#geometry_view saves the geometry we generated to a VTK file which we can view with e.g. Paraview. This is the geometry we generate with this example script:

(gprMax) $ python -m gprMax ./cylinder_Ascan_2D.in -gpu

=== Electromagnetic modelling software based on the Finite-Difference Time-Domain (FDTD) method 

    www.gprmax.com   __  __
     __ _ _ __  _ __|  \/  | __ ___  __
    / _` | '_ \| '__| |\/| |/ _` \ \/ /
   | (_| | |_) | |  | |  | | (_| |>  <
    \__, | .__/|_|  |_|  |_|\__,_/_/\_\
    |___/|_|
                     v3.1.5 (Big Smoke)

 Copyright (C) 2015-2019: The University of Edinburgh
 Authors: Craig Warren and Antonis Giannopoulos

...
...
...

--- Model 1/1, input file: ./cylinder_Ascan_2D.in -----------------------------

Constants/variables used/available for Python scripting: {c: 299792458.0, current_model_run: 1, e0: 8.854187817620389e-12, inputfile: ./cylinder_Ascan_2D.in, m0: 1.2566370614359173e-06, number_model_runs: 1, z0: 376.73031346177066}

Model title: A-scan from a metal cylinder buried in a dielectric half-space
Number of CPU (OpenMP) threads: 4
GPU solving using: 0 - GeForce GTX 1060 6GB
Spatial discretisation: 0.002 x 0.002 x 0.002m
Domain size: 0.24 x 0.21 x 0.002m (120 x 105 x 1 = 12600 cells)
Mode: 2D TMz
Time step (at CFL limit): 4.71731e-12 secs
Time window: 3e-09 secs (637 iterations)

Waveform my_ricker of type ricker with maximum amplitude scaling 1, frequency 1.5e+09Hz created.
Hertzian dipole with polarity z at 0.1m, 0.17m, 0m, using waveform my_ricker created.
Receiver at 0.14m, 0.17m, 0m with output component(s) Ex, Ey, Ez, Hx, Hy, Hz created.
Material half_space with eps_r=6, sigma=0 S/m; mu_r=1, sigma*=0 Ohm/m created.
Geometry view from 0m, 0m, 0m, to 0.24m, 0.21m, 0.002m, discretisation 0.002m, 0.002m, 0.002m, with filename base cylinder_half_space created.

Memory (RAM) required: ~51.5MB host + ~51.5MB GPU

Box from 0m, 0m, 0m, to 0.24m, 0.17m, 0.002m of material(s) half_space created, dielectric smoothing is on.
Cylinder with face centres 0.12m, 0.08m, 0m and 0.12m, 0.08m, 0.002m, with radius 0.01m, of material(s) pec created, dielectric smoothing is off.
Processing geometry related cmds: 100%|██████| 2/2 [00:00<00:00, 1074.09cmds/s]

PML: formulation: HORIPML, order: 1, thickness: x0: 10, y0: 10, z0: 0, xmax: 10, ymax: 10, zmax: 0 cells
Building PML boundaries: 100%|██████████████████| 4/4 [00:00<00:00, 446.07it/s]

Building main grid: 100%|███████████████████████| 2/2 [00:00<00:00, 521.74it/s]

Materials:
    |                                             |                     |       | sigma |      | sigma*  | Dielectric 
 ID | Name                                        | Type                | eps_r | [S/m] | mu_r | [Ohm/m] | smoothable 
----+---------------------------------------------+---------------------+-------+-------+------+---------+------------
  0 | pec                                         | builtin             | 1     | inf   | 1    | 0       | False      
  1 | free_space                                  | builtin             | 1     | 0     | 1    | 0       | True       
  2 | half_space                                  |                     | 6     | 0     | 1    | 0       | True       
  3 | free_space+free_space+half_space+half_space | dielectric-smoothed | 3.5   | 0     | 1    | 0       | True       

Numerical dispersion analysis: estimated largest physical phase-velocity error is -0.42% in material 'half_space' whose wavelength sampled by 14 cells. Maximum significant frequency estimated as 4.32623e+09Hz

Writing geometry view file 1/1, cylinder_half_space.vti:   0%| | 0.00/75.6k [00:Writing geometry view file 1/1, cylinder_half_space.vti: 100%|█| 75.6k/75.6k [00:00<00:00, 2.25Mbyte/s]

Output file: ./cylinder_Ascan_2D.out

Running simulation, model 1/1: 100%|███████| 637/637 [00:00<00:00, 1763.66it/s]
Memory (RAM) used: ~206MB host + ~313MB GPU
Solving time [HH:MM:SS]: 0:00:00.362102

=== Simulation completed in [HH:MM:SS]: 0:00:00.706580 ========================


We have to understand that we record only the data at the receiver and no full mesh snapshots using the script above. We can visualize the receiver record using tools provided in tools.

(gprMax) $ python -m tools.plot_Ascan outputfile.out

2D B-scan

Let’s try something a bit more advanced. Usually, we don’t do single scans (A scans) but we move around with our GPR equipment.

#title: B-scan from a metal cylinder buried in a dielectric half-space
#domain: 0.240 0.210 0.002
#dx_dy_dz: 0.002 0.002 0.002
#time_window: 3e-9

#material: 6 0 1 0 half_space

#waveform: ricker 1 1.5e9 my_ricker
#hertzian_dipole: z 0.040 0.170 0 my_ricker
#rx: 0.080 0.170 0
#src_steps: 0.002 0 0
#rx_steps: 0.002 0 0

#box: 0 0 0 0.240 0.170 0.002 half_space
#cylinder: 0.120 0.080 0 0.120 0.080 0.002 0.010 pec

The input file is very similar to the example above. However, there is one important difference. We have two new paramters, #src_steps and #rx_steps. XYZ increments for each time step are assigned to move the transmitter and/or receiver position.

$ python -m gprMax ./cylinder_Bscan_2D.in -gpu

=== Electromagnetic modelling software based on the Finite-Difference Time-Domain (FDTD) method 

    www.gprmax.com   __  __
     __ _ _ __  _ __|  \/  | __ ___  __
    / _` | '_ \| '__| |\/| |/ _` \ \/ /
   | (_| | |_) | |  | |  | | (_| |>  <
    \__, | .__/|_|  |_|  |_|\__,_/_/\_\
    |___/|_|
                     v3.1.5 (Big Smoke)

 Copyright (C) 2015-2019: The University of Edinburgh
 Authors: Craig Warren and Antonis Giannopoulos

 gprMax is free software: you can redistribute it and/or modify it under the terms of
  the GNU General Public License as published by the Free Software Foundation, either
  version 3 of the License, or (at your option) any later version.
 gprMax is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
  without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
  PURPOSE.  See the GNU General Public License for more details.
 You should have received a copy of the GNU General Public License along with gprMax.
  If not, see www.gnu.org/licenses.

...
...
...

--- Model 1/1, input file: ./cylinder_Bscan_2D.in --------------------------------------

Constants/variables used/available for Python scripting: {c: 299792458.0, current_model_run: 1, e0: 8.854187817620389e-12, inputfile: ./cylinder_Bscan_2D.in, m0: 1.2566370614359173e-06, number_model_runs: 1, z0: 376.73031346177066}

Model title: B-scan from a metal cylinder buried in a dielectric half-space
Number of CPU (OpenMP) threads: 4
GPU solving using: 0 - GeForce GTX 1060 6GB
Spatial discretisation: 0.002 x 0.002 x 0.002m
Domain size: 0.24 x 0.21 x 0.002m (120 x 105 x 1 = 12600 cells)
Mode: 2D TMz
Time step (at CFL limit): 4.71731e-12 secs
Time window: 3e-09 secs (637 iterations)
Simple sources will step 0.002m, 0m, 0m for each model run.
All receivers will step 0.002m, 0m, 0m for each model run.

Waveform my_ricker of type ricker with maximum amplitude scaling 1, frequency 1.5e+09Hz created.
Hertzian dipole with polarity z at 0.04m, 0.17m, 0m, using waveform my_ricker created.
Receiver at 0.08m, 0.17m, 0m with output component(s) Ex, Ey, Ez, Hx, Hy, Hz created.
Material half_space with eps_r=6, sigma=0 S/m; mu_r=1, sigma*=0 Ohm/m created.

Memory (RAM) required: ~51.5MB host + ~51.5MB GPU

Box from 0m, 0m, 0m, to 0.24m, 0.17m, 0.002m of material(s) half_space created, dielectric smoothing is on.
Cylinder with face centres 0.12m, 0.08m, 0m and 0.12m, 0.08m, 0.002m, with radius 0.01m, of material(s) pec created, dielectric smoothing is off.
Processing geometry related cmds: 100%|███████████████| 2/2 [00:00<00:00, 1506.30cmds/s]

PML: formulation: HORIPML, order: 1, thickness: x0: 10, y0: 10, z0: 0, xmax: 10, ymax: 10, zmax: 0 cells
Building PML boundaries: 100%|███████████████████████████| 4/4 [00:00<00:00, 637.58it/s]

Building main grid: 100%|████████████████████████████████| 2/2 [00:00<00:00, 558.98it/s]

Materials:
    |                                             |                     |       | sigma |      | sigma*  | Dielectric 
 ID | Name                                        | Type                | eps_r | [S/m] | mu_r | [Ohm/m] | smoothable 
----+---------------------------------------------+---------------------+-------+-------+------+---------+------------
  0 | pec                                         | builtin             | 1     | inf   | 1    | 0       | False      
  1 | free_space                                  | builtin             | 1     | 0     | 1    | 0       | True       
  2 | half_space                                  |                     | 6     | 0     | 1    | 0       | True       
  3 | free_space+free_space+half_space+half_space | dielectric-smoothed | 3.5   | 0     | 1    | 0       | True       

Numerical dispersion analysis: estimated largest physical phase-velocity error is -0.42% in material 'half_space' whose wavelength sampled by 14 cells. Maximum significant frequency estimated as 4.32623e+09Hz

Output file: ./cylinder_Bscan_2D.out

Running simulation, model 1/1: 100%|████████████████| 637/637 [00:00<00:00, 1724.40it/s]
Memory (RAM) used: ~206MB host + ~372MB GPU
Solving time [HH:MM:SS]: 0:00:00.370340

=== Simulation completed in [HH:MM:SS]: 0:00:00.661412 =================================

Unfortunately, I can’t visualize the results since it seems like Bscan visualization works with 3D only but I’m not so sure about it and it has no priority for me to figure it out.

3D with realistic soil properties

Let’s build something more realistic. The probably most important and most challenging thing to build correctly is a realistic soil or rock model. This example utilizes a stochastic model of soil property distribution by Peplinski et al. 1995 as described in Giannakis 2016 (PhD thesis).

#title: Heterogeneous soil using a stochastic distribution of dielectric properties given by a mixing model from Peplinski
#domain: 0.4 0.4 0.4
#dx_dy_dz: 0.001 0.001 0.001
#time_window: 6e-9

#waveform: ricker 1 1.5e9 my_ricker
#hertzian_dipole: y 0.045 0.075 0.085 my_ricker
#rx: 0.105 0.075 0.085

#soil_peplinski: 0.5 0.5 2.0 2.66 0.001 0.25 my_soil
#fractal_box: 0 0 0 0.15 0.15 0.070 1.5 1 1 1 50 my_soil my_soil_box
#add_surface_roughness: 0 0 0.070 0.15 0.15 0.070 1.5 1 1 0.065 0.080 my_soil_box

#geometry_view: 0 0 0 0.15 0.15 0.1 0.001 0.001 0.001 heterogeneous_soil n

#soil_peplinski is a pre-defined for this. It is defined by the sand fraction followed by the clay fraction. Other properties are bulk density, sand particle density and a defined range of water fraction (volumetric). Additionally, we use a realistic surface model using #add_surface_roughness. The first six parameters define a #fractal_box followed by 3 fractions for each direction and a range defined by two values that define surface roughness.

$ python -m gprMax ./heterogeneous_soil.in -gpu

=== Electromagnetic modelling software based on the Finite-Difference Time-Domain (FDTD) method 

    www.gprmax.com   __  __
     __ _ _ __  _ __|  \/  | __ ___  __
    / _` | '_ \| '__| |\/| |/ _` \ \/ /
   | (_| | |_) | |  | |  | | (_| |>  <
    \__, | .__/|_|  |_|  |_|\__,_/_/\_\
    |___/|_|
                     v3.1.5 (Big Smoke)

 Copyright (C) 2015-2019: The University of Edinburgh
 Authors: Craig Warren and Antonis Giannopoulos

 gprMax is free software: you can redistribute it and/or modify it under the terms of
  the GNU General Public License as published by the Free Software Foundation, either
  version 3 of the License, or (at your option) any later version.
 gprMax is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
  without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
  PURPOSE.  See the GNU General Public License for more details.
 You should have received a copy of the GNU General Public License along with gprMax.
  If not, see www.gnu.org/licenses.

...
...
...

--- Model 1/1, input file: ./heterogeneous_soil.in -------------------------------------

Constants/variables used/available for Python scripting: {c: 299792458.0, current_model_run: 1, e0: 8.854187817620389e-12, inputfile: ./heterogeneous_soil.in, m0: 1.2566370614359173e-06, number_model_runs: 1, z0: 376.73031346177066}

Model title: Heterogeneous soil using a stochastic distribution of dielectric properties given by a mixing model from Peplinski
Number of CPU (OpenMP) threads: 4
GPU solving using: 0 - GeForce GTX 1060 6GB
Spatial discretisation: 0.001 x 0.001 x 0.001m
Domain size: 0.4 x 0.4 x 0.4m (400 x 400 x 400 = 6.4e+07 cells)
Mode: 3D
Time step (at CFL limit): 1.92583e-12 secs
Time window: 6e-09 secs (3117 iterations)

Waveform my_ricker of type ricker with maximum amplitude scaling 1, frequency 1.5e+09Hz created.
Hertzian dipole with polarity y at 0.045m, 0.075m, 0.085m, using waveform my_ricker created.
Receiver at 0.105m, 0.075m, 0.085m with output component(s) Ex, Ey, Ez, Hx, Hy, Hz created.
Mixing model (Peplinski) used to create my_soil with sand fraction 0.5, clay fraction 0.5, bulk density 2g/cm3, sand particle density 2.66g/cm3, and water volumetric fraction 0.001 to 0.25 created.
Geometry view from 0m, 0m, 0m, to 0.15m, 0.15m, 0.1m, discretisation 0.001m, 0.001m, 0.001m, with filename base heterogeneous_soil created.

Memory (RAM) required: ~4.59GB host + ~4.59GB GPU

Fractal box my_soil_box from 0m, 0m, 0m, to 0.15m, 0.15m, 0.07m with my_soil, fractal dimension 1.5, fractal weightings 1, 1, 1, fractal seeding None, with 50 material(s) created, dielectric smoothing is off.
Fractal surface from 0m, 0m, 0.07m, to 0.15m, 0.15m, 0.07m with fractal dimension 1.5, fractal weightings 1, 1, fractal seeding None, and range 0.065m to 0.08m, added to my_soil_box.
Processing geometry related cmds: 100%|█████████████████| 2/2 [00:02<00:00,  1.12s/cmds]

PML: formulation: HORIPML, order: 1, thickness: 10
Building PML boundaries: 100%|████████████████████████████| 6/6 [00:11<00:00,  1.68s/it]

Building main grid: 100%|█████████████████████████████████| 2/2 [00:04<00:00,  2.58s/it]

Memory (RAM) required - updated (dispersive): ~6.14GB


Materials:
    |            |         |         | sigma     | Delta     | tau       | omega | delta | gamma |      | sigma*  | Dielectric 
 ID | Name       | Type    | eps_r   | [S/m]     | eps_r     | [s]       | [Hz]  | [Hz]  | [Hz]  | mu_r | [Ohm/m] | smoothable 
----+------------+---------+---------+-----------+-----------+-----------+-------+-------+-------+------+---------+------------
  0 | pec        | builtin | 1       | inf       |           |           |       |       |       | 1    | 0       | False      
  1 | free_space | builtin | 1       | 0         |           |           |       |       |       | 1    | 0       | True       
  2 | |0.0035|   | debye   | 3.67822 | 0.0109054 | 0.0191009 | 9.231e-12 |       |       |       | 1    | 0       | False      
  3 | |0.0086|   | debye   | 3.93309 | 0.0165236 | 0.0704761 | 9.231e-12 |       |       |       | 1    | 0       | False      
  4 | |0.0137|   | debye   | 4.16545 | 0.0205139 | 0.139061  | 9.231e-12 |       |       |       | 1    | 0       | False      
  5 | |0.0188|   | debye   | 4.38366 | 0.0237684 | 0.220869  | 9.231e-12 |       |       |       | 1    | 0       | False      
  6 | |0.0239|   | debye   | 4.59147 | 0.0265794 | 0.313802  | 9.231e-12 |       |       |       | 1    | 0       | False      
  7 | |0.0289|   | debye   | 4.79108 | 0.0290859 | 0.416508  | 9.231e-12 |       |       |       | 1    | 0       | False      
  8 | |0.0340|   | debye   | 4.98393 | 0.0313671 | 0.528021  | 9.231e-12 |       |       |       | 1    | 0       | False      
  9 | |0.0391|   | debye   | 5.17106 | 0.0334729 | 0.647611  | 9.231e-12 |       |       |       | 1    | 0       | False      
 10 | |0.0442|   | debye   | 5.35323 | 0.0354373 | 0.774695  | 9.231e-12 |       |       |       | 1    | 0       | False      
 11 | |0.0493|   | debye   | 5.53106 | 0.0372846 | 0.908801  | 9.231e-12 |       |       |       | 1    | 0       | False      
 12 | |0.0544|   | debye   | 5.70502 | 0.0390329 | 1.04953   | 9.231e-12 |       |       |       | 1    | 0       | False      
 13 | |0.0594|   | debye   | 5.8755  | 0.040696  | 1.19655   | 9.231e-12 |       |       |       | 1    | 0       | False      
 14 | |0.0645|   | debye   | 6.04283 | 0.0422849 | 1.34955   | 9.231e-12 |       |       |       | 1    | 0       | False      
 15 | |0.0696|   | debye   | 6.20729 | 0.0438084 | 1.5083    | 9.231e-12 |       |       |       | 1    | 0       | False      
 16 | |0.0747|   | debye   | 6.36912 | 0.0452736 | 1.67255   | 9.231e-12 |       |       |       | 1    | 0       | False      
 17 | |0.0798|   | debye   | 6.52852 | 0.0466866 | 1.8421    | 9.231e-12 |       |       |       | 1    | 0       | False      
 18 | |0.0848|   | debye   | 6.68567 | 0.0480524 | 2.01678   | 9.231e-12 |       |       |       | 1    | 0       | False      
 19 | |0.0899|   | debye   | 6.84073 | 0.0493752 | 2.19642   | 9.231e-12 |       |       |       | 1    | 0       | False      
 20 | |0.0950|   | debye   | 6.99384 | 0.0506588 | 2.38085   | 9.231e-12 |       |       |       | 1    | 0       | False      
 21 | |0.1001|   | debye   | 7.14511 | 0.0519062 | 2.56996   | 9.231e-12 |       |       |       | 1    | 0       | False      
 22 | |0.1052|   | debye   | 7.29467 | 0.0531203 | 2.7636    | 9.231e-12 |       |       |       | 1    | 0       | False      
 23 | |0.1103|   | debye   | 7.44261 | 0.0543036 | 2.96166   | 9.231e-12 |       |       |       | 1    | 0       | False      
 24 | |0.1153|   | debye   | 7.58903 | 0.055458  | 3.16402   | 9.231e-12 |       |       |       | 1    | 0       | False      
 25 | |0.1204|   | debye   | 7.734   | 0.0565857 | 3.3706    | 9.231e-12 |       |       |       | 1    | 0       | False      
 26 | |0.1255|   | debye   | 7.87761 | 0.0576883 | 3.58129   | 9.231e-12 |       |       |       | 1    | 0       | False      
 27 | |0.1306|   | debye   | 8.01991 | 0.0587673 | 3.796     | 9.231e-12 |       |       |       | 1    | 0       | False      
 28 | |0.1357|   | debye   | 8.16098 | 0.0598242 | 4.01464   | 9.231e-12 |       |       |       | 1    | 0       | False      
 29 | |0.1407|   | debye   | 8.30087 | 0.0608602 | 4.23715   | 9.231e-12 |       |       |       | 1    | 0       | False      
 30 | |0.1458|   | debye   | 8.43964 | 0.0618764 | 4.46343   | 9.231e-12 |       |       |       | 1    | 0       | False      
 31 | |0.1509|   | debye   | 8.57734 | 0.0628739 | 4.69343   | 9.231e-12 |       |       |       | 1    | 0       | False      
 32 | |0.1560|   | debye   | 8.71401 | 0.0638536 | 4.92708   | 9.231e-12 |       |       |       | 1    | 0       | False      
 33 | |0.1611|   | debye   | 8.84969 | 0.0648165 | 5.1643    | 9.231e-12 |       |       |       | 1    | 0       | False      
 34 | |0.1662|   | debye   | 8.98443 | 0.0657633 | 5.40505   | 9.231e-12 |       |       |       | 1    | 0       | False      
 35 | |0.1712|   | debye   | 9.11827 | 0.0666948 | 5.64926   | 9.231e-12 |       |       |       | 1    | 0       | False      
 36 | |0.1763|   | debye   | 9.25123 | 0.0676117 | 5.89687   | 9.231e-12 |       |       |       | 1    | 0       | False      
 37 | |0.1814|   | debye   | 9.38335 | 0.0685145 | 6.14784   | 9.231e-12 |       |       |       | 1    | 0       | False      
 38 | |0.1865|   | debye   | 9.51467 | 0.069404  | 6.40212   | 9.231e-12 |       |       |       | 1    | 0       | False      
 39 | |0.1916|   | debye   | 9.64521 | 0.0702807 | 6.65965   | 9.231e-12 |       |       |       | 1    | 0       | False      
 40 | |0.1966|   | debye   | 9.775   | 0.0711451 | 6.92039   | 9.231e-12 |       |       |       | 1    | 0       | False      
 41 | |0.2017|   | debye   | 9.90407 | 0.0719976 | 7.1843    | 9.231e-12 |       |       |       | 1    | 0       | False      
 42 | |0.2068|   | debye   | 10.0324 | 0.0728388 | 7.45133   | 9.231e-12 |       |       |       | 1    | 0       | False      
 43 | |0.2119|   | debye   | 10.1601 | 0.073669  | 7.72144   | 9.231e-12 |       |       |       | 1    | 0       | False      
 44 | |0.2170|   | debye   | 10.2872 | 0.0744886 | 7.99459   | 9.231e-12 |       |       |       | 1    | 0       | False      
 45 | |0.2221|   | debye   | 10.4135 | 0.0752981 | 8.27074   | 9.231e-12 |       |       |       | 1    | 0       | False      
 46 | |0.2271|   | debye   | 10.5393 | 0.0760978 | 8.54986   | 9.231e-12 |       |       |       | 1    | 0       | False      
 47 | |0.2322|   | debye   | 10.6645 | 0.076888  | 8.83192   | 9.231e-12 |       |       |       | 1    | 0       | False      
 48 | |0.2373|   | debye   | 10.7891 | 0.077669  | 9.11687   | 9.231e-12 |       |       |       | 1    | 0       | False      
 49 | |0.2424|   | debye   | 10.9132 | 0.0784411 | 9.40468   | 9.231e-12 |       |       |       | 1    | 0       | False      
 50 | |0.2475|   | debye   | 11.0367 | 0.0792047 | 9.69532   | 9.231e-12 |       |       |       | 1    | 0       | False      
 51 | |0.2525|   | debye   | 11.1596 | 0.07996   | 9.98877   | 9.231e-12 |       |       |       | 1    | 0       | False      

Numerical dispersion analysis: estimated largest physical phase-velocity error is -0.45% in material '|0.2525|' whose wavelength sampled by 15 cells. Maximum significant frequency estimated as 4.16471e+09Hz

Writing geometry view file 1/1, heterogeneous_soil.vti:   0%| | 0.00/13.5M [00:00<?, ?bytWriting geometry view file 1/1, heterogeneous_soil.vti: 100%|█| 13.5M/13.5M [00:00<00:00, 161Mbyte/s]

Output file: ./heterogeneous_soil.out

Running simulation, model 1/1: 100%|████████████████| 3117/3117 [05:40<00:00,  9.25it/s]
Memory (RAM) used: ~2.32GB host + ~5.22GB GPU
Solving time [HH:MM:SS]: 0:05:47.092594

=== Simulation completed in [HH:MM:SS]: 0:06:07.930280 =============================================================================================================================


This produces more realistic responses: