Further changes/updates towards getting sub-gridding example with GSSI 400 working.

这个提交包含在:
Craig Warren
2019-10-28 17:08:06 +00:00
父节点 67482810b6
当前提交 6ed79e46e5
共有 18 个文件被更改,包括 290 次插入197 次删除

查看文件

@@ -73,7 +73,8 @@ class FractalBoxBuilder(UserObjectGeometry):
# If there is only 1 bin then a normal material is being used, otherwise a mixing model # If there is only 1 bin then a normal material is being used, otherwise a mixing model
if volume.nbins == 1: if volume.nbins == 1:
volume.fractalvolume = np.ones((volume.nx, volume.ny, volume.nz), dtype=config.sim_config.dtype['float_or_double']) volume.fractalvolume = np.ones((volume.nx, volume.ny, volume.nz),
dtype=config.sim_config.dtypes['float_or_double'])
materialnumID = next(x.numID for x in grid.materials if x.ID == volume.operatingonID) materialnumID = next(x.numID for x in grid.materials if x.ID == volume.operatingonID)
volume.fractalvolume *= materialnumID volume.fractalvolume *= materialnumID
else: else:
@@ -115,7 +116,8 @@ class FractalBoxBuilder(UserObjectGeometry):
if surface.fractalsurface[j - surface.ys, k - surface.zs] > 0: if surface.fractalsurface[j - surface.ys, k - surface.zs] > 0:
height = 0 height = 0
for i in range(volume.xs, surface.fractalrange[1]): for i in range(volume.xs, surface.fractalrange[1]):
if i < surface.fractalsurface[j - surface.ys, k - surface.zs] and volume.mask[i - volume.xs, j - volume.ys, k - volume.zs] != 1: if (i < surface.fractalsurface[j - surface.ys, k - surface.zs] and
volume.mask[i - volume.xs, j - volume.ys, k - volume.zs] != 1):
y, z = g.calculate_blade_geometry(blade, height) y, z = g.calculate_blade_geometry(blade, height)
# Add y, z coordinates to existing location # Add y, z coordinates to existing location
yy = int(j - volume.ys + y) yy = int(j - volume.ys + y)
@@ -136,7 +138,8 @@ class FractalBoxBuilder(UserObjectGeometry):
depth = 0 depth = 0
i = volume.xf - 1 i = volume.xf - 1
while i > volume.xs: while i > volume.xs:
if i > volume.originalxf - (surface.fractalsurface[j - surface.ys, k - surface.zs] - volume.originalxf) and volume.mask[i - volume.xs, j - volume.ys, k - volume.zs] == 1: if (i > volume.originalxf - (surface.fractalsurface[j - surface.ys, k - surface.zs] -
volume.originalxf) and volume.mask[i - volume.xs, j - volume.ys, k - volume.zs] == 1):
y, z = g.calculate_root_geometry(root, depth) y, z = g.calculate_root_geometry(root, depth)
# Add y, z coordinates to existing location # Add y, z coordinates to existing location
yy = int(j - volume.ys + y) yy = int(j - volume.ys + y)
@@ -181,7 +184,8 @@ class FractalBoxBuilder(UserObjectGeometry):
if surface.fractalsurface[i - surface.xs, k - surface.zs] > 0: if surface.fractalsurface[i - surface.xs, k - surface.zs] > 0:
height = 0 height = 0
for j in range(volume.ys, surface.fractalrange[1]): for j in range(volume.ys, surface.fractalrange[1]):
if j < surface.fractalsurface[i - surface.xs, k - surface.zs] and volume.mask[i - volume.xs, j - volume.ys, k - volume.zs] != 1: if (j < surface.fractalsurface[i - surface.xs, k - surface.zs] and
volume.mask[i - volume.xs, j - volume.ys, k - volume.zs] != 1):
x, z = g.calculate_blade_geometry(blade, height) x, z = g.calculate_blade_geometry(blade, height)
# Add x, z coordinates to existing location # Add x, z coordinates to existing location
xx = int(i - volume.xs + x) xx = int(i - volume.xs + x)
@@ -202,7 +206,8 @@ class FractalBoxBuilder(UserObjectGeometry):
depth = 0 depth = 0
j = volume.yf - 1 j = volume.yf - 1
while j > volume.ys: while j > volume.ys:
if j > volume.originalyf - (surface.fractalsurface[i - surface.xs, k - surface.zs] - volume.originalyf) and volume.mask[i - volume.xs, j - volume.ys, k - volume.zs] == 1: if (j > volume.originalyf - (surface.fractalsurface[i - surface.xs, k - surface.zs] -
volume.originalyf) and volume.mask[i - volume.xs, j - volume.ys, k - volume.zs] == 1):
x, z = g.calculate_root_geometry(root, depth) x, z = g.calculate_root_geometry(root, depth)
# Add x, z coordinates to existing location # Add x, z coordinates to existing location
xx = int(i - volume.xs + x) xx = int(i - volume.xs + x)
@@ -247,7 +252,8 @@ class FractalBoxBuilder(UserObjectGeometry):
if surface.fractalsurface[i - surface.xs, j - surface.ys] > 0: if surface.fractalsurface[i - surface.xs, j - surface.ys] > 0:
height = 0 height = 0
for k in range(volume.zs, surface.fractalrange[1]): for k in range(volume.zs, surface.fractalrange[1]):
if k < surface.fractalsurface[i - surface.xs, j - surface.ys] and volume.mask[i - volume.xs, j - volume.ys, k - volume.zs] != 1: if (k < surface.fractalsurface[i - surface.xs, j - surface.ys] and
volume.mask[i - volume.xs, j - volume.ys, k - volume.zs] != 1):
x, y = g.calculate_blade_geometry(blade, height) x, y = g.calculate_blade_geometry(blade, height)
# Add x, y coordinates to existing location # Add x, y coordinates to existing location
xx = int(i - volume.xs + x) xx = int(i - volume.xs + x)
@@ -268,7 +274,8 @@ class FractalBoxBuilder(UserObjectGeometry):
depth = 0 depth = 0
k = volume.zf - 1 k = volume.zf - 1
while k > volume.zs: while k > volume.zs:
if k > volume.originalzf - (surface.fractalsurface[i - surface.xs, j - surface.ys] - volume.originalzf) and volume.mask[i - volume.xs, j - volume.ys, k - volume.zs] == 1: if (k > volume.originalzf - (surface.fractalsurface[i - surface.xs, j - surface.ys] -
volume.originalzf) and volume.mask[i - volume.xs, j - volume.ys, k - volume.zs] == 1):
x, y = g.calculate_root_geometry(root, depth) x, y = g.calculate_root_geometry(root, depth)
# Add x, y coordinates to existing location # Add x, y coordinates to existing location
xx = int(i - volume.xs + x) xx = int(i - volume.xs + x)
@@ -287,7 +294,10 @@ class FractalBoxBuilder(UserObjectGeometry):
grassnumID = next((x.numID for x in grid.materials if x.ID == 'grass'), 0) grassnumID = next((x.numID for x in grid.materials if x.ID == 'grass'), 0)
data = volume.fractalvolume.astype('int16', order='C') data = volume.fractalvolume.astype('int16', order='C')
mask = volume.mask.copy(order='C') mask = volume.mask.copy(order='C')
build_voxels_from_array_mask(volume.xs, volume.ys, volume.zs, waternumID, grassnumID, volume.averaging, mask, data, grid.solid, grid.rigidE, grid.rigidH, grid.ID) build_voxels_from_array_mask(volume.xs, volume.ys, volume.zs,
waternumID, grassnumID, volume.averaging,
mask, data, grid.solid, grid.rigidE,
grid.rigidH, grid.ID)
else: else:
if volume.nbins == 1: if volume.nbins == 1:
@@ -297,4 +307,6 @@ class FractalBoxBuilder(UserObjectGeometry):
volume.fractalvolume += volume.mixingmodel.startmaterialnum volume.fractalvolume += volume.mixingmodel.startmaterialnum
data = volume.fractalvolume.astype('int16', order='C') data = volume.fractalvolume.astype('int16', order='C')
build_voxels_from_array(volume.xs, volume.ys, volume.zs, 0, volume.averaging, data, grid.solid, grid.rigidE, grid.rigidH, grid.ID) build_voxels_from_array(volume.xs, volume.ys, volume.zs, 0,
volume.averaging, data, grid.solid,
grid.rigidE, grid.rigidH, grid.ID)

