Advanced features
This section provides example models of some of the more advanced features of gprMax. Each example comes with an input file which you can download and run.
Eigenmode ports
The nine numbered examples under examples/features/eigenmode_ports progress
from straight and curved guides to a closed-surface horn, a complete modal
matrix study, a phase-steered array, a guide crossing cutoff, and physically
aligned degenerate circular TE11 modes. Examples 8 and 9 demonstrate the
experimental automatic tracker on an automatically detected TE11 pair and
a true crossing of two polarized branches in an anisotropic waveguide.
Automatic tracking requires further testing and should be used at the user’s
discretion; legacy tracking remains the default.
Eigenmode ports and S-parameter analysis provides a step-by-step tutorial for users who primarily
want S-parameters and far fields.
Always inspect the solved mode before committing to a long FDTD run:
python examples/features/eigenmode_ports/example_1_straight_waveguide/straight_waveguide.py --geometry-only
Geometry-only mode builds the material grid and solves the mode without time stepping. It automatically writes the modal-field plot. Check its requested polarisation, symmetry, mode order, confinement, and field behaviour at material, PEC, or PMC boundaries.
Fig. 32 Yee-staggered active field components of the fundamental 2D TM dielectric-slab mode.
The complete regression and directionality matrix is kept separately under
testing/regression/eigenmode_sources; it is not intended as introductory
user material.
Surface-impedance walls
Start with the copper-versus-PEC rectangular-guide workflow in Surface impedance and impedance volumes. It introduces material fitting and wall geometry, then explains how to inspect modal and receiver results. The same guide links executable 2D TE/TM virtual continuations and PEC/PMC contact examples. The boundary equations and benchmark evidence are in Surface impedance: theory and validation.
Plane-wave TFSF source
dielectric_sphere_tfsf.in
and its equivalent
Python API model
demonstrate a discrete plane wave incident on a dielectric sphere. The sphere
and one receiver lie inside the total-field/scattered-field box, while a
second receiver beyond its x-maximum face records scattered field only.
1#title: TFSF plane wave scattering from a dielectric sphere
2#domain: 0.160 0.160 0.160
3#dx_dy_dz: 0.002 0.002 0.002
4#time_window: 3e-9
5#pml_cells: 8
6
7#material: 4 0 1 0 dielectric
8#waveform: ricker 1 1.5e9 incident
9
10#sphere: 0.080 0.080 0.080 0.016 dielectric
11
12## +x propagation; the sphere and first receiver are inside the total-field box.
13#plane_wave_vector: 0.040 0.040 0.040 0.120 0.120 0.120 1 0 0 90 incident
14#rx: 0.060 0.080 0.080 total_field Ex Ey Ez Hx Hy Hz
15
16## This receiver is outside the TFSF box and therefore samples scattered field.
17#rx: 0.130 0.080 0.080 scattered_field Ex Ey Ez Hx Hy Hz
18
19#geometry_view: 0 0 0 0.160 0.160 0.160 0.002 0.002 0.002 dielectric_sphere_tfsf n
The example above is deliberately small and focuses on the TFSF field separation. For a quantitative far-field scattering calculation, use the PEC sphere example below.
PEC-sphere radar cross section
pec_sphere_rcs.in
and the equivalent
Python API model
together with their
plotting script
form a complete monostatic radar cross-section workflow. A 16 mm-radius PEC
sphere is represented on a 0.5 mm grid and illuminated by a broadband,
z-polarised plane wave propagating in the positive x direction. The 12 ns time
window allows the transient to decay before the frequency-domain results are
formed. The model uses the default 10-cell PML, so no #pml_cells command is
needed.
1#title: PEC-sphere monostatic backscatter RCS
2#domain: 0.160 0.160 0.160
3#dx_dy_dz: 0.0005 0.0005 0.0005
4#time_window: 12e-9
5
6#waveform: ricker 1 4.5e9 incident_pulse
7#sphere: 0.080 0.080 0.080 0.016 pec
8
9## +x propagation with E parallel to +z.
10#plane_wave_vector: 0.040 0.040 0.040 0.120 0.120 0.120 1 0 0 90 incident_pulse
11
12## The transform surface encloses the complete TFSF box.
13#ntff_surface: 0.028 0.028 0.028 0.132 0.132 0.132 rcs_surface
14#ntff_frequency: rcs_surface rcs_spectrum 0.75e9 1e9 1.25e9 1.5e9 1.75e9 2e9 2.25e9 2.5e9 2.75e9 3e9 3.25e9 3.5e9 3.75e9 4e9 4.25e9 4.5e9 4.75e9 5e9 5.25e9 5.5e9 5.75e9 6e9 6.25e9 6.5e9 6.75e9 7e9 7.25e9 7.5e9 7.75e9 8e9 8.25e9 8.5e9 8.75e9 9e9 rectangular
15#ntff_far_field: 90 180 rcs_spectrum backscatter Etheta Ephi rcs
16
17#geometry_view: 0 0 0 0.160 0.160 0.160 0.0005 0.0005 0.0005 pec_sphere_rcs n
The TFSF box encloses the sphere. The larger NTFFSurface encloses the
complete TFSF box and remains in homogeneous free space, clear of both the
TFSF correction stencil and the PML. NTFFFrequencyTransform streams the
tangential fields needed by the conventional equivalent-current formulation
at 34 frequencies from 0.75 to 9 GHz. The hash-command model automatically
associates the enclosed plane wave with the transform. The Python API makes
the same choice explicit with plane_wave_index=0.
NTFFFarField requests the monostatic direction
\(\theta=90^\circ\), \(\phi=180^\circ\), opposite to the incident
wave. The stored rcs array is linear and has units of square metres. The
plotting script normalises it by the geometrical cross section
\(\pi a^2\) and independently evaluates a dense analytical PEC Mie series.
The electrical-size axis, \(ka=2\pi f a/c\), exposes the familiar
resonances and nulls of PEC-sphere backscatter; a secondary axis gives the
corresponding frequency.
From the repository root, run with:
python -m gprMax examples/rcs/pec_sphere/pec_sphere_rcs.in -gpu 0
python examples/rcs/pec_sphere/plot_pec_sphere_rcs.py
Omit -gpu 0 to run on the CPU. A GPU is recommended for this
\(320^3\)-cell model. The model also writes a fine VTK HDF geometry view
for inspection in ParaView.
Fig. 33 Monostatic backscatter RCS from the equivalent-current NTFF output compared with the PEC-sphere Mie series over \(0.25<ka<3.02\).
For this 0.5 mm discretisation, which represents the sphere radius with 32 cells, the RMS difference over the sweep is approximately 0.44 dB. At \(ka\simeq1\), gprMax gives -25.16 dBsm and the Mie series gives -25.33 dBsm. The error grows around sharp RCS minima because a small shift in a null produces a relatively large dB difference. The principal limitation is the staircased representation of the curved PEC surface; a formal convergence study should repeat the model at several spatial resolutions and compare the complex far fields as well as RCS.
Building a heterogeneous soil
This example demonstrates how to build a more realistic soil model using a stochastic distribution of dielectric properties. A mixing model for soils proposed by Peplinski (http://dx.doi.org/10.1109/36.387598) is used to define a series of dispersive material properties for the soil.
1#title: Heterogeneous soil using a stochastic distribution of dielectric properties given by a mixing model from Peplinski
2#domain: 0.15 0.15 0.1
3#dx_dy_dz: 0.001 0.001 0.001
4#time_window: 6e-9
5
6#waveform: ricker 1 1.5e9 my_ricker
7#hertzian_dipole: y 0.045 0.075 0.085 my_ricker
8#rx: 0.105 0.075 0.085
9
10#soil_peplinski: 0.5 0.5 2.0 2.66 0.001 0.25 my_soil
11#fractal_box: 0 0 0 0.15 0.15 0.070 1.5 1 1 1 50 my_soil my_soil_box
12#add_surface_roughness: 0 0 0.070 0.15 0.15 0.070 1.5 1 1 0.065 0.080 my_soil_box
13
14#geometry_view: 0 0 0 0.15 0.15 0.1 0.001 0.001 0.001 heterogeneous_soil n
Fig. 34 FDTD geometry mesh showing a heterogeneous soil model with a rough surface.
Line 10 defines a series of dispersive materials to represent a soil with sand fraction 0.5, clay fraction 0.5, bulk density \(2~g/cm^3\), sand particle density of \(2.66~g/cm^3\), and a volumetric water fraction range of 0.001 - 0.25. The volumetric water fraction is given as a range which is what defines a series of dispersive materials.
These materials can then be distributed stochastically over a volume using the #fractal_box command. Line 11 defines a volume, a fractal dimension, a number of materials, and a mixing model to use. The fractal dimension, 1.5, controls how the materials are stochastically distributed. The fractal weightings, 1, 1, 1, weight the fractal in the x, y, and z directions. The number of materials, 50, specifies how many dispersive materials to create using the mixing model (my_soil).
Adding rough surfaces
A rough surface can be added to any side of #fractal_box using,
#add_surface_roughness: 0 0 0.070 0.15 0.15 0.070 1.5 1 1 0.065 0.080 my_soil_box
which defines one of the surfaces of the #fractal_box, a fractal dimension, and minimum and maximum values for the height of the roughness (relative to the original #fractal_box volume). In this example the roughness will be stochastically distributed with troughs up to 5mm deep, and peaks up to 10mm high.
More information, including adding surface water and vegetation, can be found in the section on using the fractal box command.
Using subgrid(s)
Including finely detailed objects or regions of high dielectric strength in FDTD modeling can dramatically increase the computational burden of the method. This is because the conditionally stable nature of the algorithm requires a minimum time step for a given spatial discretization. Thus, when the spatial discretization is lowered, either to reduce numerical dispersion or include small-sized features, the time step must be reduced. Also, the number of spatial cells is increased. One approach to reducing the overall computational cost is to introduce local finely discretized regions into a coarser finite-difference grid. This approach is known as subgridding. The computing time is reduced since there are fewer cells to solve. Also, there are fewer iterations since the coarse time step is maintained in the coarse region. gprMax uses a new Huygens subgridding (HSG) algorithm with a novel artificial loss mechanism called the switched Huygens subgridding (SHSG). For a detailed description of subgridding and the SHSG method please read [HAR2021].
Subgridding functionality requires using our Python API.
High dielectric example
This example is a basic demonstration of how to use subgrids. The geometry is 3D (required for any use of subgrids) and is of a water-filled (high dielectric constant) cylindrical object in freespace. The subgrid encloses the cylindrical object using a fine spatial discretisation (1mm), and a courser spatial discretisation (5mm) is used in the rest of the model (main grid). A simple Hertzian dipole source is used with a waveform shaped as the first derivative of a gaussian.
Fig. 35 The geometry of a 3D model of a water cylinder (meshed using a subgrid) in free space.
1"""Cylinder in freespace
2
3This example model demonstrates how to use subgrids at a basic level.
4
5The geometry is 3D (required for any use of subgrids) and is of a water-filled
6cylindrical object in freespace. The subgrid encloses the cylinderical object
7using a fine spatial discretisation (1mm), and a courser spatial discretisation
8(5mm) is used in the rest of the model (main grid). A simple Hertzian dipole
9source is used with a waveform shaped as the first derivative of a gaussian.
10"""
11
12from pathlib import Path
13
14import gprMax
15from gprMax.materials import calculate_water_properties
16
17# File path - used later to specify name of output files
18fn = Path(__file__)
19parts = fn.parts
20
21# Subgrid spatial discretisation in x, y, z directions
22dl_sg = 1e-3
23
24# Subgrid ratio - must always be an odd integer multiple
25ratio = 5
26dl = dl_sg * ratio
27
28# Domain extent
29x = 0.500
30y = 0.500
31z = 0.500
32
33# Time window
34tw = 6e-9
35
36scene = gprMax.Scene()
37
38title = gprMax.Title(name=fn.name)
39dxdydz = gprMax.Discretisation(p1=(dl, dl, dl))
40domain = gprMax.Domain(p1=(x, y, z))
41time_window = gprMax.TimeWindow(time=tw)
42
43wf = gprMax.Waveform(wave_type="gaussiandot", amp=1, freq=1.5e9, id="mypulse")
44hd = gprMax.HertzianDipole(polarisation="z", p1=(0.205, 0.400, 0.250), waveform_id="mypulse")
45rx = gprMax.Rx(p1=(0.245, 0.400, 0.250))
46
47scene.add(title)
48scene.add(dxdydz)
49scene.add(domain)
50scene.add(time_window)
51scene.add(wf)
52scene.add(hd)
53scene.add(rx)
54
55# Cylinder parameters
56c1 = (0.225, 0.250, 0.100)
57c2 = (0.225, 0.250, 0.400)
58r = 0.010
59sg1 = (c1[0] - r, c1[1] - r, c1[2])
60sg2 = (c2[0] + r, c2[1] + r, c2[2])
61
62# Create subgrid
63subgrid = gprMax.SubGridHSG(p1=sg1, p2=sg2, ratio=ratio, id="sg")
64scene.add(subgrid)
65
66# Create water material
67eri, er, tau, sig = calculate_water_properties()
68water = gprMax.Material(er=eri, se=sig, mr=1, sm=0, id="water")
69subgrid.add(water)
70water = gprMax.AddDebyeDispersion(poles=1, er_delta=[er - eri], tau=[tau], material_ids=["water"])
71subgrid.add(water)
72
73# Add cylinder to subgrid
74cylinder = gprMax.Cylinder(p1=c1, p2=c2, r=r, material_id="water")
75subgrid.add(cylinder)
76
77# Create some geometry views for both subgrid and main grid
78gvsg = gprMax.GeometryView(
79 p1=sg1,
80 p2=sg2,
81 dl=(dl_sg, dl_sg, dl_sg),
82 filename=fn.with_suffix("").parts[-1] + "_sg",
83 output_type="n",
84)
85subgrid.add(gvsg)
86
87gv1 = gprMax.GeometryView(
88 p1=(0, 0, 0),
89 p2=(x, y, z),
90 dl=(dl, dl, dl),
91 filename=fn.with_suffix("").parts[-1],
92 output_type="n",
93)
94scene.add(gv1)
95
96# Create some snapshots of entire domain
97for i in range(5):
98 s = gprMax.Snapshot(
99 p1=(0, 0, 0),
100 p2=(x, y, z),
101 dl=(dl, dl, dl),
102 time=(i + 0.5) * 1e-9,
103 filename=fn.with_suffix("").parts[-1] + "_" + str(i + 1),
104 )
105 scene.add(s)
106
107gprMax.run(
108 scenes=[scene], n=1, geometry_only=False, outputfile=fn, subgrid=True, autotranslate=True
109)
Much of the functionality demonstrated in this example is standard use of our Python API, so mainly the parts that relate to the subgrid will be described here. Lines 20-25 specify the spatial discretisation of the course main grid (5mm) and fine subgrid (1mm). Lines 56-60 specify the centres and radius of the cylinder and coordinates of a bounding box which will be used to set the domain of the subgrid.
The subgrid object is created on line 63 (providing its extent, the ratio of the spatial resolution, and a string identifier) and then added to the main scene on line 64. Any objects that are to be placed within the subgrid can be added to the subgrid scene (through the variable subgrid) in the same way as the main grid/scene.
In lines 67-71 the material used to represent water is created and added to the subgrid. The function calculate_water_properties() is used to help define the properties of water which is represented as a dispersive material using a single pole Debye model.
Lines 74-75 define a cylinder object with the material water that we just created, and then add it to the subgrid.
On lines 78-81 a view of the subgrid geometry is added to the subgrid object.
Finally, on line 95 when the model is run the keyword arguments subgrid and autotranslate are given and set to True. The subgrid argument tells gprMax that subgrids are being used, and the autotranslate argument allows the user to specify subgrid objects using main grid coordinates which will then be internally translated to local subgrid coordinates. Without using this option the user would have to specify subgrid objects in local subgrid coordinates.
Antenna modelling example
gssi_400_over_fractal_subsurface.py
This example combines an imported GPR antenna model (like a GSSI 400 MHz antenna) with a rough subsurface interface. The three-dimensional model has two soil layers: sandy soil above a higher-permittivity soil, both with conductive loss. The antenna sits on the flat ground surface at a height of 1.53 m above the model origin; the rough interface is below it.
The antenna uses its supported 2 mm spatial discretisation, preserving the contributor’s geometry and feed design. The main grid uses a 10 mm spatial discretisation, giving an odd subgrid ratio of 5. Do not remove the antenna model’s resolution check to run it on a different mesh.
The time window is chosen for the time-domain GPR response. If the automatic transmitter-port output warns about a significant late-time tail, extend the time window before using its impedance or S11 spectra; the fields need more time to decay for reliable frequency-domain processing.
Fig. 36 Illustration of the antenna and two-layered subsurface from an earlier configuration. The downloadable example now uses a 2 mm / 10 mm mesh.
Fig. 37 Detail from the earlier 1 mm / 9 mm configuration, illustrating the subgrid placement. The current antenna example uses 2 mm / 10 mm.
1"""GPR antenna model (like a GSSI 400MHz antenna) over layered media with a
2 rough subsurface interface.
3
4This example model demonstrates how to use subgrids at a more advanced level -
5 combining use of an imported antenna model and rough subsurface interface.
6
7The geometry is 3D (required for any use of subgrids) and has a two-layered
8subsurface. The top layer is a sandy soil and the bottom layer a soil with
9higher permittivity (both have some simple conductive loss). There is a rough
10interface between the soil layers. A GPR antenna model (like a GSSI 400MHz
11antenna) is imported and placed on the surface of the layered media. The antenna
12is meshed using its supported 2 mm spatial discretisation, preserving the
13contributor's antenna design. A coarser 10 mm spatial discretisation is used
14in the rest of the model (main grid), giving a subgrid ratio of 5.
15"""
16
17from pathlib import Path
18
19import numpy as np
20
21import gprMax
22from gprMax.toolboxes.GPRAntennaModels.GSSI import antenna_like_GSSI_400
23
24# File path - used later to specify name of output files
25fn = Path(__file__)
26parts = fn.parts
27
28# Preserve the antenna model's supported 2 mm spatial discretisation.
29dl_sg = 2e-3
30
31# Subgrid ratio - must always be an odd integer multiple
32ratio = 5
33dl = dl_sg * ratio
34
35# Domain extent
36x = 3
37y = 1
38z = 2
39
40# Time window
41# Estimated two way travel time over 1 metre in material with highest
42# permittivity, slowest velocity.
43tw = 2 / 3e8 * (np.sqrt(3.2) + np.sqrt(9))
44
45scene = gprMax.Scene()
46
47title = gprMax.Title(name=fn.name)
48dxdydz = gprMax.Discretisation(p1=(dl, dl, dl))
49domain = gprMax.Domain(p1=(x, y, z))
50time_window = gprMax.TimeWindow(time=tw)
51
52scene.add(title)
53scene.add(dxdydz)
54scene.add(domain)
55scene.add(time_window)
56
57# Dimensions of antenna case
58antenna_case = (0.3, 0.3, 0.178)
59
60# Keep the original physical soil-surface height, independent of mesh spacing.
61# The antenna position is aligned with both the main grid and the subgrid.
62antenna_p = (x / 2, y / 2, 1.53)
63
64# Extra distance surrounding antenna for subgrid
65bounding_box = 2 * dl
66
67# Subgrid extent
68sg_x0 = antenna_p[0] - antenna_case[0] / 2 - bounding_box
69sg_y0 = antenna_p[1] - antenna_case[1] / 2 - bounding_box
70sg_z0 = antenna_p[2] - bounding_box
71sg_x1 = antenna_p[0] + antenna_case[0] / 2 + bounding_box
72sg_y1 = antenna_p[1] + antenna_case[1] / 2 + bounding_box
73# Round the antenna height up to whole main-grid cells so all inner-surface
74# bounds are main-grid aligned before calculating local subgrid coordinates.
75sg_z1 = antenna_p[2] + np.ceil(antenna_case[2] / dl) * dl + bounding_box
76
77# Create subgrid
78sg = gprMax.SubGridHSG(p1=[sg_x0, sg_y0, sg_z0], p2=[sg_x1, sg_y1, sg_z1], ratio=ratio, id="sg")
79scene.add(sg)
80
81# Create and add a box of homogeneous material to main grid - sandy_soil
82sandy_soil = gprMax.Material(er=3.2, se=0.397e-3, mr=1, sm=0, id="sandy_soil")
83scene.add(sandy_soil)
84b1 = gprMax.Box(p1=(0, 0, 0), p2=(x, y, antenna_p[2]), material_id="sandy_soil")
85scene.add(b1)
86
87# Position box of sandy_soil in the subgrid.
88# It has to be positioned manually because it traverses the main grid/subgrid
89# interface. Grid traversal is when objects extend beyond the outer surface.
90# Setting autotranslate to false allows you to place objects beyond the outer
91# surface.
92
93# Use the actual subgrid settings, including the PML and coupling padding.
94ps = sg.kwargs["pml_separation"]
95pc = sg.kwargs["subgrid_pml_thickness"]
96isos = sg.kwargs["is_os_sep"] * ratio
97padding = (ps + pc + isos) * dl_sg
98
99# Cover the complete local grid, not just the region inside the inner surface.
100soil_x = sg_x1 - sg_x0 + 2 * padding
101soil_y = sg_y1 - sg_y0 + 2 * padding
102
103# Calculate maximum z-coordinate (height) for box of sandy_soil in subgrid
104h = antenna_p[2] - sg_z0 + padding
105
106# Create and add a box of homogeneous material to subgrid - sandy_soil
107sg.add(sandy_soil)
108b2 = gprMax.Box(p1=(0, 0, 0), p2=(soil_x, soil_y, h), material_id="sandy_soil")
109# Set autotranslate for the box object to false
110b2.autotranslate = False
111sg.add(b2)
112
113# Import antenna model and add components to subgrid
114gssi_objects = antenna_like_GSSI_400(*antenna_p, resolution=dl_sg)
115for obj in gssi_objects:
116 sg.add(obj)
117
118# Create and add a homogeneous material with a rough surface
119soil = gprMax.Material(er=9, se=0.397e-3, mr=1, sm=0, id="soil")
120scene.add(soil)
121
122fb = gprMax.FractalBox(
123 p1=(0, 0, 0),
124 p2=(3, 1, 1),
125 frac_dim=1.5,
126 weighting=(1, 1, 1),
127 n_materials=1,
128 mixing_model_id="soil",
129 id="fbox",
130 seed=1,
131)
132scene.add(fb)
133
134rough_surf = gprMax.AddSurfaceRoughness(
135 p1=(0, 0, 1),
136 p2=(3, 1, 1),
137 frac_dim=1.5,
138 weighting=(1, 1),
139 limits=(0.4, 1.2),
140 fractal_box_id="fbox",
141 seed=1,
142)
143scene.add(rough_surf)
144
145# Create some snapshots and geometry views
146for i in range(1, 51):
147 snap = gprMax.Snapshot(
148 p1=(0, y / 2, 0),
149 p2=(x, y / 2 + dl, z),
150 dl=(dl, dl, dl),
151 filename=Path(*parts[:-1], f"{parts[-1]}_{str(i)}").name,
152 time=i * tw / 50,
153 )
154 scene.add(snap)
155
156gvsg = gprMax.GeometryView(
157 p1=(sg_x0, sg_y0, sg_z0),
158 p2=(sg_x1, sg_y1, sg_z1),
159 dl=(dl_sg, dl_sg, dl_sg),
160 filename=fn.with_suffix("").parts[-1] + "_sg",
161 output_type="n",
162)
163sg.add(gvsg)
164
165gv1 = gprMax.GeometryView(
166 p1=(0, 0, 0), p2=(x, y, z), dl=(dl, dl, dl), filename=fn.with_suffix("").parts[-1], output_type="n"
167)
168scene.add(gv1)
169
170gprMax.run(
171 scenes=[scene], n=1, geometry_only=False, outputfile=fn, subgrid=True, autotranslate=True
172)
Much of the functionality demonstrated in this example is standard use of our Python API, or covered in the introductory subgrid example earlier in this section.
The sandy-soil box inside the subgrid uses local coordinates because it
crosses the interface with the main grid. Its autotranslate property is
set to False. Its extent includes the coupling and PML padding, calculated
from the subgrid settings rather than a fixed number of cells. The soil
surface is at the same physical height in both grids. The subgrid bounds
are aligned with the main grid, with the upper bound rounded upwards to
contain the complete antenna.
Customising the PMLs
Through our Python API there is the ability to customise and adjust the formulation and properties used for the Perfectly Matched Layer (PML) absorbing boundaries.
Note
If you just want to adjust the thickness of the PMLs and not use our Python API, that can be achieved using the
#pml_cellscommand.
This example demonstrates how different formulations of PML and PML parameters can be adjusted and used.
The model is of an elongated-thin PEC plate (25 x 100 mm). The y-directed electric field (Ey) is monitored one cell away from the plate, and a z-directed Hertzian dipole source is placed diagonally opposite the field monitoring point and at 1 mm above one of the PEC sheet corner. Only three cells of free space separate the plate target from the inner surface of the PMLs.
The performance of each PML can be compared with a reference solution using the same model with a substantially larger domain.
1from pathlib import Path
2
3import numpy as np
4
5import gprMax
6
7# File path for output
8fn = Path(__file__)
9parts = fn.parts
10
11# Discretisation
12dl = 0.001
13
14# Domain
15x = 0.051
16y = 0.126
17z = 0.026
18
19domain = gprMax.Domain(p1=(x, y, z))
20dxdydz = gprMax.Discretisation(p1=(dl, dl, dl))
21time_window = gprMax.TimeWindow(iterations=2100)
22tssf = gprMax.TimeStepStabilityFactor(f=0.99)
23
24waveform = gprMax.Waveform(wave_type="gaussiandotnorm", amp=1, freq=9.42e9, id="mypulse")
25hertzian_dipole = gprMax.HertzianDipole(
26 polarisation="z", p1=(0.013, 0.013, 0.014), waveform_id="mypulse"
27)
28rx = gprMax.Rx(p1=(0.038, 0.114, 0.013))
29
30plate = gprMax.Plate(p1=(0.013, 0.013, 0.013), p2=(0.038, 0.113, 0.013), material_id="pec")
31
32gv1 = gprMax.GeometryView(
33 p1=(0, 0, 0),
34 p2=(x, y, z),
35 dl=(dl, dl, dl),
36 filename=Path(*parts[:-1], f"{parts[-1]}_n"),
37 output_type="n",
38)
39gv2 = gprMax.GeometryView(
40 p1=(0, 0, 0),
41 p2=(x, y, z),
42 dl=(dl, dl, dl),
43 filename=Path(*parts[:-1], f"{parts[-1]}_f"),
44 output_type="f",
45)
46
47pmls = {
48 "CFS-PML": {
49 "pml": gprMax.PMLProps(formulation="HORIPML", thickness=10),
50 # Parameters from http://dx.doi.org/10.1109/TAP.2018.2823864
51 "pml_cfs": [
52 gprMax.PMLCFS(
53 alphascalingprofile="constant",
54 alphascalingdirection="forward",
55 alphamin=0.05,
56 alphamax=0.05,
57 kappascalingprofile="quartic",
58 kappascalingdirection="forward",
59 kappamin=1,
60 kappamax=8,
61 sigmascalingprofile="quartic",
62 sigmascalingdirection="forward",
63 sigmamin=0,
64 sigmamax=1.1 * ((4 + 1) / (150 * np.pi * dl)),
65 )
66 ],
67 },
68 "HORIPML-1": {
69 "pml": gprMax.PMLProps(formulation="HORIPML", thickness=10),
70 # Parameters from http://dx.doi.org/10.1109/TAP.2011.2180344
71 "pml_cfs": [
72 gprMax.PMLCFS(
73 alphascalingprofile="constant",
74 alphascalingdirection="forward",
75 alphamin=0,
76 alphamax=0,
77 kappascalingprofile="quartic",
78 kappascalingdirection="forward",
79 kappamin=1,
80 kappamax=12,
81 sigmascalingprofile="quartic",
82 sigmascalingdirection="forward",
83 sigmamin=0,
84 sigmamax=0.7 * ((4 + 1) / (150 * np.pi * dl)),
85 )
86 ],
87 },
88 "HORIPML-2": {
89 "pml": gprMax.PMLProps(formulation="HORIPML", thickness=10),
90 # Parameters from http://dx.doi.org/10.1109/TAP.2018.2823864
91 "pml_cfs": [
92 gprMax.PMLCFS(
93 alphascalingprofile="constant",
94 alphascalingdirection="forward",
95 alphamin=0,
96 alphamax=0,
97 kappascalingprofile="constant",
98 kappascalingdirection="forward",
99 kappamin=1,
100 kappamax=1,
101 sigmascalingprofile="sextic",
102 sigmascalingdirection="forward",
103 sigmamin=0,
104 sigmamax=0.275 / (150 * np.pi * dl),
105 ),
106 gprMax.PMLCFS(
107 alphascalingprofile="sextic",
108 alphascalingdirection="forward",
109 alphamin=0.07,
110 alphamax=0.07 + (0.275 / (150 * np.pi * dl)),
111 kappascalingprofile="cubic",
112 kappascalingdirection="forward",
113 kappamin=1,
114 kappamax=8,
115 sigmascalingprofile="quadratic",
116 sigmascalingdirection="forward",
117 sigmamin=0,
118 sigmamax=2.75 / (150 * np.pi * dl),
119 ),
120 ],
121 },
122 "MRIPML-1": {
123 "pml": gprMax.PMLProps(formulation="MRIPML", thickness=10),
124 # Parameters from Antonis' MATLAB script (M3Dparams.m)
125 "pml_cfs": [
126 gprMax.PMLCFS(
127 alphascalingprofile="constant",
128 alphascalingdirection="forward",
129 alphamin=0.05,
130 alphamax=0.05,
131 kappascalingprofile="quartic",
132 kappascalingdirection="forward",
133 kappamin=1,
134 kappamax=8,
135 sigmascalingprofile="quartic",
136 sigmascalingdirection="forward",
137 sigmamin=0,
138 sigmamax=1.1 * ((4 + 1) / (150 * np.pi * dl)),
139 )
140 ],
141 },
142 "MRIPML-2": {
143 "pml": gprMax.PMLProps(formulation="MRIPML", thickness=10),
144 # Parameters from http://dx.doi.org/10.1109/TAP.2018.2823864
145 "pml_cfs": [
146 gprMax.PMLCFS(
147 alphascalingprofile="quadratic",
148 alphascalingdirection="reverse",
149 alphamin=0,
150 alphamax=0.15,
151 kappascalingprofile="quartic",
152 kappascalingdirection="forward",
153 kappamin=1,
154 kappamax=12,
155 sigmascalingprofile="quartic",
156 sigmascalingdirection="forward",
157 sigmamin=0,
158 sigmamax=0.65 * ((4 + 1) / (150 * np.pi * dl)),
159 ),
160 gprMax.PMLCFS(
161 alphascalingprofile="linear",
162 alphascalingdirection="reverse",
163 alphamin=0.07,
164 alphamax=0.8,
165 kappascalingprofile="constant",
166 kappascalingdirection="forward",
167 kappamin=0,
168 kappamax=0,
169 sigmascalingprofile="quadratic",
170 sigmascalingdirection="forward",
171 sigmamin=0,
172 sigmamax=0.65 * ((2 + 1) / (150 * np.pi * dl)),
173 ),
174 ],
175 },
176}
177
178scenes = []
179for k, v in pmls.items():
180 scene = gprMax.Scene()
181 title = gprMax.Title(name=fn.with_suffix("").name + "_" + k)
182 scene.add(title)
183 scene.add(domain)
184 scene.add(dxdydz)
185 scene.add(time_window)
186 scene.add(tssf)
187 scene.add(waveform)
188 scene.add(hertzian_dipole)
189 scene.add(rx)
190 # scene.add(gv1)
191 # scene.add(gv2)
192
193 scene.add(v["pml"])
194 for pml_cfs in v["pml_cfs"]:
195 scene.add(pml_cfs)
196
197 scenes.append(scene)
198
199# Run model
200gprMax.run(scenes=scenes, n=len(pmls), geometry_only=False, outputfile=fn)
In lines 43-122 a dictionary with different PML formulations and parameters is created.