查看文件

@@ -26,6 +26,7 @@ from .cmds_geometry.cmds_geometry import UserObjectGeometry
from .exceptions import CmdInputError from .exceptions import CmdInputError
from .geometry_outputs import GeometryObjects as GeometryObjectsUser from .geometry_outputs import GeometryObjects as GeometryObjectsUser
from .materials import Material as MaterialUser from .materials import Material as MaterialUser
from .materials import DispersiveMaterial as DispersiveMaterialUser
from .materials import PeplinskiSoil as PeplinskiSoilUser from .materials import PeplinskiSoil as PeplinskiSoilUser
from .pml import CFSParameter from .pml import CFSParameter
from .pml import CFS from .pml import CFS
@@ -508,7 +509,7 @@ class Rx(UserObjectMulti):
try: try:
r.ID = self.kwargs['id'] r.ID = self.kwargs['id']
outputs = self.kwargs['outputs'] outputs = [self.kwargs['outputs']]
# Get allowable outputs # Get allowable outputs
allowableoutputs = RxUser.allowableoutputs_gpu if config.sim_config.general['cuda'] else RxUser.allowableoutputs allowableoutputs = RxUser.allowableoutputs_gpu if config.sim_config.general['cuda'] else RxUser.allowableoutputs
# Check and add field output names # Check and add field output names
@@ -767,20 +768,28 @@ class AddDebyeDispersion(UserObjectMulti):
raise CmdInputError(f"'{self.params_str()}' material(s) {notfound} do not exist") raise CmdInputError(f"'{self.params_str()}' material(s) {notfound} do not exist")
for material in materials: for material in materials:
material.type = 'debye' disp_material = DispersiveMaterialUser(material.numID, material.ID)
material.poles = poles disp_material.er = material.er
material.averagable = False disp_material.se = material.se
disp_material.mr = material.mr
disp_material.sm = material.sm
disp_material.type = 'debye'
disp_material.poles = poles
disp_material.averagable = False
for i in range(0, poles): for i in range(0, poles):
# N.B Not checking if relaxation times are greater than time-step
if tau[i] > 0: if tau[i] > 0:
material.deltaer.append(er_delta[i]) log.debug('Not checking if relaxation times are greater than time-step')
material.tau.append(tau[i]) disp_material.deltaer.append(er_delta[i])
disp_material.tau.append(tau[i])
else: else:
raise CmdInputError(f"'{self.params_str()}' requires positive values for the permittivity difference.") raise CmdInputError(f"'{self.params_str()}' requires positive values for the permittivity difference.")
if material.poles > MaterialUser.maxpoles: if disp_material.poles > config.model_configs[grid.model_num].materials['maxpoles']:
MaterialUser.maxpoles = material.poles config.model_configs[grid.model_num].materials['maxpoles'] = disp_material.poles
log.info(f"Debye disperion added to {material.ID} with delta_eps_r={', '.join('%4.2f' % deltaer for deltaer in material.deltaer)}, and tau={', '.join('%4.3e' % tau for tau in material.tau)} secs created.") # Replace original material with newly created DispersiveMaterial
grid.materials = [disp_material if mat.numID==material.numID else mat for mat in grid.materials]
log.info(f"Debye disperion added to {disp_material.ID} with delta_eps_r={', '.join('%4.2f' % deltaer for deltaer in disp_material.deltaer)}, and tau={', '.join('%4.3e' % tau for tau in disp_material.tau)} secs created.")
class AddLorentzDispersion(UserObjectMulti): class AddLorentzDispersion(UserObjectMulti):
@@ -1028,7 +1037,7 @@ class GeometryView(UserObjectMulti):
g = GeometryViewUser(xs, ys, zs, xf, yf, zf, dx, dy, dz, filename, fileext, grid) g = GeometryViewUser(xs, ys, zs, xf, yf, zf, dx, dy, dz, filename, fileext, grid)
log.info(f'Geometry view from {xs * grid.dx:g}m, {ys * grid.dy:g}m, {zs * grid.dz:g}m, to {xf * grid.dx:g}m, {yf * grid.dy:g}m, {zf * grid.dz:g}m, discretisation {dx * grid.dx:g}m, {dy * grid.dy:g}m, {dz * grid.dz:g}m, multi_grid={self.multi_grid}, grid={grid.name}, with filename base {g.basefilename} created.') log.info(f'Geometry view from {xs * grid.dx:g}m, {ys * grid.dy:g}m, {zs * grid.dz:g}m, to {xf * grid.dx:g}m, {yf * grid.dy:g}m, {zf * grid.dz:g}m, discretisation {dx * grid.dx:g}m, {dy * grid.dy:g}m, {dz * grid.dz:g}m, multi_grid={self.multi_grid}, grid={grid.name}, with filename base {g.filename} created.')
# Append the new GeometryView object to the geometry views list # Append the new GeometryView object to the geometry views list
grid.geometryviews.append(g) grid.geometryviews.append(g)

查看文件

@@ -187,7 +187,7 @@ class SimulationConfig:
'cuda': False, 'cuda': False,
'opencl': False, 'opencl': False,
'precision': 'single', 'precision': 'single',
'autotranslate': False} 'autotranslate': self.args.autotranslate}
log.debug('Should autotranslate be a ModelConfig parameter?') log.debug('Should autotranslate be a ModelConfig parameter?')
self.em_consts = {'c': c, # Speed of light in free space (m/s) self.em_consts = {'c': c, # Speed of light in free space (m/s)

查看文件

@@ -19,7 +19,6 @@
import logging import logging
import os import os
from pathlib import Path from pathlib import Path
import sys
import h5py import h5py
import numpy as np import numpy as np
@@ -61,7 +60,7 @@ class GeometryView:
self.dx = dx self.dx = dx
self.dy = dy self.dy = dy
self.dz = dz self.dz = dz
self.basefilename = filename self.filename = filename
self.fileext = fileext self.fileext = fileext
self.G = G self.G = G
@@ -89,19 +88,19 @@ class GeometryView:
self.vtk_nylines = self.ny * (self.nx + 1) * (self.nz + 1) self.vtk_nylines = self.ny * (self.nx + 1) * (self.nz + 1)
self.vtk_nzlines = self.nz * (self.nx + 1) * (self.ny + 1) self.vtk_nzlines = self.nz * (self.nx + 1) * (self.ny + 1)
self.vtk_numlines = self.vtk_nxlines + self.vtk_nylines + self.vtk_nzlines self.vtk_numlines = self.vtk_nxlines + self.vtk_nylines + self.vtk_nzlines
self.vtk_connectivity_offset = round_value((self.vtk_numpoints * self.vtk_connectivity_offset = ((self.vtk_numpoints *
self.vtk_numpoint_components * self.vtk_numpoint_components *
np.dtype(np.float32).itemsize) + np.dtype(np.float32).itemsize) +
np.dtype(np.uint32).itemsize) np.dtype(np.uint32).itemsize)
self.vtk_offsets_offset = round_value(self.vtk_connectivity_offset + self.vtk_offsets_offset = (self.vtk_connectivity_offset +
(self.vtk_numlines * (self.vtk_numlines *
self.vtk_numline_components * self.vtk_numline_components *
np.dtype(np.uint32).itemsize) + np.dtype(np.uint32).itemsize) +
np.dtype(np.uint32).itemsize) np.dtype(np.uint32).itemsize)
self.vtk_materials_offset = round_value(self.vtk_offsets_offset + self.vtk_materials_offset = (self.vtk_offsets_offset +
(self.vtk_numlines * (self.vtk_numlines *
np.dtype(np.uint32).itemsize) + np.dtype(np.uint32).itemsize) +
np.dtype(np.uint32).itemsize) np.dtype(np.uint32).itemsize)
vtk_cell_offsets = (((self.vtk_numline_components * self.vtk_numlines) + vtk_cell_offsets = (((self.vtk_numline_components * self.vtk_numlines) +
self.vtk_numline_components - self.vtk_numline_components - 1) // self.vtk_numline_components - self.vtk_numline_components - 1) //
self.vtk_numline_components + 1) self.vtk_numline_components + 1)
@@ -114,8 +113,8 @@ class GeometryView:
def set_filename(self): def set_filename(self):
"""Construct filename from user-supplied name and model run number.""" """Construct filename from user-supplied name and model run number."""
parts = config.model_configs[self.G.model_num].output_file_path.with_suffix('').parts parts = config.model_configs[self.G.model_num].output_file_path.parts
self.filename = Path(*parts[:-1], parts[-1]) self.filename = Path(*parts[:-1], self.filename + config.model_configs[self.G.model_num].appendmodelnumber)
self.filename = self.filename.with_suffix(self.fileext) self.filename = self.filename.with_suffix(self.fileext)
def write_vtk(self, G, pbar): def write_vtk(self, G, pbar):
@@ -142,19 +141,15 @@ class GeometryView:
for index, rx in enumerate(G.rxs): for index, rx in enumerate(G.rxs):
self.rxs[rx.xcoord, rx.ycoord, rx.zcoord] = index + 1 self.rxs[rx.xcoord, rx.ycoord, rx.zcoord] = index + 1
vtk_srcs_pml_offset = round_value((np.dtype(np.uint32).itemsize * vtk_srcs_pml_offset = ((np.dtype(np.uint32).itemsize * self.vtk_nxcells *
self.vtk_nxcells * self.vtk_nycells * self.vtk_nzcells) +
self.vtk_nycells * np.dtype(np.uint32).itemsize)
self.vtk_nzcells) + vtk_rxs_offset = ((np.dtype(np.uint32).itemsize * self.vtk_nxcells *
np.dtype(np.uint32).itemsize) self.vtk_nycells * self.vtk_nzcells) +
vtk_rxs_offset = round_value((np.dtype(np.uint32).itemsize * np.dtype(np.uint32).itemsize +
self.vtk_nxcells * self.vtk_nycells * (np.dtype(np.int8).itemsize * self.vtk_nxcells *
self.vtk_nzcells) + self.vtk_nycells * self.vtk_nzcells) +
np.dtype(np.uint32).itemsize + np.dtype(np.uint32).itemsize)
(np.dtype(np.int8).itemsize *
self.vtk_nxcells * self.vtk_nycells *
self.vtk_nzcells) +
np.dtype(np.uint32).itemsize)
with open(self.filename, 'wb') as f: with open(self.filename, 'wb') as f:
f.write('<?xml version="1.0"?>\n'.encode('utf-8')) f.write('<?xml version="1.0"?>\n'.encode('utf-8'))
@@ -428,7 +423,7 @@ class GeometryViewFineMultiGrid:
self.nx = G.nx self.nx = G.nx
self.ny = G.ny self.ny = G.ny
self.nz = G.nz self.nz = G.nz
self.basefilename = filename self.filename = filename
self.fileext = '.vtp' self.fileext = '.vtp'
self.sg_views = [] self.sg_views = []
@@ -455,8 +450,8 @@ class GeometryViewFineMultiGrid:
def set_filename(self): def set_filename(self):
"""Construct filename from user-supplied name and model run number.""" """Construct filename from user-supplied name and model run number."""
parts = config.model_configs[self.G.model_num].input_file_path.parts parts = config.model_configs[self.G.model_num].output_file_path.parts
self.filename = Path(*parts[:-1], parts[-1] + config.model_configs[self.G.model_num].appendmodelnumber) self.filename = Path(*parts[:-1], self.filename + config.model_configs[self.G.model_num].appendmodelnumber)
self.filename = self.filename.with_suffix(self.fileext) self.filename = self.filename.with_suffix(self.fileext)
def write_vtk(self, *args): def write_vtk(self, *args):

查看文件

@@ -37,6 +37,7 @@ def run(
mpicomm=None, mpicomm=None,
gpu=None, gpu=None,
subgrid=None, subgrid=None,
autotranslate=False,
geometry_only=False, geometry_only=False,
geometry_fixed=False, geometry_fixed=False,
write_processed=False, write_processed=False,
@@ -45,10 +46,13 @@ def run(
programming interface (API). Run the simulation for the programming interface (API). Run the simulation for the
given list of scenes. given list of scenes.
:param scenes: list of the scenes to run the model. Multiple scene objects can given in order to run multiple simulation runs. Each scene must contain the essential simulation objects :param scenes: list of the scenes to run the model. Multiple scene objects
can given in order to run multiple simulation runs. Each
scene must contain the essential simulation objects
:type scenes: list, optional :type scenes: list, optional
:param inputfile: input file path. Can also run simulation by providing an input file. :param inputfile: input file path. Can also run simulation by providing an
input file.
:type inputfile: str, optional :type inputfile: str, optional
:param outputfile: file path to the output data file. :param outputfile: file path to the output data file.
@@ -57,32 +61,54 @@ def run(
:param n: number of required simulation runs. :param n: number of required simulation runs.
:type n: int, non-optional :type n: int, non-optional
:param task: task identifier (model number) when running simulation as a job array on open grid scheduler/grid engine. for further details see the parallel performance section of the user guide :param task: task identifier (model number) when running simulation as a
job array on open grid scheduler/grid engine. For further
details see the parallel performance section of the User Guide.
:type task: int, optional :type task: int, optional
:param restart: model number to start/restart simulation from. It would typically be used to restart a series of models from a specific model number, with the n argument, e.g. to restart from A-scan 45 when creating a B-scan with 60 traces :param restart: model number to start/restart simulation from. It would
typically be used to restart a series of models from a
specific model number, with the n argument, e.g. to restart
from A-scan 45 when creating a B-scan with 60 traces.
:type restart: int, optional :type restart: int, optional
:param mpi: number of Message Passing Interface (MPI) tasks, i.e. master + workers, for MPI task farm. This option is most usefully combined with n to allow individual models to be farmed out using a MPI task farm, e.g. to create a B-scan with 60 traces and use MPI to farm out each trace1. For further details see the parallel performance section of the User Guide :param mpi: number of Message Passing Interface (MPI) tasks,
i.e. master + workers, for MPI task farm. This option is most
usefully combined with n to allow individual models to be farmed
out using a MPI task farm, e.g. to create a B-scan with 60 traces
and use MPI to farm out each trace1. For further details see the
parallel performance section of the User Guide.
:type mpi: int, optional :type mpi: int, optional
:param mpi_no_spawn: flag to use MPI task farm without spawn mechanism. For further details see the parallel performance section of the User Guide. :param mpi_no_spawn: flag to use MPI task farm without spawn mechanism.
For further details see the parallel performance
section of the User Guide.
:type mpi_no_spawn: bool, optional :type mpi_no_spawn: bool, optional
:param gpu: flag to use NVIDIA GPU or list of NVIDIA GPU device ID(s) for specific GPU card(s) :param gpu: flag to use NVIDIA GPU or list of NVIDIA GPU device ID(s) for
specific GPU card(s).
:type gpu: list or bool, optional :type gpu: list or bool, optional
:param subgrid: flag to use sub-gridding. :param subgrid: flag to use sub-gridding.
:type subgrid: bool, optional :type subgrid: bool, optional
:param geometry_only: build a model and produce any geometry views but do not run the simulation. :param autotranslate: for sub-gridding - auto translate objects with main grid
coordinates to their equivalent local grid coordinate
within the subgrid. If this option is off users must
specify sub-grid object point within the global
subgrid space.
:type autotranslate: bool, optional
:param geometry_only: build a model and produce any geometry views but do
not run the simulation.
:type geometry_only: bool, optional :type geometry_only: bool, optional
:param geometry_fixed: build a model and produce any geometry views but do not run the simulation. :param geometry_fixed: run a series of models where the geometry does not
change between models.
:type geometry_fixed: bool, optional :type geometry_fixed: bool, optional
:param write_processed: write another input file after any Python code and :param write_processed: write another input file after any Python code and
in the original input file has been processed. in the original input file has been processed.
:type write_processed: bool, optional :type write_processed: bool, optional
""" """
@@ -101,7 +127,8 @@ def run(
args.mpi_no_spawn = mpi_no_spawn args.mpi_no_spawn = mpi_no_spawn
args.mpicomm = mpicomm args.mpicomm = mpicomm
args.gpu = gpu args.gpu = gpu
args.subgrid=subgrid args.subgrid = subgrid
args.autotranslate = autotranslate
args.geometry_only = geometry_only args.geometry_only = geometry_only
args.geometry_fixed = geometry_fixed args.geometry_fixed = geometry_fixed
args.write_processed = write_processed args.write_processed = write_processed

查看文件

@@ -48,7 +48,7 @@ class FDTDGrid:
""" """
self.title = '' self.title = ''
self.name = 'Main' self.name = 'main_grid'
self.model_num = model_num self.model_num = model_num
self.nx = 0 self.nx = 0
@@ -346,7 +346,7 @@ class CUDAGrid(FDTDGrid):
def reset_fields(self): def reset_fields(self):
"""Clear arrays for field components and PMLs.""" """Clear arrays for field components and PMLs."""
super().reset_fields() super().reset_fields()
# Clear arrays for field components # Clear arrays for field components

查看文件

@@ -62,7 +62,7 @@ def process_python_include_code(inputfile, usernamespace):
# Process any Python code # Process any Python code
if(inputlines[x].startswith('#python:')): if(inputlines[x].startswith('#python:')):
log.warning('#python blocks are deprecated and will be removed in the next release of gprMax. Please convert your model to use our Python API instead.')
# String to hold Python code to be executed # String to hold Python code to be executed
pythoncode = '' pythoncode = ''
x += 1 x += 1

查看文件

@@ -209,16 +209,17 @@ class ModelBuildRun:
gb.build_materials() gb.build_materials()
# Check to see if numerical dispersion might be a problem # Check to see if numerical dispersion might be a problem
results = dispersion_analysis(G) for grid in grids:
if results['error']: results = dispersion_analysis(grid)
log.warning(Fore.RED + f"\nNumerical dispersion analysis not carried out as {results['error']}" + Style.RESET_ALL) if results['error']:
elif results['N'] < config.model_configs[G.model_num].numdispersion['mingridsampling']: log.warning(Fore.RED + f"\nNumerical dispersion analysis ({grid.name}) not carried out as {results['error']}" + Style.RESET_ALL)
raise GeneralError(f"Non-physical wave propagation: Material '{results['material'].ID}' has wavelength sampled by {results['N']} cells, less than required minimum for physical wave propagation. Maximum significant frequency estimated as {results['maxfreq']:g}Hz") elif results['N'] < config.model_configs[G.model_num].numdispersion['mingridsampling']:
elif (results['deltavp'] and np.abs(results['deltavp']) > raise GeneralError(f"\nNon-physical wave propagation in {grid.name} detected. Material '{results['material'].ID}' has wavelength sampled by {results['N']} cells, less than required minimum for physical wave propagation. Maximum significant frequency estimated as {results['maxfreq']:g}Hz")
config.model_configs[G.model_num].numdispersion['maxnumericaldisp']): elif (results['deltavp'] and np.abs(results['deltavp']) >
log.warning(Fore.RED + f"\nPotentially significant numerical dispersion. Estimated largest physical phase-velocity error is {results['deltavp']:.2f}% in material '{results['material'].ID}' whose wavelength sampled by {results['N']} cells. Maximum significant frequency estimated as {results['maxfreq']:g}Hz" + Style.RESET_ALL) config.model_configs[G.model_num].numdispersion['maxnumericaldisp']):
elif results['deltavp']: log.warning(Fore.RED + f"\n{grid.name} has potentially significant numerical dispersion. Estimated largest physical phase-velocity error is {results['deltavp']:.2f}% in material '{results['material'].ID}' whose wavelength sampled by {results['N']} cells. Maximum significant frequency estimated as {results['maxfreq']:g}Hz" + Style.RESET_ALL)
log.info(f"\nNumerical dispersion analysis: estimated largest physical phase-velocity error is {results['deltavp']:.2f}% in material '{results['material'].ID}' whose wavelength sampled by {results['N']} cells. Maximum significant frequency estimated as {results['maxfreq']:g}Hz") elif results['deltavp']:
log.info(f"\nNumerical dispersion analysis ({grid.name}): estimated largest physical phase-velocity error is {results['deltavp']:.2f}% in material '{results['material'].ID}' whose wavelength sampled by {results['N']} cells. Maximum significant frequency estimated as {results['maxfreq']:g}Hz")
def reuse_geometry(self): def reuse_geometry(self):
# Reset iteration number # Reset iteration number
@@ -293,8 +294,6 @@ class ModelBuildRun:
tsolve (float): time taken to execute solving (seconds). tsolve (float): time taken to execute solving (seconds).
""" """
log.info(f'\nOutput file: {config.model_configs[self.G.model_num].output_file_path_ext.name}')
# Check number of OpenMP threads # Check number of OpenMP threads
if config.sim_config.general['cpu']: if config.sim_config.general['cpu']:
log.info(f'CPU (OpenMP) threads for solving: {config.model_configs[self.G.model_num].ompthreads}\n') log.info(f'CPU (OpenMP) threads for solving: {config.model_configs[self.G.model_num].ompthreads}\n')
@@ -306,7 +305,7 @@ class ModelBuildRun:
# Prepare iterator # Prepare iterator
if config.sim_config.is_messages(): if config.sim_config.is_messages():
iterator = tqdm(range(self.G.iterations), desc='Running simulation, model ' + str(self.G.model_num + 1) + '/' + str(config.sim_config.model_end), ncols=get_terminal_width() - 1, file=sys.stdout, disable=not config.sim_config.general['progressbars']) iterator = tqdm(range(self.G.iterations), desc=f'Running model {self.G.model_num + 1}/{config.sim_config.model_end}, {config.model_configs[self.G.model_num].output_file_path_ext.name}', ncols=get_terminal_width() - 1, file=sys.stdout, disable=not config.sim_config.general['progressbars'])
else: else:
iterator = range(self.G.iterations) iterator = range(self.G.iterations)
@@ -329,7 +328,7 @@ class GridBuilder:
def build_pmls(self): def build_pmls(self):
log.info('') log.info('')
pbar = tqdm(total=sum(1 for value in self.grid.pmlthickness.values() if value > 0), pbar = tqdm(total=sum(1 for value in self.grid.pmlthickness.values() if value > 0),
desc=f'Building {self.grid.name} Grid PML boundaries', desc=f'Building PML boundaries ({self.grid.name})',
ncols=get_terminal_width() - 1, file=sys.stdout, ncols=get_terminal_width() - 1, file=sys.stdout,
disable=not config.sim_config.general['progressbars']) disable=not config.sim_config.general['progressbars'])
for pml_id, thickness in self.grid.pmlthickness.items(): for pml_id, thickness in self.grid.pmlthickness.items():
@@ -342,7 +341,7 @@ class GridBuilder:
# Build the model, i.e. set the material properties (ID) for every edge # Build the model, i.e. set the material properties (ID) for every edge
# of every Yee cell # of every Yee cell
log.info('') log.info('')
pbar = tqdm(total=2, desc=f'Building {self.grid.name} Grid', pbar = tqdm(total=2, desc=f'Building Yee cells ({self.grid.name})',
ncols=get_terminal_width() - 1, file=sys.stdout, ncols=get_terminal_width() - 1, file=sys.stdout,
disable=not config.sim_config.general['progressbars']) disable=not config.sim_config.general['progressbars'])
build_electric_components(self.grid.solid, self.grid.rigidE, self.grid.ID, self.grid) build_electric_components(self.grid.solid, self.grid.rigidE, self.grid.ID, self.grid)
@@ -373,5 +372,5 @@ class GridBuilder:
materialstable.outer_border = False materialstable.outer_border = False
materialstable.justify_columns[0] = 'right' materialstable.justify_columns[0] = 'right'
log.info(f'\n{self.grid.name} Grid Materials:') log.info(f'\nMaterials ({self.grid.name}):')
log.info(materialstable.table) log.info(materialstable.table)

查看文件

@@ -133,11 +133,6 @@ class Scene:
# Process main grid multiple commands # Process main grid multiple commands
self.process_cmds(self.multiple_cmds, G) self.process_cmds(self.multiple_cmds, G)
# Estimate and check memory (RAM) usage
log.debug('Fix memory checks')
# G.memory_check()
#snapshot_memory_check(G)
# Initialise an array for volumetric material IDs (solid), boolean # Initialise an array for volumetric material IDs (solid), boolean
# arrays for specifying materials not to be averaged (rigid), # arrays for specifying materials not to be averaged (rigid),
# an array for cell edge IDs (ID) # an array for cell edge IDs (ID)

查看文件

@@ -24,8 +24,8 @@ from ..grid import FDTDGrid
class SubGridBase(FDTDGrid): class SubGridBase(FDTDGrid):
def __init__(self, **kwargs): def __init__(self, *args, **kwargs):
super().__init__() super().__init__(args[0])
self.mode = '3D' self.mode = '3D'
self.ratio = kwargs['ratio'] self.ratio = kwargs['ratio']

查看文件

@@ -30,8 +30,8 @@ class SubGridHSG(SubGridBase):
gridtype = '3DSUBGRID' gridtype = '3DSUBGRID'
def __init__(self, **kwargs): def __init__(self, model_num, **kwargs):
super().__init__(**kwargs) super().__init__(model_num, **kwargs)
self.gridtype = SubGridHSG.gridtype self.gridtype = SubGridHSG.gridtype
def update_magnetic_is(self, precursors): def update_magnetic_is(self, precursors):
@@ -40,7 +40,7 @@ class SubGridHSG(SubGridBase):
Args: Args:
nwl, nwm, nwn, face, field, inc_field, lookup_id, sign, mod, co nwl, nwm, nwn, face, field, inc_field, lookup_id, sign, mod, co
""" """
# Hz = c0Hz - c1Ey + c2Ex # Hz = c0Hz - c1Ey + c2Ex
# Hy = c0Hy - c3Ex + c1Ez # Hy = c0Hy - c3Ex + c1Ez
# Hx = c0Hx - c2Ez + c3Ey # Hx = c0Hx - c2Ez + c3Ey
@@ -131,37 +131,29 @@ class SubGridHSG(SubGridBase):
def __str__(self): def __str__(self):
self.memory_estimate_basic() mem_use = self.mem_est_basic()
s = '\n' s = '\n'
s += Fore.CYAN s += Fore.CYAN
s += 'Sub Grid HSG\n' s += 'Sub Grid HSG\n'
s += 'Name: {}\n'.format(self.name) s += f'Name: {self.name}\n'
s += 'dx, dy, dz: {}m {}m {}m\n'.format(self.dx, self.dy, self.dz) s += f'dx, dy, dz: {self.dx}m {self.dy}m {self.dz}m\n'
s += 'dt: {}s\n'.format(self.dt) s += f'dt: {self.dt}s\n'
s += 'Memory Estimate: {}\n'.format(human_size(self.memoryusage)) s += f'Memory Estimate: {human_size(mem_use)}\n'
s += 'Position: ({}m, {}m, {}m), ({}m, {}m, {}m)\n'.format(self.x1, s += f'Position: ({self.x1}m, {self.y1}m, {self.z1}m), ({self.x2}m, {self.y2}m, {self.z2}m)\n'
self.y1, s += f'Main Grid Indices: lower left({self.io}, {self.j0}, {self.k0}), upper right({self.i1}, {self.j1}, {self.k1})\n'
self.z1, s += f'Total Cells: {self.nx} {self.ny} {self.nz}\n'
self.x2, s += f'Working Region Cells: {self.nwx} {self.nwy} {self.nwz}\n'
self.y2,
self.z2)
s += 'Main Grid Indices: lower left({}, {}, {}), upper right({}, {}, {})\n'.format(self.i0, self.j0, self.k0, self.i1, self.j1, self.k1)
s += 'Total Cells: {} {} {}\n'.format(self.nx, self.ny, self.nz)
s += 'Working Region Cells: {} {} {}\n'.format(self.nwx,
self.nwy,
self.nwz)
for h in self.hertziandipoles: for h in self.hertziandipoles:
s += 'Hertizian dipole: {} {} {}\n'.format(h.xcoord, s += f'Hertizian dipole: {h.xcoord} {h.ycoord} {h.zcoord}\n'
h.ycoord,
h.zcoord)
s += str([x for x in self.waveforms s += str([x for x in self.waveforms
if x.ID == h.waveformID][0]) + '\n' if x.ID == h.waveformID][0]) + '\n'
for r in self.rxs: for r in self.rxs:
s += 'Receiver: {} {} {}\n'.format(r.xcoord, r.ycoord, r.zcoord) s += f'Receiver: {r.xcoord} {r.ycoord} {r.zcoord}\n'
for tl in self.transmissionlines: for tl in self.transmissionlines:
s += 'Transmission Line: {} {} {}\n'.format(tl.xcoord, tl.ycoord, tl.zcoord) s += f'Transmission Line: {tl.xcoord} {tl.ycoord} {tl.zcoord}\n'
s += str([x for x in self.waveforms s += str([x for x in self.waveforms
if x.ID == tl.waveformID][0]) + '\n' if x.ID == tl.waveformID][0]) + '\n'
s += Style.RESET_ALL s += Style.RESET_ALL

查看文件

@@ -96,9 +96,6 @@ class SubGridBase(UserObjectMulti):
# Set the temporal discretisation # Set the temporal discretisation
sg.calculate_dt() sg.calculate_dt()
# ensure stability
sg.round_time_step()
# set the indices related to the subgrids main grid placement # set the indices related to the subgrids main grid placement
self.set_main_grid_indices(sg, grid, uip, p1, p2) self.set_main_grid_indices(sg, grid, uip, p1, p2)
@@ -128,8 +125,6 @@ class SubGridBase(UserObjectMulti):
# Copy over built in materials # Copy over built in materials
sg.materials = [copy(m) for m in grid.materials if m.numID in range(0, grid.n_built_in_materials + 1)] sg.materials = [copy(m) for m in grid.materials if m.numID in range(0, grid.n_built_in_materials + 1)]
# use same number of threads
sg.nthreads = grid.nthreads
# Dont mix and match different subgrids # Dont mix and match different subgrids
for sg_made in grid.subgrids: for sg_made in grid.subgrids:
@@ -194,10 +189,8 @@ class SubGridHSG(SubGridBase):
self.hash = '#subgrid_hsg' self.hash = '#subgrid_hsg'
def create(self, grid, uip): def create(self, grid, uip):
sg = SubGridHSGUser(**self.kwargs) sg = SubGridHSGUser(grid.model_num, **self.kwargs)
self.setup(sg, grid, uip) self.setup(sg, grid, uip)
if config.is_messages():
print(sg)
return sg return sg
@@ -219,4 +212,4 @@ class ReferenceRx(Rx):
r.offset = ratio // 2 r.offset = ratio // 2
except KeyError: except KeyError:
raise CmdInputError("'{}' has an no ratio parameter".format(self.__str__())) raise CmdInputError(f"'{self.__str__()}' has an no ratio parameter")

查看文件

@@ -46,10 +46,10 @@ def create_user_input_points(grid, user_obj):
if isinstance(grid, SubGridBase): if isinstance(grid, SubGridBase):
# Local object configuration trumps. User can turn of autotranslate for # Local object configuration trumps. User can turn of autotranslate for
# specfic objects. # specfic objects.
if not user_obj.autotranslate and config.general['autotranslate']: if not user_obj.autotranslate and config.sim_config.general['autotranslate']:
return MainGridUserInput(grid) return MainGridUserInput(grid)
if config.general['autotranslate']: if config.sim_config.general['autotranslate']:
return SubgridUserInput(grid) return SubgridUserInput(grid)
else: else:
return MainGridUserInput(grid) return MainGridUserInput(grid)

查看文件

@@ -28,7 +28,7 @@ import subprocess
from shutil import get_terminal_size from shutil import get_terminal_size
import sys import sys
import textwrap import textwrap
from time import process_time from time import perf_counter
from colorama import init from colorama import init
from colorama import Fore from colorama import Fore
@@ -485,4 +485,4 @@ def detect_check_gpus(deviceIDs):
def timer(): def timer():
"""Function to return the current process wide time in fractional seconds.""" """Function to return the current process wide time in fractional seconds."""
return process_time() return perf_counter()

1
todo
查看文件

@@ -1 +0,0 @@
memory update for all grid (model_build_run.py)

查看文件

@@ -18,7 +18,6 @@
import argparse import argparse
import os import os
import sys
import h5py import h5py
import numpy as np import numpy as np
@@ -51,7 +50,7 @@ def mpl_plot(filename, outputs=Rx.defaultoutputs, fft=False):
# Check there are any receivers # Check there are any receivers
if nrx == 0: if nrx == 0:
raise CmdInputError('No receivers found in {}'.format(filename)) raise CmdInputError(f'No receivers found in {filename}')
# Check for single output component when doing a FFT # Check for single output component when doing a FFT
if fft: if fft:
@@ -77,7 +76,7 @@ def mpl_plot(filename, outputs=Rx.defaultoutputs, fft=False):
output = outputs[0] output = outputs[0]
if output not in availableoutputs: if output not in availableoutputs:
raise CmdInputError('{} output requested to plot, but the available output for receiver 1 is {}'.format(output, ', '.join(availableoutputs))) raise CmdInputError(f"{output} output requested to plot, but the available output for receiver 1 is {', '.join(availableoutputs)}")
outputdata = f[path + output][:] * polarity outputdata = f[path + output][:] * polarity
@@ -165,7 +164,7 @@ def mpl_plot(filename, outputs=Rx.defaultoutputs, fft=False):
# Check if requested output is in file # Check if requested output is in file
if output not in availableoutputs: if output not in availableoutputs:
raise CmdInputError('Output(s) requested to plot: {}, but available output(s) for receiver {} in the file: {}'.format(', '.join(outputs), rx, ', '.join(availableoutputs))) raise CmdInputError(f"Output(s) requested to plot: {', '.join(outputs)}, but available output(s) for receiver {rx} in the file: {', '.join(availableoutputs)}")
outputdata = f[path + output][:] * polarity outputdata = f[path + output][:] * polarity

查看文件

@@ -1,64 +0,0 @@
import gprMax
scene = gprMax.Scene()
# global configuration
# objects within the inner surface can be positioned automatically
# in this configuration coordinates in the main grid Space
# are converted to subgrid local space automatically
# if objects or required in the region between the inner Surface
# and the outer surface autotranslate must be set to False
# and the local position must be set explicitly. Addionally,
# in this instance, objects which occur within the IS OS gap
# must be positioned in the subgrid and the main grid.
gprMax.config.general['autotranslate'] = True
# Points are specified as iterables
p1 = (1, 1, 1)
# single objects
dxdydz = gprMax.Discretisation(p1=(1e-2, 1e-2, 1e-2))
messages = gprMax.Messages(yn='y')
tw = gprMax.TimeWindow(iterations=250)
domain = gprMax.Domain(p1=p1)
title = gprMax.Title(name='mysimulation')
waveform = gprMax.Waveform(wave_type='gaussiandotnorm', amp=1, freq=400e6, id='mywaveform')
# position in subgrid automatically
dipole = gprMax.HertzianDipole(p1=(0.5, 0.5, 0.5), polarisation='y', waveform_id='mywaveform')
# equivalent position when autotranslate is false
#rx = gprMax.Rx(p1=(50 * 1e-2 / 5, 50 * 1e-2 / 5, 50 * 1e-2 / 5))
# rx in the subgrid
# subgrid data is stored in an additional output file called
# outputfile_<subgrid_id>.out
rx1 = gprMax.Rx(p1=(0.52, 0.5, 0.5))
# rx in the main grid
rx2 = gprMax.Rx(p1=(0.2, 0.5, 0.5))
# set the position of the Inner Surface
subgrid = gprMax.SubGridHSG(p1=(45e-2, 45e-2, 45e-2),
p2=(55e-2, 55e-2, 55e-2),
ratio=3,
id='subgrid1',
filter=False)
#pml_separation=6,
#subgrid_pml_thickness=10)
scene = gprMax.Scene()
scene.add(dxdydz)
scene.add(messages)
scene.add(tw)
scene.add(domain)
scene.add(title)
scene.add(subgrid)
scene.add(rx2)
subgrid.add(waveform)
subgrid.add(dipole)
subgrid.add(rx1)
#gprMax.run(scenes=[scene], n=1, geometry_only=False, outputfile='mysimulation', geometry_fixed=False, subgrid=True)
gprMax.run(scenes=[scene], n=1, geometry_only=False, outputfile='mysimulation', geometry_fixed=False, subgrid=True)

查看文件

@@ -0,0 +1,137 @@
# Copyright (C) 2015-2019, John Hartley
#
# This module is licensed under the Creative Commons Attribution-ShareAlike 4.0 International License.
# To view a copy of this license, visit http://creativecommons.org/licenses/by-sa/4.0/.
from pathlib import Path
import gprMax
from user_libs.antennas.GSSI import antenna_like_GSSI_400
import numpy as np
# file path step
fn = Path(__file__).with_suffix('')
parts = fn.parts
# subgrid Discretisation is 1 mm in x, y, z directions. This allows us
# to model the geometry of the antenna
dl_s = 1e-3
# subgridding ratio. This must always be an odd integer multiple. In this case
# the main grid discrestisation is 9e-3 m.
ratio = 9
dl = dl_s * 9
# estimated return time for signal to propagate 1 metre and back
tw = 2 / 3e8 * (np.sqrt(3.2) + np.sqrt(9))
# domain extent
x = 3
y = 1
z = 2
scene = gprMax.Scene()
title_gpr = gprMax.Title(name=fn.name)
dxdydz = gprMax.Discretisation(p1=(dl, dl, dl))
domain = gprMax.Domain(p1=(x, y, z))
time_window = gprMax.TimeWindow(time=tw)
scene.add(domain)
scene.add(title_gpr)
scene.add(dxdydz)
scene.add(time_window)
# half space material
halfspace_m = gprMax.Material(er=3.2, se=0.397e-3, mr=1, sm=0, id='soil')
scene.add(halfspace_m)
antenna_case = (0.3, 0.3, 0.178)
bounding_box = 2 * dl
# pml + boundary_cells + is_os + subgrid+boundary + half antenna
x0 = (10 + 15 + 5 + 2) * dl + antenna_case[0] / 2
#antenna_p = (x / 2, y / 2, z - 30 * dl - bounding_box - antenna_case[2])
# Position of antenna base
antenna_p = (x / 2, y / 2, 170 * dl)
sg_x0 = antenna_p[0] - antenna_case[0] / 2 - bounding_box
sg_y0 = antenna_p[1] - antenna_case[1] / 2 - bounding_box
sg_z0 = antenna_p[2] - bounding_box
sg_x1 = antenna_p[0] + antenna_case[0] / 2 + bounding_box
sg_y1 = antenna_p[1] + antenna_case[1] / 2 + bounding_box
sg_z1 = antenna_p[2] + antenna_case[2] + bounding_box
sg = gprMax.SubGridHSG(p1=[sg_x0, sg_y0, sg_z0], p2=[sg_x1, sg_y1, sg_z1], ratio=ratio, id='subgrid1')
scene.add(sg)
# half space box in main grid
halfspace = gprMax.Box(p1=(0, 0, 0), p2=(x, y, antenna_p[2]), material_id='soil')
scene.add(halfspace)
# position half space box in the subgrid. The halfspace has to be positioned
# manually because it traverses the grid. Grid traversal requries that objects extend
# beyond the OS. Turning off autotranslate allows you to place objects beyond the OS.
# PML seperation from the OS
ps = ratio // 2 + 2
# number of pml cells in the subgrid
pc = 6
# is os seperation
isos = 3 * ratio
h = antenna_p[2] - sg_z0 + (ps + pc + isos) * dl_s
# half space box
halfspace = gprMax.Box(p1=(0, 0, 0), p2=(411 * dl_s, 411 * dl_s, h), material_id='soil')
# position the box using local coordinates3e8 / 400e6
halfspace.autotranslate = False
sg.add(halfspace)
# Import the antenna model and add components to subgrid
gssi_objects = antenna_like_GSSI_400(*antenna_p, resolution=dl_s)
for obj in gssi_objects:
sg.add(obj)
# half space box
halfspace = gprMax.Box(p1=(0, 0, 0), p2=(x, y, antenna_p[2]), material_id='soil')
scene.add(halfspace)
for i in range(1, 51):
snap = gprMax.Snapshot(p1=(0, y / 2, 0), p2=(x, y / 2 + dl, z), dl=(dl, dl, dl),
filename=Path(*parts[:-1], parts[-1] + '_' + str(i)).name,
time=i * tw / 50)
scene.add(snap)
# create a geometry view of the main grid and the sub grid stitched together
gv = gprMax.GeometryView(p1=(0, 0, 0),
p2=domain.props.p1,
dl=dl,
filename=fn.name,
output_type='f',
multi_grid=True)
# create a geometry view of the main grid and the sub grid stitched together
gv_normal = gprMax.GeometryView(p1=(0, 0, 0),
p2=domain.props.p1,
dl=dl,
filename=Path(*parts[:-1], parts[-1] + '_voxels').name,
output_type='n')
# scene.add(gv)
scene.add(gv_normal)
# half space material
layer_m = gprMax.Material(er=9, se=0.397e-3, mr=1, sm=0, id='soil_2')
scene.add(layer_m)
fb = gprMax.FractalBox(p1=(0, 0, 0), p2=(3, 1, 1), frac_dim=1.5, weighting=(1, 1, 1), n_materials=1, mixing_model_id='soil_2', id='fbox', seed=1)
scene.add(fb)
sr = gprMax.AddSurfaceRoughness(p1=(0, 0, 1), p2=(3, 1, 1), frac_dim=1.5, weighting=(1, 1), limits=(0.4, 1.2), fractal_box_id='fbox', seed=1)
scene.add(sr)
gprMax.run(scenes=[scene], n=1, geometry_only=False, outputfile=fn.parent, subgrid=True, autotranslate=True)