diff --git a/gprMax/cmds_geometry/add_grass.py b/gprMax/cmds_geometry/add_grass.py index 4799707a..dacdc9b5 100644 --- a/gprMax/cmds_geometry/add_grass.py +++ b/gprMax/cmds_geometry/add_grass.py @@ -91,11 +91,15 @@ class AddGrass(UserObjectGeometry): self._do_rotate() # Get the correct fractal volume - volumes = [volume for volume in grid.fractalvolumes if volume.ID == fractal_box_id] + volumes = [ + volume for volume in grid.fractalvolumes if volume.ID == fractal_box_id + ] try: volume = volumes[0] except NameError: - logger.exception(f"{self.__str__()} cannot find FractalBox {fractal_box_id}") + logger.exception( + f"{self.__str__()} cannot find FractalBox {fractal_box_id}" + ) raise p1, p2 = uip.check_box_points(p1, p2, self.__str__()) @@ -103,7 +107,9 @@ class AddGrass(UserObjectGeometry): xf, yf, zf = p2 if frac_dim < 0: - logger.exception(f"{self.__str__()} requires a positive value for the fractal dimension") + logger.exception( + f"{self.__str__()} requires a positive value for the fractal dimension" + ) raise ValueError if limits[0] < 0 or limits[1] < 0: logger.exception( @@ -114,12 +120,19 @@ class AddGrass(UserObjectGeometry): # Check for valid orientations if xs == xf: if ys == yf or zs == zf: - logger.exception(f"{self.__str__()} dimensions are not specified correctly") + logger.exception( + f"{self.__str__()} dimensions are not specified correctly" + ) raise ValueError if xs not in [volume.xs, volume.xf]: - logger.exception(f"{self.__str__()} must specify external surfaces on a fractal box") + logger.exception( + f"{self.__str__()} must specify external surfaces on a fractal box" + ) raise ValueError - fractalrange = (round_value(limits[0] / grid.dx), round_value(limits[1] / grid.dx)) + fractalrange = ( + round_value(limits[0] / grid.dx), + round_value(limits[1] / grid.dx), + ) # xminus surface if xs == volume.xs: logger.exception( @@ -139,12 +152,19 @@ class AddGrass(UserObjectGeometry): elif ys == yf: if zs == zf: - logger.exception(f"{self.__str__()} dimensions are not specified correctly") + logger.exception( + f"{self.__str__()} dimensions are not specified correctly" + ) raise ValueError if ys not in [volume.ys, volume.yf]: - logger.exception(f"{self.__str__()} must specify external surfaces on a fractal box") + logger.exception( + f"{self.__str__()} must specify external surfaces on a fractal box" + ) raise ValueError - fractalrange = (round_value(limits[0] / grid.dy), round_value(limits[1] / grid.dy)) + fractalrange = ( + round_value(limits[0] / grid.dy), + round_value(limits[1] / grid.dy), + ) # yminus surface if ys == volume.ys: logger.exception( @@ -164,9 +184,14 @@ class AddGrass(UserObjectGeometry): elif zs == zf: if zs not in [volume.zs, volume.zf]: - logger.exception(f"{self.__str__()} must specify external surfaces on a fractal box") + logger.exception( + f"{self.__str__()} must specify external surfaces on a fractal box" + ) raise ValueError - fractalrange = (round_value(limits[0] / grid.dz), round_value(limits[1] / grid.dz)) + fractalrange = ( + round_value(limits[0] / grid.dz), + round_value(limits[1] / grid.dz), + ) # zminus surface if zs == volume.zs: logger.exception( @@ -219,7 +244,8 @@ class AddGrass(UserObjectGeometry): # probability values, and convert the 1D index back into a x, y index # for the original surface. bladesindex = np.unravel_index( - np.digitize(A, probability1D), (surface.fractalsurface.shape[0], surface.fractalsurface.shape[1]) + np.digitize(A, probability1D), + (surface.fractalsurface.shape[0], surface.fractalsurface.shape[1]), ) # Set the fractal range to minimum and maximum heights of the grass blades @@ -227,7 +253,9 @@ class AddGrass(UserObjectGeometry): # Set the fractal surface using the pre-calculated spatial distribution # and a random height - surface.fractalsurface = np.zeros((surface.fractalsurface.shape[0], surface.fractalsurface.shape[1])) + surface.fractalsurface = np.zeros( + (surface.fractalsurface.shape[0], surface.fractalsurface.shape[1]) + ) for i in range(len(bladesindex[0])): surface.fractalsurface[bladesindex[0][i], bladesindex[1][i]] = R.randint( surface.fractalrange[0], surface.fractalrange[1], size=1 diff --git a/gprMax/cmds_geometry/add_surface_roughness.py b/gprMax/cmds_geometry/add_surface_roughness.py index 30489a7a..423a0992 100644 --- a/gprMax/cmds_geometry/add_surface_roughness.py +++ b/gprMax/cmds_geometry/add_surface_roughness.py @@ -91,11 +91,15 @@ class AddSurfaceRoughness(UserObjectGeometry): self._do_rotate() # Get the correct fractal volume - volumes = [volume for volume in grid.fractalvolumes if volume.ID == fractal_box_id] + volumes = [ + volume for volume in grid.fractalvolumes if volume.ID == fractal_box_id + ] if volumes: volume = volumes[0] else: - logger.exception(f"{self.__str__()} cannot find FractalBox {fractal_box_id}") + logger.exception( + f"{self.__str__()} cannot find FractalBox {fractal_box_id}" + ) raise ValueError p1, p2 = uip.check_box_points(p1, p2, self.__str__()) @@ -103,7 +107,10 @@ class AddSurfaceRoughness(UserObjectGeometry): xf, yf, zf = p2 if frac_dim < 0: - logger.exception(f"{self.__str__()} requires a positive value for the " + "fractal dimension") + logger.exception( + f"{self.__str__()} requires a positive value for the " + + "fractal dimension" + ) raise ValueError if weighting[0] < 0: logger.exception( @@ -121,12 +128,19 @@ class AddSurfaceRoughness(UserObjectGeometry): # Check for valid orientations if xs == xf: if ys == yf or zs == zf: - logger.exception(f"{self.__str__()} dimensions are not specified correctly") + logger.exception( + f"{self.__str__()} dimensions are not specified correctly" + ) raise ValueError if xs not in [volume.xs, volume.xf]: - logger.exception(f"{self.__str__()} can only be used on the external surfaces of a fractal box") + logger.exception( + f"{self.__str__()} can only be used on the external surfaces of a fractal box" + ) raise ValueError - fractalrange = (round_value(limits[0] / grid.dx), round_value(limits[1] / grid.dx)) + fractalrange = ( + round_value(limits[0] / grid.dx), + round_value(limits[1] / grid.dx), + ) # xminus surface if xs == volume.xs: if fractalrange[0] < 0 or fractalrange[1] > volume.xf: @@ -152,12 +166,20 @@ class AddSurfaceRoughness(UserObjectGeometry): elif ys == yf: if zs == zf: - logger.exception(f"{self.__str__()} dimensions are not specified correctly") + logger.exception( + f"{self.__str__()} dimensions are not specified correctly" + ) raise ValueError if ys not in [volume.ys, volume.yf]: - logger.exception(f"{self.__str__()} can only be used on the external " + "surfaces of a fractal box") + logger.exception( + f"{self.__str__()} can only be used on the external " + + "surfaces of a fractal box" + ) raise ValueError - fractalrange = (round_value(limits[0] / grid.dy), round_value(limits[1] / grid.dy)) + fractalrange = ( + round_value(limits[0] / grid.dy), + round_value(limits[1] / grid.dy), + ) # yminus surface if ys == volume.ys: if fractalrange[0] < 0 or fractalrange[1] > volume.yf: @@ -183,9 +205,15 @@ class AddSurfaceRoughness(UserObjectGeometry): elif zs == zf: if zs not in [volume.zs, volume.zf]: - logger.exception(f"{self.__str__()} can only be used on the external " + "surfaces of a fractal box") + logger.exception( + f"{self.__str__()} can only be used on the external " + + "surfaces of a fractal box" + ) raise ValueError - fractalrange = (round_value(limits[0] / grid.dz), round_value(limits[1] / grid.dz)) + fractalrange = ( + round_value(limits[0] / grid.dz), + round_value(limits[1] / grid.dz), + ) # zminus surface if zs == volume.zs: if fractalrange[0] < 0 or fractalrange[1] > volume.zf: @@ -222,7 +250,9 @@ class AddSurfaceRoughness(UserObjectGeometry): # List of existing surfaces IDs existingsurfaceIDs = [x.surfaceID for x in volume.fractalsurfaces] if surface.surfaceID in existingsurfaceIDs: - logger.exception(f"{self.__str__()} has already been used on the {surface.surfaceID} surface") + logger.exception( + f"{self.__str__()} has already been used on the {surface.surfaceID} surface" + ) raise ValueError surface.generate_fractal_surface() diff --git a/gprMax/cmds_geometry/add_surface_water.py b/gprMax/cmds_geometry/add_surface_water.py index c04c2c4f..29a14edd 100644 --- a/gprMax/cmds_geometry/add_surface_water.py +++ b/gprMax/cmds_geometry/add_surface_water.py @@ -74,10 +74,14 @@ class AddSurfaceWater(UserObjectGeometry): if self.do_rotate: self._do_rotate() - if volumes := [volume for volume in grid.fractalvolumes if volume.ID == fractal_box_id]: + if volumes := [ + volume for volume in grid.fractalvolumes if volume.ID == fractal_box_id + ]: volume = volumes[0] else: - logger.exception(f"{self.__str__()} cannot find FractalBox {fractal_box_id}") + logger.exception( + f"{self.__str__()} cannot find FractalBox {fractal_box_id}" + ) raise ValueError p1, p2 = uip.check_box_points(p1, p2, self.__str__()) @@ -85,16 +89,22 @@ class AddSurfaceWater(UserObjectGeometry): xf, yf, zf = p2 if depth <= 0: - logger.exception(f"{self.__str__()} requires a positive value for the depth of water") + logger.exception( + f"{self.__str__()} requires a positive value for the depth of water" + ) raise ValueError # Check for valid orientations if xs == xf: if ys == yf or zs == zf: - logger.exception(f"{self.__str__()} dimensions are not specified correctly") + logger.exception( + f"{self.__str__()} dimensions are not specified correctly" + ) raise ValueError if xs not in [volume.xs, volume.xf]: - logger.exception(f"{self.__str__()} can only be used on the external surfaces of a fractal box") + logger.exception( + f"{self.__str__()} can only be used on the external surfaces of a fractal box" + ) raise ValueError # xminus surface if xs == volume.xs: @@ -107,10 +117,14 @@ class AddSurfaceWater(UserObjectGeometry): elif ys == yf: if zs == zf: - logger.exception(f"{self.__str__()} dimensions are not specified correctly") + logger.exception( + f"{self.__str__()} dimensions are not specified correctly" + ) raise ValueError if ys not in [volume.ys, volume.yf]: - logger.exception(f"{self.__str__()} can only be used on the external surfaces of a fractal box") + logger.exception( + f"{self.__str__()} can only be used on the external surfaces of a fractal box" + ) raise ValueError # yminus surface if ys == volume.ys: @@ -123,7 +137,9 @@ class AddSurfaceWater(UserObjectGeometry): elif zs == zf: if zs not in [volume.zs, volume.zf]: - logger.exception(f"{self.__str__()} can only be used on the external surfaces of a fractal box") + logger.exception( + f"{self.__str__()} can only be used on the external surfaces of a fractal box" + ) raise ValueError # zminus surface if zs == volume.zs: @@ -138,7 +154,9 @@ class AddSurfaceWater(UserObjectGeometry): logger.exception(f"{self.__str__()} dimensions are not specified correctly") raise ValueError - surface = next((x for x in volume.fractalsurfaces if x.surfaceID == requestedsurface), None) + surface = next( + (x for x in volume.fractalsurfaces if x.surfaceID == requestedsurface), None + ) if not surface: logger.exception( f"{self.__str__()} specified surface {requestedsurface} does not have a rough surface applied" @@ -148,7 +166,10 @@ class AddSurfaceWater(UserObjectGeometry): surface.filldepth = filldepthcells # Check that requested fill depth falls within range of surface roughness - if surface.filldepth < surface.fractalrange[0] or surface.filldepth > surface.fractalrange[1]: + if ( + surface.filldepth < surface.fractalrange[0] + or surface.filldepth > surface.fractalrange[1] + ): logger.exception( f"{self.__str__()} requires a value for the depth of water that lies with the " f"range of the requested surface roughness" diff --git a/gprMax/cmds_geometry/box.py b/gprMax/cmds_geometry/box.py index dba8b196..2e7d19f1 100644 --- a/gprMax/cmds_geometry/box.py +++ b/gprMax/cmds_geometry/box.py @@ -24,8 +24,7 @@ import gprMax.config as config from ..cython.geometry_primitives import build_box from ..materials import Material -from .cmds_geometry import (UserObjectGeometry, check_averaging, - rotate_2point_object) +from .cmds_geometry import UserObjectGeometry, check_averaging, rotate_2point_object logger = logging.getLogger(__name__) @@ -128,10 +127,18 @@ class Box(UserObjectGeometry): m = Material(numID, requiredID) m.type = "dielectric-smoothed" # Create dielectric-smoothed constituents for material - m.er = np.mean((materials[0].er, materials[1].er, materials[2].er), axis=0) - m.se = np.mean((materials[0].se, materials[1].se, materials[2].se), axis=0) - m.mr = np.mean((materials[0].mr, materials[1].mr, materials[2].mr), axis=0) - m.sm = np.mean((materials[0].sm, materials[1].sm, materials[2].sm), axis=0) + m.er = np.mean( + (materials[0].er, materials[1].er, materials[2].er), axis=0 + ) + m.se = np.mean( + (materials[0].se, materials[1].se, materials[2].se), axis=0 + ) + m.mr = np.mean( + (materials[0].mr, materials[1].mr, materials[2].mr), axis=0 + ) + m.sm = np.mean( + (materials[0].sm, materials[1].sm, materials[2].sm), axis=0 + ) # Append the new material object to the materials list grid.materials.append(m) diff --git a/gprMax/cmds_geometry/cone.py b/gprMax/cmds_geometry/cone.py index cde5f91c..45214ab8 100644 --- a/gprMax/cmds_geometry/cone.py +++ b/gprMax/cmds_geometry/cone.py @@ -55,7 +55,9 @@ class Cone(UserObjectGeometry): r1 = self.kwargs["r1"] r2 = self.kwargs["r2"] except KeyError: - logger.exception(f"{self.__str__()} please specify two points and two radii") + logger.exception( + f"{self.__str__()} please specify two points and two radii" + ) raise # Check averaging @@ -86,11 +88,15 @@ class Cone(UserObjectGeometry): x2, y2, z2 = uip.round_to_grid(p2) if r1 < 0: - logger.exception(f"{self.__str__()} the radius of the first face {r1:g} should be a positive value.") + logger.exception( + f"{self.__str__()} the radius of the first face {r1:g} should be a positive value." + ) raise ValueError if r2 < 0: - logger.exception(f"{self.__str__()} the radius of the second face {r2:g} should be a positive value.") + logger.exception( + f"{self.__str__()} the radius of the second face {r2:g} should be a positive value." + ) raise ValueError if r1 == 0 and r2 == 0: @@ -125,10 +131,18 @@ class Cone(UserObjectGeometry): m = Material(numID, requiredID) m.type = "dielectric-smoothed" # Create dielectric-smoothed constituents for material - m.er = np.mean((materials[0].er, materials[1].er, materials[2].er), axis=0) - m.se = np.mean((materials[0].se, materials[1].se, materials[2].se), axis=0) - m.mr = np.mean((materials[0].mr, materials[1].mr, materials[2].mr), axis=0) - m.sm = np.mean((materials[0].sm, materials[1].sm, materials[2].sm), axis=0) + m.er = np.mean( + (materials[0].er, materials[1].er, materials[2].er), axis=0 + ) + m.se = np.mean( + (materials[0].se, materials[1].se, materials[2].se), axis=0 + ) + m.mr = np.mean( + (materials[0].mr, materials[1].mr, materials[2].mr), axis=0 + ) + m.sm = np.mean( + (materials[0].sm, materials[1].sm, materials[2].sm), axis=0 + ) # Append the new material object to the materials list grid.materials.append(m) diff --git a/gprMax/cmds_geometry/cylinder.py b/gprMax/cmds_geometry/cylinder.py index ceb7f494..aeee500e 100644 --- a/gprMax/cmds_geometry/cylinder.py +++ b/gprMax/cmds_geometry/cylinder.py @@ -83,7 +83,9 @@ class Cylinder(UserObjectGeometry): x2, y2, z2 = uip.round_to_grid(p2) if r <= 0: - logger.exception(f"{self.__str__()} the radius {r:g} should be a positive value.") + logger.exception( + f"{self.__str__()} the radius {r:g} should be a positive value." + ) raise ValueError # Look up requested materials in existing list of material instances @@ -114,10 +116,18 @@ class Cylinder(UserObjectGeometry): m = Material(numID, requiredID) m.type = "dielectric-smoothed" # Create dielectric-smoothed constituents for material - m.er = np.mean((materials[0].er, materials[1].er, materials[2].er), axis=0) - m.se = np.mean((materials[0].se, materials[1].se, materials[2].se), axis=0) - m.mr = np.mean((materials[0].mr, materials[1].mr, materials[2].mr), axis=0) - m.sm = np.mean((materials[0].sm, materials[1].sm, materials[2].sm), axis=0) + m.er = np.mean( + (materials[0].er, materials[1].er, materials[2].er), axis=0 + ) + m.se = np.mean( + (materials[0].se, materials[1].se, materials[2].se), axis=0 + ) + m.mr = np.mean( + (materials[0].mr, materials[1].mr, materials[2].mr), axis=0 + ) + m.sm = np.mean( + (materials[0].sm, materials[1].sm, materials[2].sm), axis=0 + ) # Append the new material object to the materials list grid.materials.append(m) diff --git a/gprMax/cmds_geometry/cylindrical_sector.py b/gprMax/cmds_geometry/cylindrical_sector.py index e7948d59..d00934f3 100644 --- a/gprMax/cmds_geometry/cylindrical_sector.py +++ b/gprMax/cmds_geometry/cylindrical_sector.py @@ -95,15 +95,23 @@ class CylindricalSector(UserObjectGeometry): sectorangle = 2 * np.pi * (end / 360) if normal not in ["x", "y", "z"]: - logger.exception(f"{self.__str__()} the normal direction must be either x, y or z.") + logger.exception( + f"{self.__str__()} the normal direction must be either x, y or z." + ) raise ValueError if r <= 0: - logger.exception(f"{self.__str__()} the radius {r:g} should be a positive value.") + logger.exception( + f"{self.__str__()} the radius {r:g} should be a positive value." + ) if sectorstartangle < 0 or sectorangle <= 0: - logger.exception(f"{self.__str__()} the starting angle and sector angle should be a positive values.") + logger.exception( + f"{self.__str__()} the starting angle and sector angle should be a positive values." + ) raise ValueError if sectorstartangle >= 2 * np.pi or sectorangle >= 2 * np.pi: - logger.exception(f"{self.__str__()} the starting angle and sector angle must be less than 360 degrees.") + logger.exception( + f"{self.__str__()} the starting angle and sector angle must be less than 360 degrees." + ) raise ValueError # Look up requested materials in existing list of material instances @@ -134,10 +142,18 @@ class CylindricalSector(UserObjectGeometry): m = Material(numID, requiredID) m.type = "dielectric-smoothed" # Create dielectric-smoothed constituents for material - m.er = np.mean((materials[0].er, materials[1].er, materials[2].er), axis=0) - m.se = np.mean((materials[0].se, materials[1].se, materials[2].se), axis=0) - m.mr = np.mean((materials[0].mr, materials[1].mr, materials[2].mr), axis=0) - m.sm = np.mean((materials[0].sm, materials[1].sm, materials[2].sm), axis=0) + m.er = np.mean( + (materials[0].er, materials[1].er, materials[2].er), axis=0 + ) + m.se = np.mean( + (materials[0].se, materials[1].se, materials[2].se), axis=0 + ) + m.mr = np.mean( + (materials[0].mr, materials[1].mr, materials[2].mr), axis=0 + ) + m.sm = np.mean( + (materials[0].sm, materials[1].sm, materials[2].sm), axis=0 + ) # Append the new material object to the materials list grid.materials.append(m) diff --git a/gprMax/cmds_geometry/edge.py b/gprMax/cmds_geometry/edge.py index fbc936f7..4c0f1d60 100644 --- a/gprMax/cmds_geometry/edge.py +++ b/gprMax/cmds_geometry/edge.py @@ -20,8 +20,7 @@ import logging import numpy as np -from ..cython.geometry_primitives import (build_edge_x, build_edge_y, - build_edge_z) +from ..cython.geometry_primitives import build_edge_x, build_edge_y, build_edge_z from .cmds_geometry import UserObjectGeometry, rotate_2point_object logger = logging.getLogger(__name__) @@ -92,15 +91,21 @@ class Edge(UserObjectGeometry): raise ValueError elif xs != xf: for i in range(xs, xf): - build_edge_x(i, ys, zs, material.numID, grid.rigidE, grid.rigidH, grid.ID) + build_edge_x( + i, ys, zs, material.numID, grid.rigidE, grid.rigidH, grid.ID + ) elif ys != yf: for j in range(ys, yf): - build_edge_y(xs, j, zs, material.numID, grid.rigidE, grid.rigidH, grid.ID) + build_edge_y( + xs, j, zs, material.numID, grid.rigidE, grid.rigidH, grid.ID + ) elif zs != zf: for k in range(zs, zf): - build_edge_z(xs, ys, k, material.numID, grid.rigidE, grid.rigidH, grid.ID) + build_edge_z( + xs, ys, k, material.numID, grid.rigidE, grid.rigidH, grid.ID + ) logger.info( f"{self.grid_name(grid)}Edge from {p3[0]:g}m, {p3[1]:g}m, " diff --git a/gprMax/cmds_geometry/ellipsoid.py b/gprMax/cmds_geometry/ellipsoid.py index fcbffff0..43b810b0 100644 --- a/gprMax/cmds_geometry/ellipsoid.py +++ b/gprMax/cmds_geometry/ellipsoid.py @@ -53,7 +53,9 @@ class Ellipsoid(UserObjectGeometry): zr = self.kwargs["zr"] except KeyError: - logger.exception(f"{self.__str__()} please specify a point and the three semiaxes.") + logger.exception( + f"{self.__str__()} please specify a point and the three semiaxes." + ) raise # Check averaging @@ -109,10 +111,18 @@ class Ellipsoid(UserObjectGeometry): m = Material(numID, requiredID) m.type = "dielectric-smoothed" # Create dielectric-smoothed constituents for material - m.er = np.mean((materials[0].er, materials[1].er, materials[2].er), axis=0) - m.se = np.mean((materials[0].se, materials[1].se, materials[2].se), axis=0) - m.mr = np.mean((materials[0].mr, materials[1].mr, materials[2].mr), axis=0) - m.sm = np.mean((materials[0].sm, materials[1].sm, materials[2].sm), axis=0) + m.er = np.mean( + (materials[0].er, materials[1].er, materials[2].er), axis=0 + ) + m.se = np.mean( + (materials[0].se, materials[1].se, materials[2].se), axis=0 + ) + m.mr = np.mean( + (materials[0].mr, materials[1].mr, materials[2].mr), axis=0 + ) + m.sm = np.mean( + (materials[0].sm, materials[1].sm, materials[2].sm), axis=0 + ) # Append the new material object to the materials list grid.materials.append(m) diff --git a/gprMax/cmds_geometry/fractal_box.py b/gprMax/cmds_geometry/fractal_box.py index e4fcc8f2..37e838ac 100644 --- a/gprMax/cmds_geometry/fractal_box.py +++ b/gprMax/cmds_geometry/fractal_box.py @@ -21,13 +21,14 @@ import logging import numpy as np import gprMax.config as config -from gprMax.cmds_geometry.cmds_geometry import (UserObjectGeometry, - rotate_2point_object) +from gprMax.cmds_geometry.cmds_geometry import UserObjectGeometry, rotate_2point_object from gprMax.fractals import FractalVolume from gprMax.materials import ListMaterial -from ..cython.geometry_primitives import (build_voxels_from_array, - build_voxels_from_array_mask) +from ..cython.geometry_primitives import ( + build_voxels_from_array, + build_voxels_from_array_mask, +) logger = logging.getLogger(__name__) @@ -118,40 +119,59 @@ class FractalBox(UserObjectGeometry): xf, yf, zf = p2 if frac_dim < 0: - logger.exception(f"{self.__str__()} requires a positive value for the fractal dimension") + logger.exception( + f"{self.__str__()} requires a positive value for the fractal dimension" + ) raise ValueError if weighting[0] < 0: - logger.exception(f"{self.__str__()} requires a positive value for the fractal weighting in the x direction") + logger.exception( + f"{self.__str__()} requires a positive value for the fractal weighting in the x direction" + ) raise ValueError if weighting[1] < 0: - logger.exception(f"{self.__str__()} requires a positive value for the fractal weighting in the y direction") + logger.exception( + f"{self.__str__()} requires a positive value for the fractal weighting in the y direction" + ) raise ValueError if weighting[2] < 0: - logger.exception(f"{self.__str__()} requires a positive value for the fractal weighting in the z direction") + logger.exception( + f"{self.__str__()} requires a positive value for the fractal weighting in the z direction" + ) if n_materials < 0: - logger.exception(f"{self.__str__()} requires a positive value for the number of bins") + logger.exception( + f"{self.__str__()} requires a positive value for the number of bins" + ) raise ValueError # Find materials to use to build fractal volume, either from mixing # models or normal materials. - mixingmodel = next((x for x in grid.mixingmodels if x.ID == mixing_model_id), None) + mixingmodel = next( + (x for x in grid.mixingmodels if x.ID == mixing_model_id), None + ) material = next((x for x in grid.materials if x.ID == mixing_model_id), None) nbins = n_materials if mixingmodel: if nbins == 1: - logger.exception(f"{self.__str__()} must be used with more than one material from the mixing model.") + logger.exception( + f"{self.__str__()} must be used with more than one material from the mixing model." + ) raise ValueError if isinstance(mixingmodel, ListMaterial) and nbins > len(mixingmodel.mat): logger.exception( - f"{self.__str__()} too many materials/bins " "requested compared to materials in " "mixing model." + f"{self.__str__()} too many materials/bins " + "requested compared to materials in " + "mixing model." ) raise ValueError # Create materials from mixing model as number of bins now known # from fractal_box command. mixingmodel.calculate_properties(nbins, grid) elif not material: - logger.exception(f"{self.__str__()} mixing model or material with " + "ID {mixing_model_id} does not exist") + logger.exception( + f"{self.__str__()} mixing model or material with " + + "ID {mixing_model_id} does not exist" + ) raise ValueError self.volume = FractalVolume(xs, xf, ys, yf, zs, zf, frac_dim, seed) @@ -223,7 +243,11 @@ class FractalBox(UserObjectGeometry): (self.volume.nx, self.volume.ny, self.volume.nz), dtype=config.sim_config.dtypes["float_or_double"], ) - materialnumID = next(x.numID for x in grid.materials if x.ID == self.volume.operatingonID) + materialnumID = next( + x.numID + for x in grid.materials + if x.ID == self.volume.operatingonID + ) self.volume.fractalvolume *= materialnumID else: self.volume.generate_fractal_volume() @@ -231,7 +255,9 @@ class FractalBox(UserObjectGeometry): for j in range(0, self.volume.ny): for k in range(0, self.volume.nz): numberinbin = self.volume.fractalvolume[i, j, k] - self.volume.fractalvolume[i, j, k] = self.volume.mixingmodel.matID[int(numberinbin)] + self.volume.fractalvolume[i, j, k] = ( + self.volume.mixingmodel.matID[int(numberinbin)] + ) self.volume.generate_volume_mask() @@ -240,32 +266,69 @@ class FractalBox(UserObjectGeometry): # TODO: Allow extract of rough surface profile (to print/file?) for surface in self.volume.fractalsurfaces: if surface.surfaceID == "xminus": - for i in range(surface.fractalrange[0], surface.fractalrange[1]): + for i in range( + surface.fractalrange[0], surface.fractalrange[1] + ): for j in range(surface.ys, surface.yf): for k in range(surface.zs, surface.zf): - if i > surface.fractalsurface[j - surface.ys, k - surface.zs]: - self.volume.mask[i - self.volume.xs, j - self.volume.ys, k - self.volume.zs] = 1 - elif surface.filldepth > 0 and i > surface.filldepth: - self.volume.mask[i - self.volume.xs, j - self.volume.ys, k - self.volume.zs] = 2 + if ( + i + > surface.fractalsurface[ + j - surface.ys, k - surface.zs + ] + ): + self.volume.mask[ + i - self.volume.xs, + j - self.volume.ys, + k - self.volume.zs, + ] = 1 + elif ( + surface.filldepth > 0 and i > surface.filldepth + ): + self.volume.mask[ + i - self.volume.xs, + j - self.volume.ys, + k - self.volume.zs, + ] = 2 else: - self.volume.mask[i - self.volume.xs, j - self.volume.ys, k - self.volume.zs] = 0 + self.volume.mask[ + i - self.volume.xs, + j - self.volume.ys, + k - self.volume.zs, + ] = 0 elif surface.surfaceID == "xplus": if not surface.ID: - for i in range(surface.fractalrange[0], surface.fractalrange[1]): + for i in range( + surface.fractalrange[0], surface.fractalrange[1] + ): for j in range(surface.ys, surface.yf): for k in range(surface.zs, surface.zf): - if i < surface.fractalsurface[j - surface.ys, k - surface.zs]: + if ( + i + < surface.fractalsurface[ + j - surface.ys, k - surface.zs + ] + ): self.volume.mask[ - i - self.volume.xs, j - self.volume.ys, k - self.volume.zs + i - self.volume.xs, + j - self.volume.ys, + k - self.volume.zs, ] = 1 - elif surface.filldepth > 0 and i < surface.filldepth: + elif ( + surface.filldepth > 0 + and i < surface.filldepth + ): self.volume.mask[ - i - self.volume.xs, j - self.volume.ys, k - self.volume.zs + i - self.volume.xs, + j - self.volume.ys, + k - self.volume.zs, ] = 2 else: self.volume.mask[ - i - self.volume.xs, j - self.volume.ys, k - self.volume.zs + i - self.volume.xs, + j - self.volume.ys, + k - self.volume.zs, ] = 0 elif surface.ID == "grass": g = surface.grass[0] @@ -273,17 +336,31 @@ class FractalBox(UserObjectGeometry): blade = 0 for j in range(surface.ys, surface.yf): for k in range(surface.zs, surface.zf): - if surface.fractalsurface[j - surface.ys, k - surface.zs] > 0: + if ( + surface.fractalsurface[ + j - surface.ys, k - surface.zs + ] + > 0 + ): height = 0 - for i in range(self.volume.xs, surface.fractalrange[1]): + for i in range( + self.volume.xs, surface.fractalrange[1] + ): if ( - i < surface.fractalsurface[j - surface.ys, k - surface.zs] + i + < surface.fractalsurface[ + j - surface.ys, k - surface.zs + ] and self.volume.mask[ - i - self.volume.xs, j - self.volume.ys, k - self.volume.zs + i - self.volume.xs, + j - self.volume.ys, + k - self.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 yy = int(j - self.volume.ys + y) zz = int(k - self.volume.zs + z) @@ -297,7 +374,9 @@ class FractalBox(UserObjectGeometry): ): break else: - self.volume.mask[i - self.volume.xs, yy, zz] = 3 + self.volume.mask[ + i - self.volume.xs, yy, zz + ] = 3 height += 1 blade += 1 @@ -305,7 +384,12 @@ class FractalBox(UserObjectGeometry): root = 0 for j in range(surface.ys, surface.yf): for k in range(surface.zs, surface.zf): - if surface.fractalsurface[j - surface.ys, k - surface.zs] > 0: + if ( + surface.fractalsurface[ + j - surface.ys, k - surface.zs + ] + > 0 + ): depth = 0 i = self.volume.xf - 1 while i > self.volume.xs: @@ -313,15 +397,21 @@ class FractalBox(UserObjectGeometry): i > self.volume.originalxf - ( - surface.fractalsurface[j - surface.ys, k - surface.zs] + surface.fractalsurface[ + j - surface.ys, k - surface.zs + ] - self.volume.originalxf ) and self.volume.mask[ - i - self.volume.xs, j - self.volume.ys, k - self.volume.zs + i - self.volume.xs, + j - self.volume.ys, + k - self.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 yy = int(j - self.volume.ys + y) zz = int(k - self.volume.zs + z) @@ -335,38 +425,77 @@ class FractalBox(UserObjectGeometry): ): break else: - self.volume.mask[i - self.volume.xs, yy, zz] = 3 + self.volume.mask[ + i - self.volume.xs, yy, zz + ] = 3 depth += 1 i -= 1 root += 1 elif surface.surfaceID == "yminus": for i in range(surface.xs, surface.xf): - for j in range(surface.fractalrange[0], surface.fractalrange[1]): + for j in range( + surface.fractalrange[0], surface.fractalrange[1] + ): for k in range(surface.zs, surface.zf): - if j > surface.fractalsurface[i - surface.xs, k - surface.zs]: - self.volume.mask[i - self.volume.xs, j - self.volume.ys, k - self.volume.zs] = 1 - elif surface.filldepth > 0 and j > surface.filldepth: - self.volume.mask[i - self.volume.xs, j - self.volume.ys, k - self.volume.zs] = 2 + if ( + j + > surface.fractalsurface[ + i - surface.xs, k - surface.zs + ] + ): + self.volume.mask[ + i - self.volume.xs, + j - self.volume.ys, + k - self.volume.zs, + ] = 1 + elif ( + surface.filldepth > 0 and j > surface.filldepth + ): + self.volume.mask[ + i - self.volume.xs, + j - self.volume.ys, + k - self.volume.zs, + ] = 2 else: - self.volume.mask[i - self.volume.xs, j - self.volume.ys, k - self.volume.zs] = 0 + self.volume.mask[ + i - self.volume.xs, + j - self.volume.ys, + k - self.volume.zs, + ] = 0 elif surface.surfaceID == "yplus": if not surface.ID: for i in range(surface.xs, surface.xf): - for j in range(surface.fractalrange[0], surface.fractalrange[1]): + for j in range( + surface.fractalrange[0], surface.fractalrange[1] + ): for k in range(surface.zs, surface.zf): - if j < surface.fractalsurface[i - surface.xs, k - surface.zs]: + if ( + j + < surface.fractalsurface[ + i - surface.xs, k - surface.zs + ] + ): self.volume.mask[ - i - self.volume.xs, j - self.volume.ys, k - self.volume.zs + i - self.volume.xs, + j - self.volume.ys, + k - self.volume.zs, ] = 1 - elif surface.filldepth > 0 and j < surface.filldepth: + elif ( + surface.filldepth > 0 + and j < surface.filldepth + ): self.volume.mask[ - i - self.volume.xs, j - self.volume.ys, k - self.volume.zs + i - self.volume.xs, + j - self.volume.ys, + k - self.volume.zs, ] = 2 else: self.volume.mask[ - i - self.volume.xs, j - self.volume.ys, k - self.volume.zs + i - self.volume.xs, + j - self.volume.ys, + k - self.volume.zs, ] = 0 elif surface.ID == "grass": g = surface.grass[0] @@ -374,17 +503,31 @@ class FractalBox(UserObjectGeometry): blade = 0 for i in range(surface.xs, surface.xf): for k in range(surface.zs, surface.zf): - if surface.fractalsurface[i - surface.xs, k - surface.zs] > 0: + if ( + surface.fractalsurface[ + i - surface.xs, k - surface.zs + ] + > 0 + ): height = 0 - for j in range(self.volume.ys, surface.fractalrange[1]): + for j in range( + self.volume.ys, surface.fractalrange[1] + ): if ( - j < surface.fractalsurface[i - surface.xs, k - surface.zs] + j + < surface.fractalsurface[ + i - surface.xs, k - surface.zs + ] and self.volume.mask[ - i - self.volume.xs, j - self.volume.ys, k - self.volume.zs + i - self.volume.xs, + j - self.volume.ys, + k - self.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 xx = int(i - self.volume.xs + x) zz = int(k - self.volume.zs + z) @@ -398,7 +541,9 @@ class FractalBox(UserObjectGeometry): ): break else: - self.volume.mask[xx, j - self.volume.ys, zz] = 3 + self.volume.mask[ + xx, j - self.volume.ys, zz + ] = 3 height += 1 blade += 1 @@ -406,7 +551,12 @@ class FractalBox(UserObjectGeometry): root = 0 for i in range(surface.xs, surface.xf): for k in range(surface.zs, surface.zf): - if surface.fractalsurface[i - surface.xs, k - surface.zs] > 0: + if ( + surface.fractalsurface[ + i - surface.xs, k - surface.zs + ] + > 0 + ): depth = 0 j = self.volume.yf - 1 while j > self.volume.ys: @@ -414,15 +564,21 @@ class FractalBox(UserObjectGeometry): j > self.volume.originalyf - ( - surface.fractalsurface[i - surface.xs, k - surface.zs] + surface.fractalsurface[ + i - surface.xs, k - surface.zs + ] - self.volume.originalyf ) and self.volume.mask[ - i - self.volume.xs, j - self.volume.ys, k - self.volume.zs + i - self.volume.xs, + j - self.volume.ys, + k - self.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 xx = int(i - self.volume.xs + x) zz = int(k - self.volume.zs + z) @@ -436,7 +592,9 @@ class FractalBox(UserObjectGeometry): ): break else: - self.volume.mask[xx, j - self.volume.ys, zz] = 3 + self.volume.mask[ + xx, j - self.volume.ys, zz + ] = 3 depth += 1 j -= 1 root += 1 @@ -444,30 +602,67 @@ class FractalBox(UserObjectGeometry): elif surface.surfaceID == "zminus": for i in range(surface.xs, surface.xf): for j in range(surface.ys, surface.yf): - for k in range(surface.fractalrange[0], surface.fractalrange[1]): - if k > surface.fractalsurface[i - surface.xs, j - surface.ys]: - self.volume.mask[i - self.volume.xs, j - self.volume.ys, k - self.volume.zs] = 1 - elif surface.filldepth > 0 and k > surface.filldepth: - self.volume.mask[i - self.volume.xs, j - self.volume.ys, k - self.volume.zs] = 2 + for k in range( + surface.fractalrange[0], surface.fractalrange[1] + ): + if ( + k + > surface.fractalsurface[ + i - surface.xs, j - surface.ys + ] + ): + self.volume.mask[ + i - self.volume.xs, + j - self.volume.ys, + k - self.volume.zs, + ] = 1 + elif ( + surface.filldepth > 0 and k > surface.filldepth + ): + self.volume.mask[ + i - self.volume.xs, + j - self.volume.ys, + k - self.volume.zs, + ] = 2 else: - self.volume.mask[i - self.volume.xs, j - self.volume.ys, k - self.volume.zs] = 0 + self.volume.mask[ + i - self.volume.xs, + j - self.volume.ys, + k - self.volume.zs, + ] = 0 elif surface.surfaceID == "zplus": if not surface.ID: for i in range(surface.xs, surface.xf): for j in range(surface.ys, surface.yf): - for k in range(surface.fractalrange[0], surface.fractalrange[1]): - if k < surface.fractalsurface[i - surface.xs, j - surface.ys]: + for k in range( + surface.fractalrange[0], surface.fractalrange[1] + ): + if ( + k + < surface.fractalsurface[ + i - surface.xs, j - surface.ys + ] + ): self.volume.mask[ - i - self.volume.xs, j - self.volume.ys, k - self.volume.zs + i - self.volume.xs, + j - self.volume.ys, + k - self.volume.zs, ] = 1 - elif surface.filldepth > 0 and k < surface.filldepth: + elif ( + surface.filldepth > 0 + and k < surface.filldepth + ): self.volume.mask[ - i - self.volume.xs, j - self.volume.ys, k - self.volume.zs + i - self.volume.xs, + j - self.volume.ys, + k - self.volume.zs, ] = 2 else: self.volume.mask[ - i - self.volume.xs, j - self.volume.ys, k - self.volume.zs + i - self.volume.xs, + j - self.volume.ys, + k - self.volume.zs, ] = 0 elif surface.ID == "grass": g = surface.grass[0] @@ -475,17 +670,31 @@ class FractalBox(UserObjectGeometry): blade = 0 for i in range(surface.xs, surface.xf): for j in range(surface.ys, surface.yf): - if surface.fractalsurface[i - surface.xs, j - surface.ys] > 0: + if ( + surface.fractalsurface[ + i - surface.xs, j - surface.ys + ] + > 0 + ): height = 0 - for k in range(self.volume.zs, surface.fractalrange[1]): + for k in range( + self.volume.zs, surface.fractalrange[1] + ): if ( - k < surface.fractalsurface[i - surface.xs, j - surface.ys] + k + < surface.fractalsurface[ + i - surface.xs, j - surface.ys + ] and self.volume.mask[ - i - self.volume.xs, j - self.volume.ys, k - self.volume.zs + i - self.volume.xs, + j - self.volume.ys, + k - self.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 xx = int(i - self.volume.xs + x) yy = int(j - self.volume.ys + y) @@ -499,7 +708,9 @@ class FractalBox(UserObjectGeometry): ): break else: - self.volume.mask[xx, yy, k - self.volume.zs] = 3 + self.volume.mask[ + xx, yy, k - self.volume.zs + ] = 3 height += 1 blade += 1 @@ -507,7 +718,12 @@ class FractalBox(UserObjectGeometry): root = 0 for i in range(surface.xs, surface.xf): for j in range(surface.ys, surface.yf): - if surface.fractalsurface[i - surface.xs, j - surface.ys] > 0: + if ( + surface.fractalsurface[ + i - surface.xs, j - surface.ys + ] + > 0 + ): depth = 0 k = self.volume.zf - 1 while k > self.volume.zs: @@ -515,15 +731,21 @@ class FractalBox(UserObjectGeometry): k > self.volume.originalzf - ( - surface.fractalsurface[i - surface.xs, j - surface.ys] + surface.fractalsurface[ + i - surface.xs, j - surface.ys + ] - self.volume.originalzf ) and self.volume.mask[ - i - self.volume.xs, j - self.volume.ys, k - self.volume.zs + i - self.volume.xs, + j - self.volume.ys, + k - self.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 xx = int(i - self.volume.xs + x) yy = int(j - self.volume.ys + y) @@ -537,14 +759,20 @@ class FractalBox(UserObjectGeometry): ): break else: - self.volume.mask[xx, yy, k - self.volume.zs] = 3 + self.volume.mask[ + xx, yy, k - self.volume.zs + ] = 3 depth += 1 k -= 1 root += 1 # Build voxels from any true values of the 3D mask array - waternumID = next((x.numID for x in grid.materials if x.ID == "water"), 0) - grassnumID = next((x.numID for x in grid.materials if x.ID == "grass"), 0) + waternumID = next( + (x.numID for x in grid.materials if x.ID == "water"), 0 + ) + grassnumID = next( + (x.numID for x in grid.materials if x.ID == "grass"), 0 + ) data = self.volume.fractalvolume.astype("int16", order="C") mask = self.volume.mask.copy(order="C") build_voxels_from_array_mask( @@ -576,7 +804,9 @@ class FractalBox(UserObjectGeometry): for j in range(0, self.volume.ny): for k in range(0, self.volume.nz): numberinbin = self.volume.fractalvolume[i, j, k] - self.volume.fractalvolume[i, j, k] = self.volume.mixingmodel.matID[int(numberinbin)] + self.volume.fractalvolume[i, j, k] = ( + self.volume.mixingmodel.matID[int(numberinbin)] + ) data = self.volume.fractalvolume.astype("int16", order="C") build_voxels_from_array( diff --git a/gprMax/cmds_geometry/geometry_objects_read.py b/gprMax/cmds_geometry/geometry_objects_read.py index 9149dcff..2839c056 100644 --- a/gprMax/cmds_geometry/geometry_objects_read.py +++ b/gprMax/cmds_geometry/geometry_objects_read.py @@ -71,7 +71,11 @@ class GeometryObjectsRead(UserObjectGeometry): materials = [ line.rstrip() + "{" + matstr + "}\n" for line in f - if (line.startswith("#") and not line.startswith("##") and line.rstrip("\n")) + if ( + line.startswith("#") + and not line.startswith("##") + and line.rstrip("\n") + ) ] # Build scene @@ -126,12 +130,26 @@ class GeometryObjectsRead(UserObjectGeometry): rigidE = f["/rigidE"][:] rigidH = f["/rigidH"][:] ID = f["/ID"][:] - grid.solid[xs : xs + data.shape[0], ys : ys + data.shape[1], zs : zs + data.shape[2]] = ( - data + numexistmaterials - ) - grid.rigidE[:, xs : xs + rigidE.shape[1], ys : ys + rigidE.shape[2], zs : zs + rigidE.shape[3]] = rigidE - grid.rigidH[:, xs : xs + rigidH.shape[1], ys : ys + rigidH.shape[2], zs : zs + rigidH.shape[3]] = rigidH - grid.ID[:, xs : xs + ID.shape[1], ys : ys + ID.shape[2], zs : zs + ID.shape[3]] = ID + numexistmaterials + grid.solid[ + xs : xs + data.shape[0], + ys : ys + data.shape[1], + zs : zs + data.shape[2], + ] = data + numexistmaterials + grid.rigidE[ + :, + xs : xs + rigidE.shape[1], + ys : ys + rigidE.shape[2], + zs : zs + rigidE.shape[3], + ] = rigidE + grid.rigidH[ + :, + xs : xs + rigidH.shape[1], + ys : ys + rigidH.shape[2], + zs : zs + rigidH.shape[3], + ] = rigidH + grid.ID[ + :, xs : xs + ID.shape[1], ys : ys + ID.shape[2], zs : zs + ID.shape[3] + ] = ID + numexistmaterials logger.info( f"{self.grid_name(grid)}Geometry objects from file {geofile} " f"inserted at {xs * grid.dx:g}m, {ys * grid.dy:g}m, " diff --git a/gprMax/cmds_geometry/plate.py b/gprMax/cmds_geometry/plate.py index d2e5dcc6..f93ea757 100644 --- a/gprMax/cmds_geometry/plate.py +++ b/gprMax/cmds_geometry/plate.py @@ -20,8 +20,7 @@ import logging import numpy as np -from ..cython.geometry_primitives import (build_face_xy, build_face_xz, - build_face_yz) +from ..cython.geometry_primitives import build_face_xy, build_face_xz, build_face_yz from .cmds_geometry import UserObjectGeometry, rotate_2point_object logger = logging.getLogger(__name__) @@ -115,7 +114,9 @@ class Plate(UserObjectGeometry): for j in range(ys, yf): for k in range(zs, zf): - build_face_yz(xs, j, k, numIDy, numIDz, grid.rigidE, grid.rigidH, grid.ID) + build_face_yz( + xs, j, k, numIDy, numIDz, grid.rigidE, grid.rigidH, grid.ID + ) # xz-plane plate elif ys == yf: @@ -130,7 +131,9 @@ class Plate(UserObjectGeometry): for i in range(xs, xf): for k in range(zs, zf): - build_face_xz(i, ys, k, numIDx, numIDz, grid.rigidE, grid.rigidH, grid.ID) + build_face_xz( + i, ys, k, numIDx, numIDz, grid.rigidE, grid.rigidH, grid.ID + ) # xy-plane plate elif zs == zf: @@ -145,7 +148,9 @@ class Plate(UserObjectGeometry): for i in range(xs, xf): for j in range(ys, yf): - build_face_xy(i, j, zs, numIDx, numIDy, grid.rigidE, grid.rigidH, grid.ID) + build_face_xy( + i, j, zs, numIDx, numIDy, grid.rigidE, grid.rigidH, grid.ID + ) logger.info( f"{self.grid_name(grid)}Plate from {p3[0]:g}m, {p3[1]:g}m, " diff --git a/gprMax/cmds_geometry/sphere.py b/gprMax/cmds_geometry/sphere.py index 92ec2660..837aa6d2 100644 --- a/gprMax/cmds_geometry/sphere.py +++ b/gprMax/cmds_geometry/sphere.py @@ -104,10 +104,18 @@ class Sphere(UserObjectGeometry): m = Material(numID, requiredID) m.type = "dielectric-smoothed" # Create dielectric-smoothed constituents for material - m.er = np.mean((materials[0].er, materials[1].er, materials[2].er), axis=0) - m.se = np.mean((materials[0].se, materials[1].se, materials[2].se), axis=0) - m.mr = np.mean((materials[0].mr, materials[1].mr, materials[2].mr), axis=0) - m.sm = np.mean((materials[0].sm, materials[1].sm, materials[2].sm), axis=0) + m.er = np.mean( + (materials[0].er, materials[1].er, materials[2].er), axis=0 + ) + m.se = np.mean( + (materials[0].se, materials[1].se, materials[2].se), axis=0 + ) + m.mr = np.mean( + (materials[0].mr, materials[1].mr, materials[2].mr), axis=0 + ) + m.sm = np.mean( + (materials[0].sm, materials[1].sm, materials[2].sm), axis=0 + ) # Append the new material object to the materials list grid.materials.append(m) diff --git a/gprMax/cmds_geometry/triangle.py b/gprMax/cmds_geometry/triangle.py index baef1e93..345dfeb7 100644 --- a/gprMax/cmds_geometry/triangle.py +++ b/gprMax/cmds_geometry/triangle.py @@ -109,7 +109,9 @@ class Triangle(UserObjectGeometry): x3, y3, z3 = uip.round_to_grid(up3) if thickness < 0: - logger.exception(f"{self.__str__()} requires a positive value for thickness") + logger.exception( + f"{self.__str__()} requires a positive value for thickness" + ) raise ValueError # Check for valid orientations @@ -123,7 +125,9 @@ class Triangle(UserObjectGeometry): elif z1 == z2 == z3: normal = "z" else: - logger.exception(f"{self.__str__()} the triangle is not specified correctly") + logger.exception( + f"{self.__str__()} the triangle is not specified correctly" + ) raise ValueError # Look up requested materials in existing list of material instances @@ -146,7 +150,9 @@ class Triangle(UserObjectGeometry): numIDx = materials[0].numID numIDy = materials[1].numID numIDz = materials[2].numID - requiredID = materials[0].ID + "+" + materials[1].ID + "+" + materials[2].ID + requiredID = ( + materials[0].ID + "+" + materials[1].ID + "+" + materials[2].ID + ) averagedmaterial = [x for x in grid.materials if x.ID == requiredID] if averagedmaterial: numID = averagedmaterial.numID @@ -155,10 +161,18 @@ class Triangle(UserObjectGeometry): m = Material(numID, requiredID) m.type = "dielectric-smoothed" # Create dielectric-smoothed constituents for material - m.er = np.mean((materials[0].er, materials[1].er, materials[2].er), axis=0) - m.se = np.mean((materials[0].se, materials[1].se, materials[2].se), axis=0) - m.mr = np.mean((materials[0].mr, materials[1].mr, materials[2].mr), axis=0) - m.sm = np.mean((materials[0].sm, materials[1].sm, materials[2].sm), axis=0) + m.er = np.mean( + (materials[0].er, materials[1].er, materials[2].er), axis=0 + ) + m.se = np.mean( + (materials[0].se, materials[1].se, materials[2].se), axis=0 + ) + m.mr = np.mean( + (materials[0].mr, materials[1].mr, materials[2].mr), axis=0 + ) + m.sm = np.mean( + (materials[0].sm, materials[1].sm, materials[2].sm), axis=0 + ) # Append the new material object to the materials list grid.materials.append(m) diff --git a/gprMax/cmds_multiuse.py b/gprMax/cmds_multiuse.py index 4ef5ff3b..0d2f1074 100644 --- a/gprMax/cmds_multiuse.py +++ b/gprMax/cmds_multiuse.py @@ -25,9 +25,11 @@ from scipy import interpolate import gprMax.config as config -from .cmds_geometry.cmds_geometry import (UserObjectGeometry, - rotate_2point_object, - rotate_polarisation) +from .cmds_geometry.cmds_geometry import ( + UserObjectGeometry, + rotate_2point_object, + rotate_polarisation, +) from .geometry_outputs import GeometryObjects as GeometryObjectsUser from .materials import DispersiveMaterial as DispersiveMaterialUser from .materials import ListMaterial as ListMaterialUser @@ -118,16 +120,22 @@ class ExcitationFile(UserObjectMulti): try: excitationfile = self.kwargs["filepath"] fullargspec = inspect.getfullargspec(interpolate.interp1d) - kwargs = dict(zip(reversed(fullargspec.args), reversed(fullargspec.defaults))) + kwargs = dict( + zip(reversed(fullargspec.args), reversed(fullargspec.defaults)) + ) except KeyError: - logger.exception(f"{self.__str__()} requires either one or three parameter(s)") + logger.exception( + f"{self.__str__()} requires either one or three parameter(s)" + ) raise # See if file exists at specified path and if not try input file directory excitationfile = Path(excitationfile) # excitationfile = excitationfile.resolve() if not excitationfile.exists(): - excitationfile = Path(config.sim_config.input_file_path.parent, excitationfile) + excitationfile = Path( + config.sim_config.input_file_path.parent, excitationfile + ) logger.info(self.grid_name(grid) + f"Excitation file: {excitationfile}") @@ -135,7 +143,11 @@ class ExcitationFile(UserObjectMulti): waveformIDs = np.loadtxt(excitationfile, max_rows=1, dtype=str) # Read all waveform values into an array - waveformvalues = np.loadtxt(excitationfile, skiprows=1, dtype=config.sim_config.dtypes["float_or_double"]) + waveformvalues = np.loadtxt( + excitationfile, + skiprows=1, + dtype=config.sim_config.dtypes["float_or_double"], + ) # Time array (if specified) for interpolation, otherwise use simulation time if waveformIDs[0].lower() == "time": @@ -156,7 +168,11 @@ class ExcitationFile(UserObjectMulti): w.type = "user" # Select correct column of waveform values depending on array shape - singlewaveformvalues = waveformvalues[:] if len(waveformvalues.shape) == 1 else waveformvalues[:, i] + singlewaveformvalues = ( + waveformvalues[:] + if len(waveformvalues.shape) == 1 + else waveformvalues[:, i] + ) # Truncate waveform array if it is longer than time array if len(singlewaveformvalues) > len(waveformtime): @@ -171,10 +187,13 @@ class ExcitationFile(UserObjectMulti): ) # Interpolate waveform values - w.userfunc = interpolate.interp1d(waveformtime, singlewaveformvalues, **kwargs) + w.userfunc = interpolate.interp1d( + waveformtime, singlewaveformvalues, **kwargs + ) logger.info( - self.grid_name(grid) + f"User waveform {w.ID} created using {timestr} and, if " + self.grid_name(grid) + + f"User waveform {w.ID} created using {timestr} and, if " f"required, interpolation parameters (kind: {kwargs['kind']}, " f"fill value: {kwargs['fill_value']})." ) @@ -222,15 +241,19 @@ class Waveform(UserObjectMulti): freq = self.kwargs["freq"] ID = self.kwargs["id"] except KeyError: - logger.exception(self.params_str() + (" builtin waveforms " "require exactly four parameters.")) + logger.exception( + self.params_str() + + (" builtin waveforms require exactly four parameters.") + ) raise if freq <= 0: logger.exception( - self.params_str() + (" requires an excitation " "frequency value of greater than zero.") + self.params_str() + + (" requires an excitation frequency value of greater than zero.") ) raise ValueError if any(x.ID == ID for x in grid.waveforms): - logger.exception(self.params_str() + (f" with ID {ID} already " "exists.")) + logger.exception(self.params_str() + (f" with ID {ID} already exists.")) raise ValueError w = WaveformUser() @@ -253,9 +276,14 @@ class Waveform(UserObjectMulti): uservalues = self.kwargs["user_values"] ID = self.kwargs["id"] fullargspec = inspect.getfullargspec(interpolate.interp1d) - kwargs = dict(zip(reversed(fullargspec.args), reversed(fullargspec.defaults))) + kwargs = dict( + zip(reversed(fullargspec.args), reversed(fullargspec.defaults)) + ) except KeyError: - logger.exception(self.params_str() + (" a user-defined " "waveform requires at least two parameters.")) + logger.exception( + self.params_str() + + (" a user-defined waveform requires at least two parameters.") + ) raise if "user_time" in self.kwargs: @@ -270,7 +298,7 @@ class Waveform(UserObjectMulti): kwargs["fill_value"] = self.kwargs["fill_value"] if any(x.ID == ID for x in grid.waveforms): - logger.exception(self.params_str() + (f" with ID {ID} already " "exists.")) + logger.exception(self.params_str() + (f" with ID {ID} already exists.")) raise ValueError w = WaveformUser() @@ -278,7 +306,10 @@ class Waveform(UserObjectMulti): w.type = wavetype w.userfunc = interpolate.interp1d(waveformtime, uservalues, **kwargs) - logger.info(self.grid_name(grid) + (f"Waveform {w.ID} that is " "user-defined created.")) + logger.info( + self.grid_name(grid) + + (f"Waveform {w.ID} that is user-defined created.") + ) grid.waveforms.append(w) @@ -323,7 +354,7 @@ class VoltageSource(UserObjectMulti): resistance = self.kwargs["resistance"] waveform_id = self.kwargs["waveform_id"] except KeyError: - logger.exception(self.params_str() + (" requires at least six " "parameters.")) + logger.exception(self.params_str() + (" requires at least six parameters.")) raise if self.do_rotate: @@ -331,37 +362,49 @@ class VoltageSource(UserObjectMulti): # Check polarity & position parameters if polarisation not in ("x", "y", "z"): - logger.exception(self.params_str() + (" polarisation must be " "x, y, or z.")) + logger.exception(self.params_str() + (" polarisation must be x, y, or z.")) raise ValueError if "2D TMx" in config.get_model_config().mode and polarisation in [ "y", "z", ]: - logger.exception(self.params_str() + (" polarisation must be x in " "2D TMx mode.")) + logger.exception( + self.params_str() + (" polarisation must be x in 2D TMx mode.") + ) raise ValueError elif "2D TMy" in config.get_model_config().mode and polarisation in [ "x", "z", ]: - logger.exception(self.params_str() + (" polarisation must be y in " "2D TMy mode.")) + logger.exception( + self.params_str() + (" polarisation must be y in 2D TMy mode.") + ) raise ValueError elif "2D TMz" in config.get_model_config().mode and polarisation in [ "x", "y", ]: - logger.exception(self.params_str() + (" polarisation must be z in " "2D TMz mode.")) + logger.exception( + self.params_str() + (" polarisation must be z in 2D TMz mode.") + ) raise ValueError xcoord, ycoord, zcoord = uip.check_src_rx_point(p1, self.params_str()) p2 = uip.round_to_grid_static_point(p1) if resistance < 0: - logger.exception(self.params_str() + (" requires a source " "resistance of zero " "or greater.")) + logger.exception( + self.params_str() + + (" requires a source resistance of zero or greater.") + ) raise ValueError # Check if there is a waveformID in the waveforms list if not any(x.ID == waveform_id for x in grid.waveforms): - logger.exception(self.params_str() + (" there is no waveform with " "the identifier {waveform_id}.")) + logger.exception( + self.params_str() + + (" there is no waveform with the identifier {waveform_id}.") + ) raise ValueError v = VoltageSourceUser() @@ -369,7 +412,16 @@ class VoltageSource(UserObjectMulti): v.xcoord = xcoord v.ycoord = ycoord v.zcoord = zcoord - v.ID = v.__class__.__name__ + "(" + str(v.xcoord) + "," + str(v.ycoord) + "," + str(v.zcoord) + ")" + v.ID = ( + v.__class__.__name__ + + "(" + + str(v.xcoord) + + "," + + str(v.ycoord) + + "," + + str(v.zcoord) + + ")" + ) v.resistance = resistance v.waveformID = waveform_id @@ -379,14 +431,25 @@ class VoltageSource(UserObjectMulti): # Check source start & source remove time parameters if start < 0: logger.exception( - self.params_str() + (" delay of the initiation " "of the source should not " "be less than zero.") + self.params_str() + + ( + " delay of the initiation " + "of the source should not " + "be less than zero." + ) ) raise ValueError if stop < 0: - logger.exception(self.params_str() + (" time to remove the " "source should not be " "less than zero.")) + logger.exception( + self.params_str() + + (" time to remove the source should not be less than zero.") + ) raise ValueError if stop - start <= 0: - logger.exception(self.params_str() + (" duration of the source " "should not be zero or " "less.")) + logger.exception( + self.params_str() + + (" duration of the source should not be zero or less.") + ) raise ValueError v.start = start v.stop = min(stop, grid.timewindow) @@ -401,7 +464,9 @@ class VoltageSource(UserObjectMulti): logger.info( f"{self.grid_name(grid)}Voltage source with polarity " f"{v.polarisation} at {p2[0]:g}m, {p2[1]:g}m, {p2[2]:g}m, " - f"resistance {v.resistance:.1f} Ohms," + startstop + f"using waveform {v.waveformID} created." + f"resistance {v.resistance:.1f} Ohms," + + startstop + + f"using waveform {v.waveformID} created." ) grid.voltagesources.append(v) @@ -454,25 +519,31 @@ class HertzianDipole(UserObjectMulti): # Check polarity & position parameters if polarisation not in ("x", "y", "z"): - logger.exception(self.params_str() + (" polarisation must be " "x, y, or z.")) + logger.exception(self.params_str() + (" polarisation must be x, y, or z.")) raise ValueError if "2D TMx" in config.get_model_config().mode and polarisation in [ "y", "z", ]: - logger.exception(self.params_str() + (" polarisation must be x in " "2D TMx mode.")) + logger.exception( + self.params_str() + (" polarisation must be x in 2D TMx mode.") + ) raise ValueError elif "2D TMy" in config.get_model_config().mode and polarisation in [ "x", "z", ]: - logger.exception(self.params_str() + (" polarisation must be y in " "2D TMy mode.")) + logger.exception( + self.params_str() + (" polarisation must be y in 2D TMy mode.") + ) raise ValueError elif "2D TMz" in config.get_model_config().mode and polarisation in [ "x", "y", ]: - logger.exception(self.params_str() + (" polarisation must be z in " "2D TMz mode.")) + logger.exception( + self.params_str() + (" polarisation must be z in 2D TMz mode.") + ) raise ValueError xcoord, ycoord, zcoord = uip.check_src_rx_point(p1, self.params_str()) @@ -480,7 +551,9 @@ class HertzianDipole(UserObjectMulti): # Check if there is a waveformID in the waveforms list if not any(x.ID == waveform_id for x in grid.waveforms): - logger.exception(f"{self.params_str()} there is no waveform with the identifier {waveform_id}.") + logger.exception( + f"{self.params_str()} there is no waveform with the identifier {waveform_id}." + ) raise ValueError h = HertzianDipoleUser() @@ -500,7 +573,9 @@ class HertzianDipole(UserObjectMulti): h.xcoordorigin = xcoord h.ycoordorigin = ycoord h.zcoordorigin = zcoord - h.ID = f"{h.__class__.__name__}({str(h.xcoord)},{str(h.ycoord)},{str(h.zcoord)})" + h.ID = ( + f"{h.__class__.__name__}({str(h.xcoord)},{str(h.ycoord)},{str(h.zcoord)})" + ) h.waveformID = waveform_id try: @@ -513,10 +588,14 @@ class HertzianDipole(UserObjectMulti): ) raise ValueError if stop < 0: - logger.exception(f"{self.params_str()} time to remove the source should not be less than zero.") + logger.exception( + f"{self.params_str()} time to remove the source should not be less than zero." + ) raise ValueError if stop - start <= 0: - logger.exception(f"{self.params_str()} duration of the source should not be zero or less.") + logger.exception( + f"{self.params_str()} duration of the source should not be zero or less." + ) raise ValueError h.start = start h.stop = min(stop, grid.timewindow) @@ -595,25 +674,31 @@ class MagneticDipole(UserObjectMulti): # Check polarity & position parameters if polarisation not in ("x", "y", "z"): - logger.exception(self.params_str() + (" polarisation must be " "x, y, or z.")) + logger.exception(self.params_str() + (" polarisation must be x, y, or z.")) raise ValueError if "2D TMx" in config.get_model_config().mode and polarisation in [ "y", "z", ]: - logger.exception(self.params_str() + (" polarisation must be x in " "2D TMx mode.")) + logger.exception( + self.params_str() + (" polarisation must be x in 2D TMx mode.") + ) raise ValueError elif "2D TMy" in config.get_model_config().mode and polarisation in [ "x", "z", ]: - logger.exception(self.params_str() + (" polarisation must be y in " "2D TMy mode.")) + logger.exception( + self.params_str() + (" polarisation must be y in 2D TMy mode.") + ) raise ValueError elif "2D TMz" in config.get_model_config().mode and polarisation in [ "x", "y", ]: - logger.exception(self.params_str() + (" polarisation must be z in " "2D TMz mode.")) + logger.exception( + self.params_str() + (" polarisation must be z in 2D TMz mode.") + ) raise ValueError xcoord, ycoord, zcoord = uip.check_src_rx_point(p1, self.params_str()) @@ -621,7 +706,9 @@ class MagneticDipole(UserObjectMulti): # Check if there is a waveformID in the waveforms list if not any(x.ID == waveform_id for x in grid.waveforms): - logger.exception(f"{self.params_str()} there is no waveform with the identifier {waveform_id}.") + logger.exception( + f"{self.params_str()} there is no waveform with the identifier {waveform_id}." + ) raise ValueError m = MagneticDipoleUser() @@ -632,7 +719,16 @@ class MagneticDipole(UserObjectMulti): m.xcoordorigin = xcoord m.ycoordorigin = ycoord m.zcoordorigin = zcoord - m.ID = m.__class__.__name__ + "(" + str(m.xcoord) + "," + str(m.ycoord) + "," + str(m.zcoord) + ")" + m.ID = ( + m.__class__.__name__ + + "(" + + str(m.xcoord) + + "," + + str(m.ycoord) + + "," + + str(m.zcoord) + + ")" + ) m.waveformID = waveform_id try: @@ -641,14 +737,25 @@ class MagneticDipole(UserObjectMulti): stop = self.kwargs["stop"] if start < 0: logger.exception( - self.params_str() + (" delay of the initiation " "of the source should not " "be less than zero.") + self.params_str() + + ( + " delay of the initiation " + "of the source should not " + "be less than zero." + ) ) raise ValueError if stop < 0: - logger.exception(self.params_str() + (" time to remove the " "source should not be " "less than zero.")) + logger.exception( + self.params_str() + + (" time to remove the source should not be less than zero.") + ) raise ValueError if stop - start <= 0: - logger.exception(self.params_str() + (" duration of the source " "should not be zero or " "less.")) + logger.exception( + self.params_str() + + (" duration of the source should not be zero or less.") + ) raise ValueError m.start = start m.stop = min(stop, grid.timewindow) @@ -728,25 +835,31 @@ class TransmissionLine(UserObjectMulti): # Check polarity & position parameters if polarisation not in ("x", "y", "z"): - logger.exception(self.params_str() + (" polarisation must be " "x, y, or z.")) + logger.exception(self.params_str() + (" polarisation must be x, y, or z.")) raise ValueError if "2D TMx" in config.get_model_config().mode and polarisation in [ "y", "z", ]: - logger.exception(self.params_str() + (" polarisation must be x in " "2D TMx mode.")) + logger.exception( + self.params_str() + (" polarisation must be x in 2D TMx mode.") + ) raise ValueError elif "2D TMy" in config.get_model_config().mode and polarisation in [ "x", "z", ]: - logger.exception(self.params_str() + (" polarisation must be y in " "2D TMy mode.")) + logger.exception( + self.params_str() + (" polarisation must be y in 2D TMy mode.") + ) raise ValueError elif "2D TMz" in config.get_model_config().mode and polarisation in [ "x", "y", ]: - logger.exception(self.params_str() + (" polarisation must be z in " "2D TMz mode.")) + logger.exception( + self.params_str() + (" polarisation must be z in 2D TMz mode.") + ) raise ValueError xcoord, ycoord, zcoord = uip.check_src_rx_point(p1, self.params_str()) @@ -762,7 +875,9 @@ class TransmissionLine(UserObjectMulti): # Check if there is a waveformID in the waveforms list if not any(x.ID == waveform_id for x in grid.waveforms): - logger.exception(f"{self.params_str()} there is no waveform with the identifier {waveform_id}.") + logger.exception( + f"{self.params_str()} there is no waveform with the identifier {waveform_id}." + ) raise ValueError t = TransmissionLineUser(grid) @@ -770,7 +885,16 @@ class TransmissionLine(UserObjectMulti): t.xcoord = xcoord t.ycoord = ycoord t.zcoord = zcoord - t.ID = t.__class__.__name__ + "(" + str(t.xcoord) + "," + str(t.ycoord) + "," + str(t.zcoord) + ")" + t.ID = ( + t.__class__.__name__ + + "(" + + str(t.xcoord) + + "," + + str(t.ycoord) + + "," + + str(t.zcoord) + + ")" + ) t.resistance = resistance t.waveformID = waveform_id @@ -780,14 +904,25 @@ class TransmissionLine(UserObjectMulti): stop = self.kwargs["stop"] if start < 0: logger.exception( - self.params_str() + (" delay of the initiation " "of the source should not " "be less than zero.") + self.params_str() + + ( + " delay of the initiation " + "of the source should not " + "be less than zero." + ) ) raise ValueError if stop < 0: - logger.exception(self.params_str() + (" time to remove the " "source should not be " "less than zero.")) + logger.exception( + self.params_str() + + (" time to remove the source should not be less than zero.") + ) raise ValueError if stop - start <= 0: - logger.exception(self.params_str() + (" duration of the source " "should not be zero or " "less.")) + logger.exception( + self.params_str() + + (" duration of the source should not be zero or less.") + ) raise ValueError t.start = start t.stop = min(stop, grid.timewindow) @@ -839,7 +974,11 @@ class Rx(UserObjectMulti): def _do_rotate(self, grid): """Performs rotation.""" - new_pt = (self.kwargs["p1"][0] + grid.dx, self.kwargs["p1"][1] + grid.dy, self.kwargs["p1"][2] + grid.dz) + new_pt = ( + self.kwargs["p1"][0] + grid.dx, + self.kwargs["p1"][1] + grid.dy, + self.kwargs["p1"][2] + grid.dz, + ) pts = np.array([self.kwargs["p1"], new_pt]) rot_pts = rotate_2point_object(pts, self.axis, self.angle, self.origin) self.kwargs["p1"] = tuple(rot_pts[0, :]) @@ -878,7 +1017,9 @@ class Rx(UserObjectMulti): # If no ID or outputs are specified, use default r.ID = f"{r.__class__.__name__}({str(r.xcoord)},{str(r.ycoord)},{str(r.zcoord)})" for key in RxUser.defaultoutputs: - r.outputs[key] = np.zeros(grid.iterations, dtype=config.sim_config.dtypes["float_or_double"]) + r.outputs[key] = np.zeros( + grid.iterations, dtype=config.sim_config.dtypes["float_or_double"] + ) else: outputs.sort() # Get allowable outputs @@ -889,7 +1030,10 @@ class Rx(UserObjectMulti): # Check and add field output names for field in outputs: if field in allowableoutputs: - r.outputs[field] = np.zeros(grid.iterations, dtype=config.sim_config.dtypes["float_or_double"]) + r.outputs[field] = np.zeros( + grid.iterations, + dtype=config.sim_config.dtypes["float_or_double"], + ) else: logger.exception( f"{self.params_str()} contains an output " @@ -940,10 +1084,14 @@ class RxArray(UserObjectMulti): dx, dy, dz = uip.discretise_point(dl) if xs > xf or ys > yf or zs > zf: - logger.exception(f"{self.params_str()} the lower coordinates should be less than the upper coordinates.") + logger.exception( + f"{self.params_str()} the lower coordinates should be less than the upper coordinates." + ) raise ValueError if dx < 0 or dy < 0 or dz < 0: - logger.exception(f"{self.params_str()} the step size should not be less than zero.") + logger.exception( + f"{self.params_str()} the step size should not be less than zero." + ) raise ValueError if dx < 1: if dx == 0: @@ -993,7 +1141,10 @@ class RxArray(UserObjectMulti): p5 = uip.round_to_grid_static_point(p5) r.ID = f"{r.__class__.__name__}({str(x)},{str(y)},{str(z)})" for key in RxUser.defaultoutputs: - r.outputs[key] = np.zeros(grid.iterations, dtype=config.sim_config.dtypes["float_or_double"]) + r.outputs[key] = np.zeros( + grid.iterations, + dtype=config.sim_config.dtypes["float_or_double"], + ) logger.info( f" Receiver at {p5[0]:g}m, {p5[1]:g}m, " f"{p5[2]:g}m with output component(s) " @@ -1066,7 +1217,9 @@ class Snapshot(UserObjectMulti): if time > 0: iterations = round_value((time / grid.dt)) + 1 else: - logger.exception(f"{self.params_str()} time value must be greater than zero.") + logger.exception( + f"{self.params_str()} time value must be greater than zero." + ) raise ValueError try: @@ -1101,16 +1254,34 @@ class Snapshot(UserObjectMulti): outputs = dict.fromkeys(SnapshotUser.allowableoutputs, True) if dx < 0 or dy < 0 or dz < 0: - logger.exception(f"{self.params_str()} the step size should not be less than zero.") + logger.exception( + f"{self.params_str()} the step size should not be less than zero." + ) raise ValueError if dx < 1 or dy < 1 or dz < 1: - logger.exception(f"{self.params_str()} the step size should not be less than the spatial discretisation.") + logger.exception( + f"{self.params_str()} the step size should not be less than the spatial discretisation." + ) raise ValueError if iterations <= 0 or iterations > grid.iterations: logger.exception(f"{self.params_str()} time value is not valid.") raise ValueError - s = SnapshotUser(xs, ys, zs, xf, yf, zf, dx, dy, dz, iterations, filename, fileext=fileext, outputs=outputs) + s = SnapshotUser( + xs, + ys, + zs, + xf, + yf, + zf, + dx, + dy, + dz, + iterations, + filename, + fileext=fileext, + outputs=outputs, + ) logger.info( f"Snapshot from {p3[0]:g}m, {p3[1]:g}m, {p3[2]:g}m, to " @@ -1160,7 +1331,9 @@ class Material(UserObjectMulti): if se != "inf": se = float(se) if se < 0: - logger.exception(f"{self.params_str()} requires a positive value for electric conductivity.") + logger.exception( + f"{self.params_str()} requires a positive value for electric conductivity." + ) raise ValueError else: se = float("inf") @@ -1170,10 +1343,14 @@ class Material(UserObjectMulti): ) raise ValueError if sm < 0: - logger.exception(f"{self.params_str()} requires a positive value for magnetic loss.") + logger.exception( + f"{self.params_str()} requires a positive value for magnetic loss." + ) raise ValueError if any(x.ID == material_id for x in grid.materials): - logger.exception(f"{self.params_str()} with ID {material_id} already exists") + logger.exception( + f"{self.params_str()} with ID {material_id} already exists" + ) raise ValueError # Create a new instance of the Material class material @@ -1227,7 +1404,9 @@ class AddDebyeDispersion(UserObjectMulti): raise if poles < 0: - logger.exception(f"{self.params_str()} requires a positive value for number of poles.") + logger.exception( + f"{self.params_str()} requires a positive value for number of poles." + ) raise ValueError # Look up requested materials in existing list of material instances @@ -1249,17 +1428,24 @@ class AddDebyeDispersion(UserObjectMulti): disp_material.averagable = False for i in range(poles): if tau[i] > 0: - logger.debug("Not checking if relaxation times are " "greater than time-step.") + logger.debug( + "Not checking if relaxation times are greater than time-step." + ) disp_material.deltaer.append(er_delta[i]) disp_material.tau.append(tau[i]) else: - logger.exception(f"{self.params_str()} requires positive values for the permittivity difference.") + logger.exception( + f"{self.params_str()} requires positive values for the permittivity difference." + ) raise ValueError if disp_material.poles > config.get_model_config().materials["maxpoles"]: config.get_model_config().materials["maxpoles"] = disp_material.poles # Replace original material with newly created DispersiveMaterial - grid.materials = [disp_material if mat.numID == material.numID else mat for mat in grid.materials] + grid.materials = [ + disp_material if mat.numID == material.numID else mat + for mat in grid.materials + ] logger.info( f"{self.grid_name(grid)}Debye disperion added to {disp_material.ID} " @@ -1300,7 +1486,9 @@ class AddLorentzDispersion(UserObjectMulti): raise if poles < 0: - logger.exception(f"{self.params_str()} requires a positive value for number of poles.") + logger.exception( + f"{self.params_str()} requires a positive value for number of poles." + ) raise ValueError # Look up requested materials in existing list of material instances @@ -1338,7 +1526,10 @@ class AddLorentzDispersion(UserObjectMulti): config.get_model_config().materials["maxpoles"] = disp_material.poles # Replace original material with newly created DispersiveMaterial - grid.materials = [disp_material if mat.numID == material.numID else mat for mat in grid.materials] + grid.materials = [ + disp_material if mat.numID == material.numID else mat + for mat in grid.materials + ] logger.info( f"{self.grid_name(grid)}Lorentz disperion added to {disp_material.ID} " @@ -1376,7 +1567,9 @@ class AddDrudeDispersion(UserObjectMulti): raise if poles < 0: - logger.exception(f"{self.params_str()} requires a positive value for number of poles.") + logger.exception( + f"{self.params_str()} requires a positive value for number of poles." + ) raise ValueError # Look up requested materials in existing list of material instances @@ -1384,7 +1577,9 @@ class AddDrudeDispersion(UserObjectMulti): if len(materials) != len(material_ids): notfound = [x for x in material_ids if x not in materials] - logger.exception(f"{self.params_str()} material(s) {notfound} do not exist.") + logger.exception( + f"{self.params_str()} material(s) {notfound} do not exist." + ) raise ValueError for material in materials: @@ -1412,7 +1607,10 @@ class AddDrudeDispersion(UserObjectMulti): config.get_model_config().materials["maxpoles"] = disp_material.poles # Replace original material with newly created DispersiveMaterial - grid.materials = [disp_material if mat.numID == material.numID else mat for mat in grid.materials] + grid.materials = [ + disp_material if mat.numID == material.numID else mat + for mat in grid.materials + ] logger.info( f"{self.grid_name(grid)}Drude disperion added to {disp_material.ID} " @@ -1452,20 +1650,30 @@ class SoilPeplinski(UserObjectMulti): water_fraction_upper = self.kwargs["water_fraction_upper"] ID = self.kwargs["id"] except KeyError: - logger.exception(f"{self.params_str()} requires at exactly seven parameters.") + logger.exception( + f"{self.params_str()} requires at exactly seven parameters." + ) raise if sand_fraction < 0: - logger.exception(f"{self.params_str()} requires a positive value for the sand fraction.") + logger.exception( + f"{self.params_str()} requires a positive value for the sand fraction." + ) raise ValueError if clay_fraction < 0: - logger.exception(f"{self.params_str()} requires a positive value for the clay fraction.") + logger.exception( + f"{self.params_str()} requires a positive value for the clay fraction." + ) raise ValueError if bulk_density < 0: - logger.exception(f"{self.params_str()} requires a positive value for the bulk density.") + logger.exception( + f"{self.params_str()} requires a positive value for the bulk density." + ) raise ValueError if sand_density < 0: - logger.exception(f"{self.params_str()} requires a positive value for the sand particle density.") + logger.exception( + f"{self.params_str()} requires a positive value for the sand particle density." + ) raise ValueError if water_fraction_lower < 0: logger.exception( @@ -1486,7 +1694,12 @@ class SoilPeplinski(UserObjectMulti): # Create a new instance of the Material class material # (start index after pec & free_space) s = PeplinskiSoilUser( - ID, sand_fraction, clay_fraction, bulk_density, sand_density, (water_fraction_lower, water_fraction_upper) + ID, + sand_fraction, + clay_fraction, + bulk_density, + sand_density, + (water_fraction_lower, water_fraction_upper), ) logger.info( @@ -1532,7 +1745,9 @@ class MaterialRange(UserObjectMulti): ro_upper = self.kwargs["ro_upper"] ID = self.kwargs["id"] except KeyError: - logger.exception(f"{self.params_str()} requires at exactly nine parameters.") + logger.exception( + f"{self.params_str()} requires at exactly nine parameters." + ) raise if er_lower < 1: @@ -1548,10 +1763,14 @@ class MaterialRange(UserObjectMulti): ) raise ValueError if sigma_lower < 0: - logger.exception(f"{self.params_str()} requires a positive value for the lower limit of conductivity.") + logger.exception( + f"{self.params_str()} requires a positive value for the lower limit of conductivity." + ) raise ValueError if ro_lower < 0: - logger.exception(f"{self.params_str()} requires a positive value for the lower range magnetic loss.") + logger.exception( + f"{self.params_str()} requires a positive value for the lower range magnetic loss." + ) raise ValueError if er_upper < 1: logger.exception( @@ -1566,17 +1785,25 @@ class MaterialRange(UserObjectMulti): ) raise ValueError if sigma_upper < 0: - logger.exception(f"{self.params_str()} requires a positive value for the upper range of conductivity.") + logger.exception( + f"{self.params_str()} requires a positive value for the upper range of conductivity." + ) raise ValueError if ro_upper < 0: - logger.exception(f"{self.params_str()} requires a positive value for the upper range of magnetic loss.") + logger.exception( + f"{self.params_str()} requires a positive value for the upper range of magnetic loss." + ) if any(x.ID == ID for x in grid.mixingmodels): logger.exception(f"{self.params_str()} with ID {ID} already exists") raise ValueError s = RangeMaterialUser( - ID, (er_lower, er_upper), (sigma_lower, sigma_upper), (mr_lower, mr_upper), (ro_lower, ro_upper) + ID, + (er_lower, er_upper), + (sigma_lower, sigma_upper), + (mr_lower, mr_upper), + (ro_lower, ro_upper), ) logger.info( @@ -1616,7 +1843,9 @@ class MaterialList(UserObjectMulti): s = ListMaterialUser(ID, list_of_materials) - logger.info(f"{self.grid_name(grid)}A list of materials used to create {s.ID} that includes {s.mat}, created") + logger.info( + f"{self.grid_name(grid)}A list of materials used to create {s.ID} that includes {s.mat}, created" + ) grid.mixingmodels.append(s) @@ -1649,8 +1878,7 @@ class GeometryView(UserObjectMulti): """ if output_type == "n": - from .geometry_outputs import \ - GeometryViewVoxels as GeometryViewUser + from .geometry_outputs import GeometryViewVoxels as GeometryViewUser else: from .geometry_outputs import GeometryViewLines as GeometryViewUser @@ -1682,18 +1910,30 @@ class GeometryView(UserObjectMulti): dx, dy, dz = uip.discretise_static_point(dl) if dx < 0 or dy < 0 or dz < 0: - logger.exception(f"{self.params_str()} the step size should not be less than zero.") + logger.exception( + f"{self.params_str()} the step size should not be less than zero." + ) raise ValueError if dx > grid.nx or dy > grid.ny or dz > grid.nz: - logger.exception(f"{self.params_str()} the step size should be less than the domain size.") + logger.exception( + f"{self.params_str()} the step size should be less than the domain size." + ) raise ValueError if dx < 1 or dy < 1 or dz < 1: - logger.exception(f"{self.params_str()} the step size should not be less than the spatial discretisation.") + logger.exception( + f"{self.params_str()} the step size should not be less than the spatial discretisation." + ) raise ValueError if output_type not in ["n", "f"]: - logger.exception(f"{self.params_str()} requires type to be either n (normal) or f (fine).") + logger.exception( + f"{self.params_str()} requires type to be either n (normal) or f (fine)." + ) raise ValueError - if output_type == "f" and (dx * grid.dx != grid.dx or dy * grid.dy != grid.dy or dz * grid.dz != grid.dz): + if output_type == "f" and ( + dx * grid.dx != grid.dx + or dy * grid.dy != grid.dy + or dz * grid.dz != grid.dz + ): logger.exception( f"{self.params_str()} requires the spatial " "discretisation for the geometry view to be the " @@ -1821,7 +2061,9 @@ class PMLCFS(UserObjectMulti): or kappascalingdirection not in CFSParameter.scalingdirections or sigmascalingdirection not in CFSParameter.scalingdirections ): - logger.exception(f"{self.params_str()} must have scaling type {','.join(CFSParameter.scalingdirections)}") + logger.exception( + f"{self.params_str()} must have scaling type {','.join(CFSParameter.scalingdirections)}" + ) raise ValueError if ( float(alphamin) < 0 @@ -1830,7 +2072,9 @@ class PMLCFS(UserObjectMulti): or float(kappamax) < 0 or float(sigmamin) < 0 ): - logger.exception(f"{self.params_str()} minimum and maximum scaling values must be greater than zero.") + logger.exception( + f"{self.params_str()} minimum and maximum scaling values must be greater than zero." + ) raise ValueError cfsalpha = CFSParameter() @@ -1874,7 +2118,9 @@ class PMLCFS(UserObjectMulti): grid.pmls["cfs"].append(cfs) if len(grid.pmls["cfs"]) > 2: - logger.exception(f"{self.params_str()} can only be used up to two times, for up to a 2nd order PML.") + logger.exception( + f"{self.params_str()} can only be used up to two times, for up to a 2nd order PML." + ) raise ValueError diff --git a/gprMax/cmds_singleuse.py b/gprMax/cmds_singleuse.py index 6e9c2e35..2569791d 100644 --- a/gprMax/cmds_singleuse.py +++ b/gprMax/cmds_singleuse.py @@ -95,17 +95,20 @@ class Discretisation(UserObjectSingle): if G.dl[0] <= 0: logger.exception( - f"{self.__str__()} discretisation requires the " f"x-direction spatial step to be greater than zero" + f"{self.__str__()} discretisation requires the " + f"x-direction spatial step to be greater than zero" ) raise ValueError if G.dl[1] <= 0: logger.exception( - f"{self.__str__()} discretisation requires the " f"y-direction spatial step to be greater than zero" + f"{self.__str__()} discretisation requires the " + f"y-direction spatial step to be greater than zero" ) raise ValueError if G.dl[2] <= 0: logger.exception( - f"{self.__str__()} discretisation requires the " f"z-direction spatial step to be greater than zero" + f"{self.__str__()} discretisation requires the " + f"z-direction spatial step to be greater than zero" ) raise ValueError @@ -131,7 +134,9 @@ class Domain(UserObjectSingle): raise if G.nx == 0 or G.ny == 0 or G.nz == 0: - logger.exception(f"{self.__str__()} requires at least one cell in " f"every dimension") + logger.exception( + f"{self.__str__()} requires at least one cell in every dimension" + ) raise ValueError logger.info( @@ -161,7 +166,10 @@ class Domain(UserObjectSingle): # Sub-grids cannot be used with 2D models. There would typically be # minimal performance benefit with sub-gridding and 2D models. - if "2D" in config.get_model_config().mode and config.sim_config.general["subgrid"]: + if ( + "2D" in config.get_model_config().mode + and config.sim_config.general["subgrid"] + ): logger.exception("Sub-gridding cannot be used with 2D models") raise ValueError @@ -188,7 +196,8 @@ class TimeStepStabilityFactor(UserObjectSingle): if f <= 0 or f > 1: logger.exception( - f"{self.__str__()} requires the value of the time " f"step stability factor to be between zero and one" + f"{self.__str__()} requires the value of the time " + f"step stability factor to be between zero and one" ) raise ValueError @@ -227,7 +236,9 @@ class TimeWindow(UserObjectSingle): G.timewindow = tmp G.iterations = int(np.ceil(tmp / G.dt)) + 1 else: - logger.exception(self.__str__() + " must have a value greater than zero") + logger.exception( + self.__str__() + " must have a value greater than zero" + ) raise ValueError except KeyError: pass @@ -261,7 +272,10 @@ class OMPThreads(UserObjectSingle): ) raise if n < 1: - logger.exception(f"{self.__str__()} requires the value to be an " f"integer not less than one") + logger.exception( + f"{self.__str__()} requires the value to be an " + f"integer not less than one" + ) raise ValueError config.get_model_config().ompthreads = set_omp_threads(n) @@ -290,7 +304,9 @@ class PMLProps(UserObjectSingle): G.pmls["formulation"] = self.kwargs["formulation"] if G.pmls["formulation"] not in PML.formulations: logger.exception( - self.__str__() + f" requires the value to be " + f"one of {' '.join(PML.formulations)}" + self.__str__() + + f" requires the value to be " + + f"one of {' '.join(PML.formulations)}" ) except KeyError: pass @@ -309,7 +325,9 @@ class PMLProps(UserObjectSingle): G.pmls["thickness"]["ymax"] = int(self.kwargs["ymax"]) G.pmls["thickness"]["zmax"] = int(self.kwargs["zmax"]) except KeyError: - logger.exception(f"{self.__str__()} requires either one or six parameter(s)") + logger.exception( + f"{self.__str__()} requires either one or six parameter(s)" + ) raise if ( diff --git a/gprMax/config.py b/gprMax/config.py index 3d159a77..73db8bab 100644 --- a/gprMax/config.py +++ b/gprMax/config.py @@ -87,7 +87,11 @@ class ModelConfig: except: deviceID = 0 - self.device = {"dev": sim_config.set_model_device(deviceID), "deviceID": deviceID, "snapsgpu2cpu": False} + self.device = { + "dev": sim_config.set_model_device(deviceID), + "deviceID": deviceID, + "snapsgpu2cpu": False, + } # Total memory usage for all grids in the model. Starts with 50MB overhead. self.mem_overhead = 65e6 @@ -96,11 +100,20 @@ class ModelConfig: self.reuse_geometry = False # String to print at start of each model run - s = f"\n--- Model {model_num + 1}/{sim_config.model_end}, " f"input file: {sim_config.input_file_path}" - self.inputfilestr = Fore.GREEN + f"{s} {'-' * (get_terminal_width() - 1 - len(s))}\n" + Style.RESET_ALL + s = ( + f"\n--- Model {model_num + 1}/{sim_config.model_end}, " + f"input file: {sim_config.input_file_path}" + ) + self.inputfilestr = ( + Fore.GREEN + + f"{s} {'-' * (get_terminal_width() - 1 - len(s))}\n" + + Style.RESET_ALL + ) # Output file path and name for specific model - self.appendmodelnumber = "" if sim_config.args.n == 1 else str(model_num + 1) # Indexed from 1 + self.appendmodelnumber = ( + "" if sim_config.args.n == 1 else str(model_num + 1) + ) # Indexed from 1 self.set_output_file_path() # Numerical dispersion analysis parameters @@ -111,7 +124,11 @@ class ModelConfig: # phase-velocity phase error. # mingridsampling: minimum grid sampling of smallest wavelength for # physical wave propagation. - self.numdispersion = {"highestfreqthres": 40, "maxnumericaldisp": 2, "mingridsampling": 3} + self.numdispersion = { + "highestfreqthres": 40, + "maxnumericaldisp": 2, + "mingridsampling": 3, + } # General information to configure materials # maxpoles: Maximum number of dispersive material poles in a model. @@ -221,7 +238,11 @@ class SimulationConfig: # progressbars when logging level is greater than # info (20) - self.general = {"solver": "cpu", "precision": "single", "progressbars": args.log_level <= 20} + self.general = { + "solver": "cpu", + "precision": "single", + "progressbars": args.log_level <= 20, + } self.em_consts = { "c": c, # Speed of light in free space (m/s) @@ -239,7 +260,10 @@ class SimulationConfig: # Both single and double precision are possible on GPUs, but single # provides best performance. self.general["precision"] = "single" - self.devices = {"devs": [], "nvcc_opts": None} # pycuda device objects; nvcc compiler options + self.devices = { + "devs": [], + "nvcc_opts": None, + } # pycuda device objects; nvcc compiler options # Suppress nvcc warnings on Microsoft Windows if sys.platform == "win32": self.devices["nvcc_opts"] = ["-w"] @@ -251,11 +275,14 @@ class SimulationConfig: if self.args.opencl is not None: self.general["solver"] = "opencl" self.general["precision"] = "single" - self.devices = {"devs": [], "compiler_opts": None} # pyopencl device device(s); compiler options + self.devices = { + "devs": [], + "compiler_opts": None, + } # pyopencl device device(s); compiler options # Suppress CompilerWarning (sub-class of UserWarning) warnings.filterwarnings("ignore", category=UserWarning) - + # Suppress unused variable warnings on gcc # if sys.platform != 'win32': self.devices['compiler_opts'] = ['-w'] @@ -271,7 +298,8 @@ class SimulationConfig: self.general["subgrid"] and self.general["solver"] == "opencl" ): logger.exception( - "You cannot currently use CUDA or OpenCL-based " "solvers with models that contain sub-grids." + "You cannot currently use CUDA or OpenCL-based " + "solvers with models that contain sub-grids." ) raise ValueError else: @@ -305,7 +333,9 @@ class SimulationConfig: return dev if not found: - logger.exception(f"Compute device with device ID {deviceID} does " "not exist.") + logger.exception( + f"Compute device with device ID {deviceID} does not exist." + ) raise ValueError def _set_precision(self): diff --git a/gprMax/contexts.py b/gprMax/contexts.py index 5941888c..88734de7 100644 --- a/gprMax/contexts.py +++ b/gprMax/contexts.py @@ -31,8 +31,7 @@ import gprMax.config as config from ._version import __version__, codename from .model_build_run import ModelBuildRun from .solvers import create_G, create_solver -from .utilities.host_info import (print_cuda_info, print_host_info, - print_opencl_info) +from .utilities.host_info import print_cuda_info, print_host_info, print_opencl_info from .utilities.utilities import get_terminal_width, logo, timer logger = logging.getLogger(__name__) @@ -45,7 +44,9 @@ class Context: """ def __init__(self): - self.model_range = range(config.sim_config.model_start, config.sim_config.model_end) + self.model_range = range( + config.sim_config.model_start, config.sim_config.model_end + ) self.tsimend = None self.tsimstart = None @@ -64,7 +65,7 @@ class Context: elif config.sim_config.general["solver"] == "opencl": print_opencl_info(config.sim_config.devices["devs"]) - # Clear list of model configs, which can be retained when gprMax is + # Clear list of model configs, which can be retained when gprMax is # called in a loop, and want to avoid this. config.model_configs = [] @@ -181,7 +182,11 @@ class MPIContext(Context): print_opencl_info(config.sim_config.devices["devs"]) s = f"\n--- Input file: {config.sim_config.input_file_path}" - logger.basic(Fore.GREEN + f"{s} {'-' * (get_terminal_width() - 1 - len(s))}\n" + Style.RESET_ALL) + logger.basic( + Fore.GREEN + + f"{s} {'-' * (get_terminal_width() - 1 - len(s))}\n" + + Style.RESET_ALL + ) sys.stdout.flush() diff --git a/gprMax/fields_outputs.py b/gprMax/fields_outputs.py index f2279242..3ce186e8 100644 --- a/gprMax/fields_outputs.py +++ b/gprMax/fields_outputs.py @@ -45,7 +45,9 @@ def store_outputs(G): # Store current component else: func = globals()[output] - rx.outputs[output][iteration] = func(rx.xcoord, rx.ycoord, rx.zcoord, Hx, Hy, Hz, G) + rx.outputs[output][iteration] = func( + rx.xcoord, rx.ycoord, rx.zcoord, Hx, Hy, Hz, G + ) for tl in G.transmissionlines: tl.Vtotal[iteration] = tl.voltage[tl.antpos] @@ -92,7 +94,12 @@ def write_hd5_data(basegrp, grid, is_subgrid=False): basegrp.attrs["nx_ny_nz"] = (grid.nx, grid.ny, grid.nz) basegrp.attrs["dx_dy_dz"] = (grid.dx, grid.dy, grid.dz) basegrp.attrs["dt"] = grid.dt - nsrc = len(grid.voltagesources + grid.hertziandipoles + grid.magneticdipoles + grid.transmissionlines) + nsrc = len( + grid.voltagesources + + grid.hertziandipoles + + grid.magneticdipoles + + grid.transmissionlines + ) basegrp.attrs["nsrc"] = nsrc basegrp.attrs["nrx"] = len(grid.rxs) basegrp.attrs["srcsteps"] = grid.srcsteps @@ -112,13 +119,21 @@ def write_hd5_data(basegrp, grid, is_subgrid=False): for srcindex, src in enumerate(srclist): grp = basegrp.create_group(f"srcs/src{str(srcindex + 1)}") grp.attrs["Type"] = type(src).__name__ - grp.attrs["Position"] = (src.xcoord * grid.dx, src.ycoord * grid.dy, src.zcoord * grid.dz) + grp.attrs["Position"] = ( + src.xcoord * grid.dx, + src.ycoord * grid.dy, + src.zcoord * grid.dz, + ) # Create group for transmission lines; add positional data, line resistance and # line discretisation attributes; write arrays for line voltages and currents for tlindex, tl in enumerate(grid.transmissionlines): grp = basegrp.create_group("tls/tl" + str(tlindex + 1)) - grp.attrs["Position"] = (tl.xcoord * grid.dx, tl.ycoord * grid.dy, tl.zcoord * grid.dz) + grp.attrs["Position"] = ( + tl.xcoord * grid.dx, + tl.ycoord * grid.dy, + tl.zcoord * grid.dz, + ) grp.attrs["Resistance"] = tl.resistance grp.attrs["dl"] = tl.dl # Save incident voltage and current @@ -133,7 +148,11 @@ def write_hd5_data(basegrp, grid, is_subgrid=False): grp = basegrp.create_group("rxs/rx" + str(rxindex + 1)) if rx.ID: grp.attrs["Name"] = rx.ID - grp.attrs["Position"] = (rx.xcoord * grid.dx, rx.ycoord * grid.dy, rx.zcoord * grid.dz) + grp.attrs["Position"] = ( + rx.xcoord * grid.dx, + rx.ycoord * grid.dy, + rx.zcoord * grid.dz, + ) for output in rx.outputs: basegrp["rxs/rx" + str(rxindex + 1) + "/" + output] = rx.outputs[output] @@ -151,7 +170,9 @@ def Ix(x, y, z, Hx, Hy, Hz, G): if y == 0 or z == 0: Ix = 0 else: - Ix = G.dy * (Hy[x, y, z - 1] - Hy[x, y, z]) + G.dz * (Hz[x, y, z] - Hz[x, y - 1, z]) + Ix = G.dy * (Hy[x, y, z - 1] - Hy[x, y, z]) + G.dz * ( + Hz[x, y, z] - Hz[x, y - 1, z] + ) return Ix @@ -168,7 +189,9 @@ def Iy(x, y, z, Hx, Hy, Hz, G): if x == 0 or z == 0: Iy = 0 else: - Iy = G.dx * (Hx[x, y, z] - Hx[x, y, z - 1]) + G.dz * (Hz[x - 1, y, z] - Hz[x, y, z]) + Iy = G.dx * (Hx[x, y, z] - Hx[x, y, z - 1]) + G.dz * ( + Hz[x - 1, y, z] - Hz[x, y, z] + ) return Iy @@ -185,6 +208,8 @@ def Iz(x, y, z, Hx, Hy, Hz, G): if x == 0 or y == 0: Iz = 0 else: - Iz = G.dx * (Hx[x, y - 1, z] - Hx[x, y, z]) + G.dy * (Hy[x, y, z] - Hy[x - 1, y, z]) + Iz = G.dx * (Hx[x, y - 1, z] - Hx[x, y, z]) + G.dy * ( + Hy[x, y, z] - Hy[x - 1, y, z] + ) return Iz diff --git a/gprMax/fractals.py b/gprMax/fractals.py index 10557561..5de7809a 100644 --- a/gprMax/fractals.py +++ b/gprMax/fractals.py @@ -56,7 +56,7 @@ class FractalSurface: self.nz = zf - zs self.dtype = np.dtype(np.complex128) self.seed = seed - self.dimension = dimension # Fractal dimension from: http://dx.doi.org/10.1017/CBO9781139174695 + self.dimension = dimension # Fractal dimension from: http://dx.doi.org/10.1017/CBO9781139174695 self.weighting = np.array([1, 1], dtype=np.float64) self.fractalrange = (0, 0) self.filldepth = 0 @@ -82,7 +82,12 @@ class FractalSurface: self.fractalsurface = np.zeros(surfacedims, dtype=self.dtype) # Positional vector at centre of array, scaled by weighting - v1 = np.array([self.weighting[0] * (surfacedims[0]) / 2, self.weighting[1] * (surfacedims[1]) / 2]) + v1 = np.array( + [ + self.weighting[0] * (surfacedims[0]) / 2, + self.weighting[1] * (surfacedims[1]) / 2, + ] + ) # 2D array of random numbers to be convolved with the fractal function rng = np.random.default_rng(seed=self.seed) @@ -119,9 +124,11 @@ class FractalSurface: fractalmax = np.amax(self.fractalsurface) fractalrange = fractalmax - fractalmin self.fractalsurface = ( - self.fractalsurface * ((self.fractalrange[1] - self.fractalrange[0]) / fractalrange) + self.fractalsurface + * ((self.fractalrange[1] - self.fractalrange[0]) / fractalrange) + self.fractalrange[0] - - ((self.fractalrange[1] - self.fractalrange[0]) / fractalrange) * fractalmin + - ((self.fractalrange[1] - self.fractalrange[0]) / fractalrange) + * fractalmin ) @@ -157,7 +164,7 @@ class FractalVolume: self.averaging = False self.dtype = np.dtype(np.complex128) self.seed = seed - self.dimension = dimension # Fractal dimension from: http://dx.doi.org/10.1017/CBO9781139174695 + self.dimension = dimension # Fractal dimension from: http://dx.doi.org/10.1017/CBO9781139174695 self.weighting = np.array([1, 1, 1], dtype=np.float64) self.nbins = 0 self.fractalsurfaces = [] @@ -167,16 +174,24 @@ class FractalVolume: # Scale filter according to size of fractal volume if self.nx == 1: - filterscaling = np.amin(np.array([self.ny, self.nz])) / np.array([self.ny, self.nz]) + filterscaling = np.amin(np.array([self.ny, self.nz])) / np.array( + [self.ny, self.nz] + ) filterscaling = np.insert(filterscaling, 0, 1) elif self.ny == 1: - filterscaling = np.amin(np.array([self.nx, self.nz])) / np.array([self.nx, self.nz]) + filterscaling = np.amin(np.array([self.nx, self.nz])) / np.array( + [self.nx, self.nz] + ) filterscaling = np.insert(filterscaling, 1, 1) elif self.nz == 1: - filterscaling = np.amin(np.array([self.nx, self.ny])) / np.array([self.nx, self.ny]) + filterscaling = np.amin(np.array([self.nx, self.ny])) / np.array( + [self.nx, self.ny] + ) filterscaling = np.insert(filterscaling, 2, 1) else: - filterscaling = np.amin(np.array([self.nx, self.ny, self.nz])) / np.array([self.nx, self.ny, self.nz]) + filterscaling = np.amin(np.array([self.nx, self.ny, self.nz])) / np.array( + [self.nx, self.ny, self.nz] + ) # Adjust weighting to account for filter scaling self.weighting = np.multiply(self.weighting, filterscaling) @@ -185,7 +200,11 @@ class FractalVolume: # Positional vector at centre of array, scaled by weighting v1 = np.array( - [self.weighting[0] * self.nx / 2, self.weighting[1] * self.ny / 2, self.weighting[2] * self.nz / 2] + [ + self.weighting[0] * self.nx / 2, + self.weighting[1] * self.ny / 2, + self.weighting[2] * self.nz / 2, + ] ) # 3D array of random numbers to be convolved with the fractal function @@ -222,10 +241,14 @@ class FractalVolume: ) # Bin fractal values - bins = np.linspace(np.amin(self.fractalvolume), np.amax(self.fractalvolume), self.nbins) + bins = np.linspace( + np.amin(self.fractalvolume), np.amax(self.fractalvolume), self.nbins + ) for j in range(self.ny): for k in range(self.nz): - self.fractalvolume[:, j, k] = np.digitize(self.fractalvolume[:, j, k], bins, right=True) + self.fractalvolume[:, j, k] = np.digitize( + self.fractalvolume[:, j, k], bins, right=True + ) def generate_volume_mask(self): """Generate a 3D volume to use as a mask for adding rough surfaces, @@ -254,7 +277,9 @@ class Grass: """ self.numblades = numblades - self.geometryparams = np.zeros((self.numblades, 6), dtype=config.sim_config.dtypes["float_or_double"]) + self.geometryparams = np.zeros( + (self.numblades, 6), dtype=config.sim_config.dtypes["float_or_double"] + ) self.seed = seed self.set_geometry_parameters() diff --git a/gprMax/geometry_outputs.py b/gprMax/geometry_outputs.py index 9206240e..7a208d79 100644 --- a/gprMax/geometry_outputs.py +++ b/gprMax/geometry_outputs.py @@ -52,7 +52,8 @@ def save_geometry_views(gvs): total=gv.nbytes, unit="byte", unit_scale=True, - desc=f"Writing geometry view file {i + 1}/{len(gvs)}, " f"{gv.filename.name}{gv.vtkfiletype.ext}", + desc=f"Writing geometry view file {i + 1}/{len(gvs)}, " + f"{gv.filename.name}{gv.vtkfiletype.ext}", ncols=get_terminal_width() - 1, file=sys.stdout, disable=not config.sim_config.general["progressbars"], @@ -96,7 +97,9 @@ class GeometryView: def set_filename(self): """Constructs filename from user-supplied name and model run number.""" parts = config.get_model_config().output_file_path.parts - self.filename = Path(*parts[:-1], self.filenamebase + config.get_model_config().appendmodelnumber) + self.filename = Path( + *parts[:-1], self.filenamebase + config.get_model_config().appendmodelnumber + ) class GeometryViewLines(GeometryView): @@ -122,7 +125,12 @@ class GeometryViewLines(GeometryView): ): # Require contiguous for evtk library ID = np.ascontiguousarray( - self.grid.ID[:, self.xs : self.xf : self.dx, self.ys : self.yf : self.dy, self.zs : self.zf : self.dz] + self.grid.ID[ + :, + self.xs : self.xf : self.dx, + self.ys : self.yf : self.dy, + self.zs : self.zf : self.dz, + ] ) else: # This array is contiguous by design @@ -158,7 +166,15 @@ class GeometryViewLines(GeometryView): offsets_size = np.arange(start=2, step=2, stop=len(x) + 1, dtype="int32").nbytes connect_size = len(x) * np.dtype("int32").itemsize cell_type_size = len(x) * np.dtype("uint8").itemsize - self.nbytes = x.nbytes + y.nbytes + z.nbytes + lines.nbytes + offsets_size + connect_size + cell_type_size + self.nbytes = ( + x.nbytes + + y.nbytes + + z.nbytes + + lines.nbytes + + offsets_size + + connect_size + + cell_type_size + ) vtk_data = {"x": x, "y": y, "z": z, "data": lines, "comments": comments} @@ -205,7 +221,11 @@ class GeometryViewVoxels(GeometryView): ): # Require contiguous for evtk library solid = np.ascontiguousarray( - self.grid.solid[self.xs : self.xf : self.dx, self.ys : self.yf : self.dy, self.zs : self.zf : self.dz] + self.grid.solid[ + self.xs : self.xf : self.dx, + self.ys : self.yf : self.dy, + self.zs : self.zf : self.dz, + ] ) else: # This array is contiguous by design @@ -236,13 +256,21 @@ class GeometryViewVoxels(GeometryView): (self.grid.k0 * self.grid.dz * self.grid.ratio), ) else: - origin = ((self.xs * self.grid.dx), (self.ys * self.grid.dy), (self.zs * self.grid.dz)) + origin = ( + (self.xs * self.grid.dx), + (self.ys * self.grid.dy), + (self.zs * self.grid.dz), + ) # Write the VTK file .vti imageToVTK( str(self.filename), origin=origin, - spacing=((self.dx * self.grid.dx), (self.dy * self.grid.dy), (self.dz * self.grid.dz)), + spacing=( + (self.dx * self.grid.dx), + (self.dy * self.grid.dy), + (self.dz * self.grid.dz), + ), cellData={"Material": vtk_data["data"]}, comments=[vtk_data["comments"]], ) @@ -304,11 +332,17 @@ class Comments: if grid.pmls["thickness"]["z0"] - self.gv.zs > 0: pmlstorender["z0"] = int(grid.pmls["thickness"]["z0"] - self.gv.zs) if self.gv.xf > grid.nx - grid.pmls["thickness"]["xmax"]: - pmlstorender["xmax"] = int(self.gv.xf - (grid.nx - grid.pmls["thickness"]["xmax"])) + pmlstorender["xmax"] = int( + self.gv.xf - (grid.nx - grid.pmls["thickness"]["xmax"]) + ) if self.gv.yf > grid.ny - grid.pmls["thickness"]["ymax"]: - pmlstorender["ymax"] = int(self.gv.yf - (grid.ny - grid.pmls["thickness"]["ymax"])) + pmlstorender["ymax"] = int( + self.gv.yf - (grid.ny - grid.pmls["thickness"]["ymax"]) + ) if self.gv.zf > grid.nz - grid.pmls["thickness"]["zmax"]: - pmlstorender["zmax"] = int(self.gv.zf - (grid.nz - grid.pmls["thickness"]["zmax"])) + pmlstorender["zmax"] = int( + self.gv.zf - (grid.nz - grid.pmls["thickness"]["zmax"]) + ) return list(pmlstorender.values()) @@ -316,7 +350,11 @@ class Comments: """Used to name sources and/or receivers.""" sc = [] for src in srcs: - p = (src.xcoord * self.grid.dx, src.ycoord * self.grid.dy, src.zcoord * self.grid.dz) + p = ( + src.xcoord * self.grid.dx, + src.ycoord * self.grid.dy, + src.zcoord * self.grid.dz, + ) p = list(map(float, p)) s = {"name": src.ID, "position": p} @@ -332,7 +370,9 @@ class Comments: def materials_comment(self): if not self.averaged_materials: - return [m.ID for m in self.grid.materials if m.type != "dielectric-smoothed"] + return [ + m.ID for m in self.grid.materials if m.type != "dielectric-smoothed" + ] else: return [m.ID for m in self.grid.materials] @@ -340,7 +380,9 @@ class Comments: class GeometryObjects: """Geometry objects to be written to file.""" - def __init__(self, xs=None, ys=None, zs=None, xf=None, yf=None, zf=None, basefilename=None): + def __init__( + self, xs=None, ys=None, zs=None, xf=None, yf=None, zf=None, basefilename=None + ): """ Args: xs, xf, ys, yf, zs, zf: ints for extent of the volume in cells. @@ -366,9 +408,23 @@ class GeometryObjects: self.filename_materials = self.filename_materials.with_suffix(".txt") # Sizes of arrays to write necessary to update progress bar - self.solidsize = (self.nx + 1) * (self.ny + 1) * (self.nz + 1) * np.dtype(np.uint32).itemsize - self.rigidsize = 18 * (self.nx + 1) * (self.ny + 1) * (self.nz + 1) * np.dtype(np.int8).itemsize - self.IDsize = 6 * (self.nx + 1) * (self.ny + 1) * (self.nz + 1) * np.dtype(np.uint32).itemsize + self.solidsize = ( + (self.nx + 1) * (self.ny + 1) * (self.nz + 1) * np.dtype(np.uint32).itemsize + ) + self.rigidsize = ( + 18 + * (self.nx + 1) + * (self.ny + 1) + * (self.nz + 1) + * np.dtype(np.int8).itemsize + ) + self.IDsize = ( + 6 + * (self.nx + 1) + * (self.ny + 1) + * (self.nz + 1) + * np.dtype(np.uint32).itemsize + ) self.datawritesize = self.solidsize + self.rigidsize + self.IDsize def write_hdf5(self, G, pbar): @@ -385,16 +441,45 @@ class GeometryObjects: fdata.attrs["dx_dy_dz"] = (G.dx, G.dy, G.dz) # Get minimum and maximum integers of materials in geometry objects volume - minmat = np.amin(G.ID[:, self.xs : self.xf + 1, self.ys : self.yf + 1, self.zs : self.zf + 1]) - maxmat = np.amax(G.ID[:, self.xs : self.xf + 1, self.ys : self.yf + 1, self.zs : self.zf + 1]) + minmat = np.amin( + G.ID[ + :, + self.xs : self.xf + 1, + self.ys : self.yf + 1, + self.zs : self.zf + 1, + ] + ) + maxmat = np.amax( + G.ID[ + :, + self.xs : self.xf + 1, + self.ys : self.yf + 1, + self.zs : self.zf + 1, + ] + ) fdata["/data"] = ( - G.solid[self.xs : self.xf + 1, self.ys : self.yf + 1, self.zs : self.zf + 1].astype("int16") - minmat + G.solid[ + self.xs : self.xf + 1, self.ys : self.yf + 1, self.zs : self.zf + 1 + ].astype("int16") + - minmat ) pbar.update(self.solidsize) - fdata["/rigidE"] = G.rigidE[:, self.xs : self.xf + 1, self.ys : self.yf + 1, self.zs : self.zf + 1] - fdata["/rigidH"] = G.rigidH[:, self.xs : self.xf + 1, self.ys : self.yf + 1, self.zs : self.zf + 1] + fdata["/rigidE"] = G.rigidE[ + :, self.xs : self.xf + 1, self.ys : self.yf + 1, self.zs : self.zf + 1 + ] + fdata["/rigidH"] = G.rigidH[ + :, self.xs : self.xf + 1, self.ys : self.yf + 1, self.zs : self.zf + 1 + ] pbar.update(self.rigidsize) - fdata["/ID"] = G.ID[:, self.xs : self.xf + 1, self.ys : self.yf + 1, self.zs : self.zf + 1] - minmat + fdata["/ID"] = ( + G.ID[ + :, + self.xs : self.xf + 1, + self.ys : self.yf + 1, + self.zs : self.zf + 1, + ] + - minmat + ) pbar.update(self.IDsize) # Write materials list to a text file @@ -409,11 +494,18 @@ class GeometryObjects: ) if hasattr(material, "poles"): if "debye" in material.type: - dispersionstr = "#add_dispersion_debye: " f"{material.poles:g} " + dispersionstr = ( + f"#add_dispersion_debye: {material.poles:g} " + ) for pole in range(material.poles): - dispersionstr += f"{material.deltaer[pole]:g} " f"{material.tau[pole]:g} " + dispersionstr += ( + f"{material.deltaer[pole]:g} " + f"{material.tau[pole]:g} " + ) elif "lorenz" in material.type: - dispersionstr = f"#add_dispersion_lorenz: " f"{material.poles:g} " + dispersionstr = ( + f"#add_dispersion_lorenz: {material.poles:g} " + ) for pole in range(material.poles): dispersionstr += ( f"{material.deltaer[pole]:g} " @@ -421,8 +513,13 @@ class GeometryObjects: f"{material.alpha[pole]:g} " ) elif "drude" in material.type: - dispersionstr = f"#add_dispersion_drude: " f"{material.poles:g} " + dispersionstr = ( + f"#add_dispersion_drude: {material.poles:g} " + ) for pole in range(material.poles): - dispersionstr += f"{material.tau[pole]:g} " f"{material.alpha[pole]:g} " + dispersionstr += ( + f"{material.tau[pole]:g} " + f"{material.alpha[pole]:g} " + ) dispersionstr += material.ID fmaterials.write(dispersionstr + "\n") diff --git a/gprMax/gprMax.py b/gprMax/gprMax.py index 008c15f4..b30ade9e 100644 --- a/gprMax/gprMax.py +++ b/gprMax/gprMax.py @@ -47,7 +47,8 @@ help_msg = { "scenes": "(list, req): 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", - "inputfile": "(str, opt): Input file path. Can also run simulation by " "providing an input file.", + "inputfile": "(str, opt): Input file path. Can also run simulation by " + "providing an input file.", "outputfile": "(str, req): File path to the output data file.", "n": "(int, req): Number of required simulation runs.", "i": "(int, opt): Model number to start/restart simulation from. It would " @@ -59,16 +60,20 @@ help_msg = { "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 trace. For " "further details see the performance section of the User Guide.", - "gpu": "(list/bool, opt): Flag to use NVIDIA GPU or list of NVIDIA GPU " "device ID(s) for specific GPU card(s).", - "opencl": "(list/bool, opt): Flag to use OpenCL or list of OpenCL device " "ID(s) for specific compute device(s).", + "gpu": "(list/bool, opt): Flag to use NVIDIA GPU or list of NVIDIA GPU " + "device ID(s) for specific GPU card(s).", + "opencl": "(list/bool, opt): Flag to use OpenCL or list of OpenCL device " + "ID(s) for specific compute device(s).", "subgrid": "(bool, opt): Flag to use sub-gridding.", "autotranslate": "(bool, opt): 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.", - "geometry_only": "(bool, opt): Build a model and produce any geometry " "views but do not run the simulation.", - "geometry_fixed": "(bool, opt): Run a series of models where the geometry " "does not change between models.", + "geometry_only": "(bool, opt): Build a model and produce any geometry " + "views but do not run the simulation.", + "geometry_fixed": "(bool, opt): Run a series of models where the geometry " + "does not change between models.", "write_processed": "(bool, opt): Writes another input file after any " "Python code (#python blocks) and in the original input " "file has been processed.", @@ -163,15 +168,26 @@ def cli(): """Entry point for command line interface (CLI).""" # Parse command line arguments - parser = argparse.ArgumentParser(prog="gprMax", formatter_class=argparse.ArgumentDefaultsHelpFormatter) + parser = argparse.ArgumentParser( + prog="gprMax", formatter_class=argparse.ArgumentDefaultsHelpFormatter + ) parser.add_argument("inputfile", help=help_msg["inputfile"]) parser.add_argument("-n", default=args_defaults["n"], type=int, help=help_msg["n"]) parser.add_argument("-i", type=int, help=help_msg["i"]) - parser.add_argument("-mpi", action="store_true", default=args_defaults["mpi"], help=help_msg["mpi"]) - parser.add_argument("-gpu", type=int, action="append", nargs="*", help=help_msg["gpu"]) - parser.add_argument("-opencl", type=int, action="append", nargs="*", help=help_msg["opencl"]) parser.add_argument( - "--geometry-only", action="store_true", default=args_defaults["geometry_only"], help=help_msg["geometry_only"] + "-mpi", action="store_true", default=args_defaults["mpi"], help=help_msg["mpi"] + ) + parser.add_argument( + "-gpu", type=int, action="append", nargs="*", help=help_msg["gpu"] + ) + parser.add_argument( + "-opencl", type=int, action="append", nargs="*", help=help_msg["opencl"] + ) + parser.add_argument( + "--geometry-only", + action="store_true", + default=args_defaults["geometry_only"], + help=help_msg["geometry_only"], ) parser.add_argument( "--geometry-fixed", @@ -185,8 +201,18 @@ def cli(): default=args_defaults["write_processed"], help=help_msg["write_processed"], ) - parser.add_argument("--log-level", type=int, default=args_defaults["log_level"], help=help_msg["log_level"]) - parser.add_argument("--log-file", action="store_true", default=args_defaults["log_file"], help=help_msg["log_file"]) + parser.add_argument( + "--log-level", + type=int, + default=args_defaults["log_level"], + help=help_msg["log_level"], + ) + parser.add_argument( + "--log-file", + action="store_true", + default=args_defaults["log_file"], + help=help_msg["log_file"], + ) args = parser.parse_args() results = run_main(args) diff --git a/gprMax/grid.py b/gprMax/grid.py index c9e75991..20dce200 100644 --- a/gprMax/grid.py +++ b/gprMax/grid.py @@ -128,30 +128,67 @@ class FDTDGrid: def initialise_field_arrays(self): """Initialise arrays for the electric and magnetic field components.""" - self.Ex = np.zeros((self.nx + 1, self.ny + 1, self.nz + 1), dtype=config.sim_config.dtypes["float_or_double"]) - self.Ey = np.zeros((self.nx + 1, self.ny + 1, self.nz + 1), dtype=config.sim_config.dtypes["float_or_double"]) - self.Ez = np.zeros((self.nx + 1, self.ny + 1, self.nz + 1), dtype=config.sim_config.dtypes["float_or_double"]) - self.Hx = np.zeros((self.nx + 1, self.ny + 1, self.nz + 1), dtype=config.sim_config.dtypes["float_or_double"]) - self.Hy = np.zeros((self.nx + 1, self.ny + 1, self.nz + 1), dtype=config.sim_config.dtypes["float_or_double"]) - self.Hz = np.zeros((self.nx + 1, self.ny + 1, self.nz + 1), dtype=config.sim_config.dtypes["float_or_double"]) + self.Ex = np.zeros( + (self.nx + 1, self.ny + 1, self.nz + 1), + dtype=config.sim_config.dtypes["float_or_double"], + ) + self.Ey = np.zeros( + (self.nx + 1, self.ny + 1, self.nz + 1), + dtype=config.sim_config.dtypes["float_or_double"], + ) + self.Ez = np.zeros( + (self.nx + 1, self.ny + 1, self.nz + 1), + dtype=config.sim_config.dtypes["float_or_double"], + ) + self.Hx = np.zeros( + (self.nx + 1, self.ny + 1, self.nz + 1), + dtype=config.sim_config.dtypes["float_or_double"], + ) + self.Hy = np.zeros( + (self.nx + 1, self.ny + 1, self.nz + 1), + dtype=config.sim_config.dtypes["float_or_double"], + ) + self.Hz = np.zeros( + (self.nx + 1, self.ny + 1, self.nz + 1), + dtype=config.sim_config.dtypes["float_or_double"], + ) def initialise_std_update_coeff_arrays(self): """Initialise arrays for storing update coefficients.""" - self.updatecoeffsE = np.zeros((len(self.materials), 5), dtype=config.sim_config.dtypes["float_or_double"]) - self.updatecoeffsH = np.zeros((len(self.materials), 5), dtype=config.sim_config.dtypes["float_or_double"]) + self.updatecoeffsE = np.zeros( + (len(self.materials), 5), dtype=config.sim_config.dtypes["float_or_double"] + ) + self.updatecoeffsH = np.zeros( + (len(self.materials), 5), dtype=config.sim_config.dtypes["float_or_double"] + ) def initialise_dispersive_arrays(self): """Initialise field arrays when there are dispersive materials present.""" self.Tx = np.zeros( - (config.get_model_config().materials["maxpoles"], self.nx + 1, self.ny + 1, self.nz + 1), + ( + config.get_model_config().materials["maxpoles"], + self.nx + 1, + self.ny + 1, + self.nz + 1, + ), dtype=config.get_model_config().materials["dispersivedtype"], ) self.Ty = np.zeros( - (config.get_model_config().materials["maxpoles"], self.nx + 1, self.ny + 1, self.nz + 1), + ( + config.get_model_config().materials["maxpoles"], + self.nx + 1, + self.ny + 1, + self.nz + 1, + ), dtype=config.get_model_config().materials["dispersivedtype"], ) self.Tz = np.zeros( - (config.get_model_config().materials["maxpoles"], self.nx + 1, self.ny + 1, self.nz + 1), + ( + config.get_model_config().materials["maxpoles"], + self.nx + 1, + self.ny + 1, + self.nz + 1, + ), dtype=config.get_model_config().materials["dispersivedtype"], ) @@ -185,7 +222,9 @@ class FDTDGrid: solidarray = self.nx * self.ny * self.nz * np.dtype(np.uint32).itemsize # 12 x rigidE array components + 6 x rigidH array components - rigidarrays = (12 + 6) * self.nx * self.ny * self.nz * np.dtype(np.int8).itemsize + rigidarrays = ( + (12 + 6) * self.nx * self.ny * self.nz * np.dtype(np.int8).itemsize + ) # 6 x field arrays + 6 x ID arrays fieldarrays = ( @@ -288,14 +327,24 @@ class FDTDGrid: def calculate_dt(self): """Calculate time step at the CFL limit.""" if config.get_model_config().mode == "2D TMx": - self.dt = 1 / (config.sim_config.em_consts["c"] * np.sqrt((1 / self.dy**2) + (1 / self.dz**2))) + self.dt = 1 / ( + config.sim_config.em_consts["c"] + * np.sqrt((1 / self.dy**2) + (1 / self.dz**2)) + ) elif config.get_model_config().mode == "2D TMy": - self.dt = 1 / (config.sim_config.em_consts["c"] * np.sqrt((1 / self.dx**2) + (1 / self.dz**2))) + self.dt = 1 / ( + config.sim_config.em_consts["c"] + * np.sqrt((1 / self.dx**2) + (1 / self.dz**2)) + ) elif config.get_model_config().mode == "2D TMz": - self.dt = 1 / (config.sim_config.em_consts["c"] * np.sqrt((1 / self.dx**2) + (1 / self.dy**2))) + self.dt = 1 / ( + config.sim_config.em_consts["c"] + * np.sqrt((1 / self.dx**2) + (1 / self.dy**2)) + ) else: self.dt = 1 / ( - config.sim_config.em_consts["c"] * np.sqrt((1 / self.dx**2) + (1 / self.dy**2) + (1 / self.dz**2)) + config.sim_config.em_consts["c"] + * np.sqrt((1 / self.dx**2) + (1 / self.dy**2) + (1 / self.dz**2)) ) # Round down time step to nearest float with precision one less than @@ -319,10 +368,14 @@ class CUDAGrid(FDTDGrid): def set_blocks_per_grid(self): """Set the blocks per grid size used for updating the electric and - magnetic field arrays on a GPU. + magnetic field arrays on a GPU. """ - self.bpg = (int(np.ceil(((self.nx + 1) * (self.ny + 1) * (self.nz + 1)) / self.tpb[0])), 1, 1) + self.bpg = ( + int(np.ceil(((self.nx + 1) * (self.ny + 1) * (self.nz + 1)) / self.tpb[0])), + 1, + 1, + ) def htod_geometry_arrays(self): """Initialise an array for cell edge IDs (ID) on compute device.""" @@ -342,11 +395,13 @@ class CUDAGrid(FDTDGrid): def htod_dispersive_arrays(self): """Initialise dispersive material coefficient arrays on compute device.""" - self.updatecoeffsdispersive_dev = self.gpuarray.to_gpu(self.updatecoeffsdispersive) + self.updatecoeffsdispersive_dev = self.gpuarray.to_gpu( + self.updatecoeffsdispersive + ) self.Tx_dev = self.gpuarray.to_gpu(self.Tx) self.Ty_dev = self.gpuarray.to_gpu(self.Ty) self.Tz_dev = self.gpuarray.to_gpu(self.Tz) - + class OpenCLGrid(FDTDGrid): """Additional grid methods for solving on compute device using OpenCL.""" @@ -385,13 +440,15 @@ class OpenCLGrid(FDTDGrid): Args: queue: pyopencl queue. """ - - self.updatecoeffsdispersive_dev = self.clarray.to_device(queue, self.updatecoeffsdispersive) + + self.updatecoeffsdispersive_dev = self.clarray.to_device( + queue, self.updatecoeffsdispersive + ) # self.updatecoeffsdispersive_dev = self.clarray.to_device(queue, np.ones((95,95,95), dtype=np.float32)) self.Tx_dev = self.clarray.to_device(queue, self.Tx) self.Ty_dev = self.clarray.to_device(queue, self.Ty) self.Tz_dev = self.clarray.to_device(queue, self.Tz) - + def dispersion_analysis(G): """Analysis of numerical dispersion (Taflove et al, 2005, p112) - @@ -431,7 +488,9 @@ def dispersion_analysis(G): iterations = min(iterations, G.iterations) waveformvalues = np.zeros(G.iterations) for iteration in range(G.iterations): - waveformvalues[iteration] = waveform.calculate_value(iteration * G.dt, G.dt) + waveformvalues[iteration] = waveform.calculate_value( + iteration * G.dt, G.dt + ) # Ensure source waveform is not being overly truncated before attempting any FFT if np.abs(waveformvalues[-1]) < np.abs(np.amax(waveformvalues)) / 100: @@ -444,7 +503,10 @@ def dispersion_analysis(G): try: freqthres = ( np.where( - power[freqmaxpower:] < -config.get_model_config().numdispersion["highestfreqthres"] + power[freqmaxpower:] + < -config.get_model_config().numdispersion[ + "highestfreqthres" + ] )[0][0] + freqmaxpower ) @@ -463,7 +525,8 @@ def dispersion_analysis(G): # If waveform is truncated don't do any further analysis else: results["error"] = ( - "waveform does not fit within specified " + "time window and is therefore being truncated." + "waveform does not fit within specified " + + "time window and is therefore being truncated." ) else: results["error"] = "no waveform detected." @@ -511,9 +574,14 @@ def dispersion_analysis(G): results["N"] = minwavelength / delta # Check grid sampling will result in physical wave propagation - if int(np.floor(results["N"])) >= config.get_model_config().numdispersion["mingridsampling"]: + if ( + int(np.floor(results["N"])) + >= config.get_model_config().numdispersion["mingridsampling"] + ): # Numerical phase velocity - vp = np.pi / (results["N"] * np.arcsin((1 / S) * np.sin((np.pi * S) / results["N"]))) + vp = np.pi / ( + results["N"] * np.arcsin((1 / S) * np.sin((np.pi * S) / results["N"])) + ) # Physical phase velocity error (percentage) results["deltavp"] = (((vp * config.c) - config.c) / config.c) * 100 diff --git a/gprMax/hash_cmds_file.py b/gprMax/hash_cmds_file.py index dddc4e8e..0c9c0b19 100644 --- a/gprMax/hash_cmds_file.py +++ b/gprMax/hash_cmds_file.py @@ -48,7 +48,11 @@ def process_python_include_code(inputfile, usernamespace): """ # Strip out any newline characters and comments that must begin with double hashes - inputlines = [line.rstrip() for line in inputfile if (not line.startswith("##") and line.rstrip("\n"))] + inputlines = [ + line.rstrip() + for line in inputfile + if (not line.startswith("##") and line.rstrip("\n")) + ] # Rewind input file in preparation for any subsequent reading function inputfile.seek(0) @@ -74,7 +78,8 @@ def process_python_include_code(inputfile, usernamespace): x += 1 if x == len(inputlines): logger.exception( - "Cannot find the end of the Python code " + "block, i.e. missing #end_python: command." + "Cannot find the end of the Python code " + + "block, i.e. missing #end_python: command." ) raise SyntaxError # Compile code for faster execution @@ -147,7 +152,9 @@ def process_include_files(hashcmds): # See if file exists at specified path and if not try input file directory includefile = Path(includefile) if not includefile.exists(): - includefile = Path(config.sim_config.input_file_path.parent, includefile) + includefile = Path( + config.sim_config.input_file_path.parent, includefile + ) with open(includefile, "r") as f: # Strip out any newline characters and comments that must begin with double hashes @@ -289,7 +296,9 @@ def check_cmd_names(processedlines, checkessential=True): # are no parameters for a command, e.g. for #taguchi: if " " not in cmdparams[0] and len(cmdparams.strip("\n")) != 0: logger.exception( - "There must be a space between the command name " + "and parameters in " + processedlines[lindex] + "There must be a space between the command name " + + "and parameters in " + + processedlines[lindex] ) raise SyntaxError @@ -312,7 +321,11 @@ def check_cmd_names(processedlines, checkessential=True): if singlecmds[cmdname] is None: singlecmds[cmdname] = cmd[1].strip(" \t\n") else: - logger.exception("You can only have a single instance of " + cmdname + " in your model") + logger.exception( + "You can only have a single instance of " + + cmdname + + " in your model" + ) raise SyntaxError elif cmdname in multiplecmds: @@ -384,7 +397,10 @@ def parse_hash_commands(scene): for key, value in sorted(usernamespace.items()): if key != "__builtins__": uservars += f"{key}: {value}, " - logger.info(f"Constants/variables used/available for Python scripting: " + f"{{{uservars[:-2]}}}\n") + logger.info( + f"Constants/variables used/available for Python scripting: " + + f"{{{uservars[:-2]}}}\n" + ) # Write a file containing the input commands after Python or include # file commands have been processed diff --git a/gprMax/hash_cmds_geometry.py b/gprMax/hash_cmds_geometry.py index 71074051..689c8bdb 100644 --- a/gprMax/hash_cmds_geometry.py +++ b/gprMax/hash_cmds_geometry.py @@ -56,11 +56,12 @@ def process_geometrycmds(geometry): tmp = object.split() if tmp[0] == "#geometry_objects_read:": - from .cmds_geometry.geometry_objects_read import \ - GeometryObjectsRead + from .cmds_geometry.geometry_objects_read import GeometryObjectsRead if len(tmp) != 6: - logger.exception("'" + " ".join(tmp) + "'" + " requires exactly five parameters") + logger.exception( + "'" + " ".join(tmp) + "'" + " requires exactly five parameters" + ) raise ValueError p1 = (float(tmp[1]), float(tmp[2]), float(tmp[3])) @@ -70,7 +71,9 @@ def process_geometrycmds(geometry): elif tmp[0] == "#edge:": if len(tmp) != 8: - logger.exception("'" + " ".join(tmp) + "'" + " requires exactly seven parameters") + logger.exception( + "'" + " ".join(tmp) + "'" + " requires exactly seven parameters" + ) raise ValueError edge = Edge( @@ -83,7 +86,9 @@ def process_geometrycmds(geometry): elif tmp[0] == "#plate:": if len(tmp) < 8: - logger.exception("'" + " ".join(tmp) + "'" + " requires at least seven parameters") + logger.exception( + "'" + " ".join(tmp) + "'" + " requires at least seven parameters" + ) raise ValueError # Isotropic case @@ -103,14 +108,18 @@ def process_geometrycmds(geometry): ) else: - logger.exception("'" + " ".join(tmp) + "'" + " too many parameters have been given") + logger.exception( + "'" + " ".join(tmp) + "'" + " too many parameters have been given" + ) raise ValueError scene_objects.append(plate) elif tmp[0] == "#triangle:": if len(tmp) < 12: - logger.exception("'" + " ".join(tmp) + "'" + " requires at least eleven parameters") + logger.exception( + "'" + " ".join(tmp) + "'" + " requires at least eleven parameters" + ) raise ValueError p1 = (float(tmp[1]), float(tmp[2]), float(tmp[3])) @@ -120,26 +129,40 @@ def process_geometrycmds(geometry): # Isotropic case with no user specified averaging if len(tmp) == 12: - triangle = Triangle(p1=p1, p2=p2, p3=p3, thickness=thickness, material_id=tmp[11]) + triangle = Triangle( + p1=p1, p2=p2, p3=p3, thickness=thickness, material_id=tmp[11] + ) # Isotropic case with user specified averaging elif len(tmp) == 13: - triangle = Triangle(p1=p1, p2=p2, p3=p3, thickness=thickness, - material_id=tmp[11], averaging=tmp[12].lower()) + triangle = Triangle( + p1=p1, + p2=p2, + p3=p3, + thickness=thickness, + material_id=tmp[11], + averaging=tmp[12].lower(), + ) # Uniaxial anisotropic case elif len(tmp) == 14: - triangle = Triangle(p1=p1, p2=p2, p3=p3, thickness=thickness, material_ids=tmp[11:]) + triangle = Triangle( + p1=p1, p2=p2, p3=p3, thickness=thickness, material_ids=tmp[11:] + ) else: - logger.exception("'" + " ".join(tmp) + "'" + " too many parameters have been given") + logger.exception( + "'" + " ".join(tmp) + "'" + " too many parameters have been given" + ) raise ValueError scene_objects.append(triangle) elif tmp[0] == "#box:": if len(tmp) < 8: - logger.exception("'" + " ".join(tmp) + "'" + " requires at least seven parameters") + logger.exception( + "'" + " ".join(tmp) + "'" + " requires at least seven parameters" + ) raise ValueError p1 = (float(tmp[1]), float(tmp[2]), float(tmp[3])) @@ -158,14 +181,18 @@ def process_geometrycmds(geometry): box = Box(p1=p1, p2=p2, material_ids=tmp[7:]) else: - logger.exception("'" + " ".join(tmp) + "'" + " too many parameters have been given") + logger.exception( + "'" + " ".join(tmp) + "'" + " too many parameters have been given" + ) raise ValueError scene_objects.append(box) elif tmp[0] == "#cylinder:": if len(tmp) < 9: - logger.exception("'" + " ".join(tmp) + "'" + " requires at least eight parameters") + logger.exception( + "'" + " ".join(tmp) + "'" + " requires at least eight parameters" + ) raise ValueError p1 = (float(tmp[1]), float(tmp[2]), float(tmp[3])) @@ -178,21 +205,27 @@ def process_geometrycmds(geometry): # Isotropic case with user specified averaging elif len(tmp) == 10: - cylinder = Cylinder(p1=p1, p2=p2, r=r, material_id=tmp[8], averaging=tmp[9].lower()) + cylinder = Cylinder( + p1=p1, p2=p2, r=r, material_id=tmp[8], averaging=tmp[9].lower() + ) # Uniaxial anisotropic case elif len(tmp) == 11: cylinder = Cylinder(p1=p1, p2=p2, r=r, material_ids=tmp[8:]) else: - logger.exception("'" + " ".join(tmp) + "'" + " too many parameters have been given") + logger.exception( + "'" + " ".join(tmp) + "'" + " too many parameters have been given" + ) raise ValueError scene_objects.append(cylinder) elif tmp[0] == "#cone:": if len(tmp) < 10: - logger.exception("'" + " ".join(tmp) + "'" + " requires at least nine parameters") + logger.exception( + "'" + " ".join(tmp) + "'" + " requires at least nine parameters" + ) raise ValueError p1 = (float(tmp[1]), float(tmp[2]), float(tmp[3])) @@ -206,21 +239,32 @@ def process_geometrycmds(geometry): # Isotropic case with user specified averaging elif len(tmp) == 11: - cone = Cone(p1=p1, p2=p2, r1=r1, r2=r2, material_id=tmp[9], averaging=tmp[10].lower()) + cone = Cone( + p1=p1, + p2=p2, + r1=r1, + r2=r2, + material_id=tmp[9], + averaging=tmp[10].lower(), + ) # Uniaxial anisotropic case elif len(tmp) == 12: cone = Cone(p1=p1, p2=p2, r1=r1, r2=r2, material_ids=tmp[9:]) else: - logger.exception("'" + " ".join(tmp) + "'" + " too many parameters have been given") + logger.exception( + "'" + " ".join(tmp) + "'" + " too many parameters have been given" + ) raise ValueError scene_objects.append(cone) elif tmp[0] == "#cylindrical_sector:": if len(tmp) < 10: - logger.exception("'" + " ".join(tmp) + "'" + " requires at least nine parameters") + logger.exception( + "'" + " ".join(tmp) + "'" + " requires at least nine parameters" + ) raise ValueError normal = tmp[1].lower() @@ -276,14 +320,18 @@ def process_geometrycmds(geometry): ) else: - logger.exception("'" + " ".join(tmp) + "'" + " too many parameters have been given") + logger.exception( + "'" + " ".join(tmp) + "'" + " too many parameters have been given" + ) raise ValueError scene_objects.append(cylindrical_sector) elif tmp[0] == "#sphere:": if len(tmp) < 6: - logger.exception("'" + " ".join(tmp) + "'" + " requires at least five parameters") + logger.exception( + "'" + " ".join(tmp) + "'" + " requires at least five parameters" + ) raise ValueError p1 = (float(tmp[1]), float(tmp[2]), float(tmp[3])) @@ -295,21 +343,27 @@ def process_geometrycmds(geometry): # Isotropic case with user specified averaging elif len(tmp) == 7: - sphere = Sphere(p1=p1, r=r, material_id=tmp[5], averaging=tmp[6].lower()) + sphere = Sphere( + p1=p1, r=r, material_id=tmp[5], averaging=tmp[6].lower() + ) # Uniaxial anisotropic case elif len(tmp) == 8: sphere = Sphere(p1=p1, r=r, material_id=tmp[5:]) else: - logger.exception("'" + " ".join(tmp) + "'" + " too many parameters have been given") + logger.exception( + "'" + " ".join(tmp) + "'" + " too many parameters have been given" + ) raise ValueError scene_objects.append(sphere) elif tmp[0] == "#ellipsoid:": if len(tmp) < 8: - logger.exception("'" + " ".join(tmp) + "'" + " requires at least seven parameters") + logger.exception( + "'" + " ".join(tmp) + "'" + " requires at least seven parameters" + ) raise ValueError p1 = (float(tmp[1]), float(tmp[2]), float(tmp[3])) @@ -323,15 +377,23 @@ def process_geometrycmds(geometry): # Isotropic case with user specified averaging elif len(tmp) == 9: - ellipsoid = Ellipsoid(p1=p1, xr=xr, yr=yr, zr=zr, - material_id=tmp[7], averaging=tmp[8].lower()) + ellipsoid = Ellipsoid( + p1=p1, + xr=xr, + yr=yr, + zr=zr, + material_id=tmp[7], + averaging=tmp[8].lower(), + ) # Uniaxial anisotropic case elif len(tmp) == 8: ellipsoid = Ellipsoid(p1=p1, xr=xr, yr=yr, zr=zr, material_id=tmp[7:]) else: - logger.exception("'" + " ".join(tmp) + "'" + " too many parameters have been given") + logger.exception( + "'" + " ".join(tmp) + "'" + " too many parameters have been given" + ) raise ValueError scene_objects.append(ellipsoid) @@ -340,7 +402,9 @@ def process_geometrycmds(geometry): # Default is no dielectric smoothing for a fractal box if len(tmp) < 14: - logger.exception("'" + " ".join(tmp) + "'" + " requires at least thirteen parameters") + logger.exception( + "'" + " ".join(tmp) + "'" + " requires at least thirteen parameters" + ) raise ValueError p1 = (float(tmp[1]), float(tmp[2]), float(tmp[3])) @@ -385,7 +449,9 @@ def process_geometrycmds(geometry): averaging=tmp[15].lower(), ) else: - logger.exception("'" + " ".join(tmp) + "'" + " too many parameters have been given") + logger.exception( + "'" + " ".join(tmp) + "'" + " too many parameters have been given" + ) raise ValueError scene_objects.append(fb) @@ -396,7 +462,12 @@ def process_geometrycmds(geometry): if tmp[0] == "#add_surface_roughness:": if len(tmp) < 13: - logger.exception("'" + " ".join(tmp) + "'" + " requires at least twelve parameters") + logger.exception( + "'" + + " ".join(tmp) + + "'" + + " requires at least twelve parameters" + ) raise ValueError p1 = (float(tmp[1]), float(tmp[2]), float(tmp[3])) @@ -426,14 +497,24 @@ def process_geometrycmds(geometry): seed=int(tmp[13]), ) else: - logger.exception("'" + " ".join(tmp) + "'" + " too many parameters have been given") + logger.exception( + "'" + + " ".join(tmp) + + "'" + + " too many parameters have been given" + ) raise ValueError scene_objects.append(asr) if tmp[0] == "#add_surface_water:": if len(tmp) != 9: - logger.exception("'" + " ".join(tmp) + "'" + " requires exactly eight parameters") + logger.exception( + "'" + + " ".join(tmp) + + "'" + + " requires exactly eight parameters" + ) raise ValueError p1 = (float(tmp[1]), float(tmp[2]), float(tmp[3])) @@ -441,12 +522,19 @@ def process_geometrycmds(geometry): depth = float(tmp[7]) fractal_box_id = tmp[8] - asf = AddSurfaceWater(p1=p1, p2=p2, depth=depth, fractal_box_id=fractal_box_id) + asf = AddSurfaceWater( + p1=p1, p2=p2, depth=depth, fractal_box_id=fractal_box_id + ) scene_objects.append(asf) if tmp[0] == "#add_grass:": if len(tmp) < 12: - logger.exception("'" + " ".join(tmp) + "'" + " requires at least eleven parameters") + logger.exception( + "'" + + " ".join(tmp) + + "'" + + " requires at least eleven parameters" + ) raise ValueError p1 = (float(tmp[1]), float(tmp[2]), float(tmp[3])) @@ -476,7 +564,12 @@ def process_geometrycmds(geometry): seed=int(tmp[12]), ) else: - logger.exception("'" + " ".join(tmp) + "'" + " too many parameters have been given") + logger.exception( + "'" + + " ".join(tmp) + + "'" + + " too many parameters have been given" + ) raise ValueError scene_objects.append(grass) diff --git a/gprMax/hash_cmds_multiuse.py b/gprMax/hash_cmds_multiuse.py index bdd108d6..891968a0 100644 --- a/gprMax/hash_cmds_multiuse.py +++ b/gprMax/hash_cmds_multiuse.py @@ -18,13 +18,27 @@ import logging -from .cmds_multiuse import (PMLCFS, AddDebyeDispersion, AddDrudeDispersion, - AddLorentzDispersion, ExcitationFile, - GeometryObjectsWrite, GeometryView, HertzianDipole, - MagneticDipole, Material, MaterialList, - MaterialRange, Rx, RxArray, Snapshot, - SoilPeplinski, TransmissionLine, VoltageSource, - Waveform) +from .cmds_multiuse import ( + PMLCFS, + AddDebyeDispersion, + AddDrudeDispersion, + AddLorentzDispersion, + ExcitationFile, + GeometryObjectsWrite, + GeometryView, + HertzianDipole, + MagneticDipole, + Material, + MaterialList, + MaterialRange, + Rx, + RxArray, + Snapshot, + SoilPeplinski, + TransmissionLine, + VoltageSource, + Waveform, +) logger = logging.getLogger(__name__) @@ -47,10 +61,19 @@ def process_multicmds(multicmds): for cmdinstance in multicmds[cmdname]: tmp = cmdinstance.split() if len(tmp) != 4: - logger.exception("'" + cmdname + ": " + " ".join(tmp) + "'" + " requires exactly four parameters") + logger.exception( + "'" + + cmdname + + ": " + + " ".join(tmp) + + "'" + + " requires exactly four parameters" + ) raise ValueError - waveform = Waveform(wave_type=tmp[0], amp=float(tmp[1]), freq=float(tmp[2]), id=tmp[3]) + waveform = Waveform( + wave_type=tmp[0], amp=float(tmp[1]), freq=float(tmp[2]), id=tmp[3] + ) scene_objects.append(waveform) cmdname = "#voltage_source" @@ -74,7 +97,14 @@ def process_multicmds(multicmds): end=float(tmp[7]), ) else: - logger.exception("'" + cmdname + ": " + " ".join(tmp) + "'" + " requires at least six parameters") + logger.exception( + "'" + + cmdname + + ": " + + " ".join(tmp) + + "'" + + " requires at least six parameters" + ) raise ValueError scene_objects.append(voltage_source) @@ -84,11 +114,20 @@ def process_multicmds(multicmds): for cmdinstance in multicmds[cmdname]: tmp = cmdinstance.split() if len(tmp) < 5: - logger.exception("'" + cmdname + ": " + " ".join(tmp) + "'" + " requires at least five parameters") + logger.exception( + "'" + + cmdname + + ": " + + " ".join(tmp) + + "'" + + " requires at least five parameters" + ) raise ValueError if len(tmp) == 5: hertzian_dipole = HertzianDipole( - polarisation=tmp[0], p1=(float(tmp[1]), float(tmp[2]), float(tmp[3])), waveform_id=tmp[4] + polarisation=tmp[0], + p1=(float(tmp[1]), float(tmp[2]), float(tmp[3])), + waveform_id=tmp[4], ) elif len(tmp) == 7: hertzian_dipole = HertzianDipole( @@ -99,7 +138,9 @@ def process_multicmds(multicmds): end=float(tmp[6]), ) else: - logger.exception("'" + cmdname + ": " + " ".join(tmp) + "'" + " too many parameters") + logger.exception( + "'" + cmdname + ": " + " ".join(tmp) + "'" + " too many parameters" + ) raise ValueError scene_objects.append(hertzian_dipole) @@ -109,11 +150,20 @@ def process_multicmds(multicmds): for cmdinstance in multicmds[cmdname]: tmp = cmdinstance.split() if len(tmp) < 5: - logger.exception("'" + cmdname + ": " + " ".join(tmp) + "'" + " requires at least five parameters") + logger.exception( + "'" + + cmdname + + ": " + + " ".join(tmp) + + "'" + + " requires at least five parameters" + ) raise ValueError if len(tmp) == 5: magnetic_dipole = MagneticDipole( - polarisation=tmp[0], p1=(float(tmp[1]), float(tmp[2]), float(tmp[3])), waveform_id=tmp[4] + polarisation=tmp[0], + p1=(float(tmp[1]), float(tmp[2]), float(tmp[3])), + waveform_id=tmp[4], ) elif len(tmp) == 7: magnetic_dipole = MagneticDipole( @@ -124,7 +174,9 @@ def process_multicmds(multicmds): end=float(tmp[6]), ) else: - logger.exception("'" + cmdname + ": " + " ".join(tmp) + "'" + " too many parameters") + logger.exception( + "'" + cmdname + ": " + " ".join(tmp) + "'" + " too many parameters" + ) raise ValueError scene_objects.append(magnetic_dipole) @@ -134,7 +186,14 @@ def process_multicmds(multicmds): for cmdinstance in multicmds[cmdname]: tmp = cmdinstance.split() if len(tmp) < 6: - logger.exception("'" + cmdname + ": " + " ".join(tmp) + "'" + " requires at least six parameters") + logger.exception( + "'" + + cmdname + + ": " + + " ".join(tmp) + + "'" + + " requires at least six parameters" + ) raise ValueError if len(tmp) == 6: @@ -154,7 +213,9 @@ def process_multicmds(multicmds): end=tmp[7], ) else: - logger.exception("'" + cmdname + ": " + " ".join(tmp) + "'" + " too many parameters") + logger.exception( + "'" + cmdname + ": " + " ".join(tmp) + "'" + " too many parameters" + ) raise ValueError scene_objects.append(tl) @@ -168,7 +229,9 @@ def process_multicmds(multicmds): raise ValueError if len(tmp) > 1: - ex_file = ExcitationFile(filepath=tmp[0], kind=tmp[1], fill_value=tmp[2]) + ex_file = ExcitationFile( + filepath=tmp[0], kind=tmp[1], fill_value=tmp[2] + ) else: ex_file = ExcitationFile(filepath=tmp[0]) @@ -179,12 +242,23 @@ def process_multicmds(multicmds): for cmdinstance in multicmds[cmdname]: tmp = cmdinstance.split() if len(tmp) != 3 and len(tmp) < 5: - logger.exception("'" + cmdname + ": " + " ".join(tmp) + "'" + " has an incorrect number of parameters") + logger.exception( + "'" + + cmdname + + ": " + + " ".join(tmp) + + "'" + + " has an incorrect number of parameters" + ) raise ValueError if len(tmp) == 3: rx = Rx(p1=(float(tmp[0]), float(tmp[1]), float(tmp[2]))) else: - rx = Rx(p1=(float(tmp[0]), float(tmp[1]), float(tmp[2])), id=tmp[3], outputs=[" ".join(tmp[4:])]) + rx = Rx( + p1=(float(tmp[0]), float(tmp[1]), float(tmp[2])), + id=tmp[3], + outputs=[" ".join(tmp[4:])], + ) scene_objects.append(rx) @@ -193,7 +267,14 @@ def process_multicmds(multicmds): for cmdinstance in multicmds[cmdname]: tmp = cmdinstance.split() if len(tmp) != 9: - logger.exception("'" + cmdname + ": " + " ".join(tmp) + "'" + " requires exactly nine parameters") + logger.exception( + "'" + + cmdname + + ": " + + " ".join(tmp) + + "'" + + " requires exactly nine parameters" + ) raise ValueError p1 = (float(tmp[0]), float(tmp[1]), float(tmp[2])) @@ -208,7 +289,14 @@ def process_multicmds(multicmds): for cmdinstance in multicmds[cmdname]: tmp = cmdinstance.split() if len(tmp) != 11: - logger.exception("'" + cmdname + ": " + " ".join(tmp) + "'" + " requires exactly eleven parameters") + logger.exception( + "'" + + cmdname + + ": " + + " ".join(tmp) + + "'" + + " requires exactly eleven parameters" + ) raise ValueError p1 = (float(tmp[0]), float(tmp[1]), float(tmp[2])) @@ -218,7 +306,9 @@ def process_multicmds(multicmds): try: iterations = int(tmp[9]) - snapshot = Snapshot(p1=p1, p2=p2, dl=dl, iterations=iterations, filename=filename) + snapshot = Snapshot( + p1=p1, p2=p2, dl=dl, iterations=iterations, filename=filename + ) except ValueError: time = float(tmp[9]) @@ -231,10 +321,23 @@ def process_multicmds(multicmds): for cmdinstance in multicmds[cmdname]: tmp = cmdinstance.split() if len(tmp) != 5: - logger.exception("'" + cmdname + ": " + " ".join(tmp) + "'" + " requires exactly five parameters") + logger.exception( + "'" + + cmdname + + ": " + + " ".join(tmp) + + "'" + + " requires exactly five parameters" + ) raise ValueError - material = Material(er=float(tmp[0]), se=float(tmp[1]), mr=float(tmp[2]), sm=float(tmp[3]), id=tmp[4]) + material = Material( + er=float(tmp[0]), + se=float(tmp[1]), + mr=float(tmp[2]), + sm=float(tmp[3]), + id=tmp[4], + ) scene_objects.append(material) cmdname = "#add_dispersion_debye" @@ -243,7 +346,14 @@ def process_multicmds(multicmds): tmp = cmdinstance.split() if len(tmp) < 4: - logger.exception("'" + cmdname + ": " + " ".join(tmp) + "'" + " requires at least four parameters") + logger.exception( + "'" + + cmdname + + ": " + + " ".join(tmp) + + "'" + + " requires at least four parameters" + ) raise ValueError poles = int(tmp[0]) @@ -255,7 +365,9 @@ def process_multicmds(multicmds): er_delta.append(float(tmp[pole])) tau.append(float(tmp[pole + 1])) - debye_dispersion = AddDebyeDispersion(poles=poles, er_delta=er_delta, tau=tau, material_ids=material_ids) + debye_dispersion = AddDebyeDispersion( + poles=poles, er_delta=er_delta, tau=tau, material_ids=material_ids + ) scene_objects.append(debye_dispersion) cmdname = "#add_dispersion_lorentz" @@ -264,7 +376,14 @@ def process_multicmds(multicmds): tmp = cmdinstance.split() if len(tmp) < 5: - logger.exception("'" + cmdname + ": " + " ".join(tmp) + "'" + " requires at least five parameters") + logger.exception( + "'" + + cmdname + + ": " + + " ".join(tmp) + + "'" + + " requires at least five parameters" + ) raise ValueError poles = int(tmp[0]) @@ -279,7 +398,11 @@ def process_multicmds(multicmds): alpha.append(float(tmp[pole + 2])) lorentz_dispersion = AddLorentzDispersion( - poles=poles, material_ids=material_ids, er_delta=er_delta, tau=tau, alpha=alpha + poles=poles, + material_ids=material_ids, + er_delta=er_delta, + tau=tau, + alpha=alpha, ) scene_objects.append(lorentz_dispersion) @@ -289,7 +412,14 @@ def process_multicmds(multicmds): tmp = cmdinstance.split() if len(tmp) < 5: - logger.exception("'" + cmdname + ": " + " ".join(tmp) + "'" + " requires at least five parameters") + logger.exception( + "'" + + cmdname + + ": " + + " ".join(tmp) + + "'" + + " requires at least five parameters" + ) raise ValueError poles = int(tmp[0]) @@ -301,7 +431,9 @@ def process_multicmds(multicmds): tau.append(float(tmp[pole])) alpha.append(float(tmp[pole + 1])) - drude_dispersion = AddDrudeDispersion(poles=poles, material_ids=material_ids, tau=tau, alpha=alpha) + drude_dispersion = AddDrudeDispersion( + poles=poles, material_ids=material_ids, tau=tau, alpha=alpha + ) scene_objects.append(drude_dispersion) cmdname = "#soil_peplinski" @@ -310,7 +442,14 @@ def process_multicmds(multicmds): tmp = cmdinstance.split() if len(tmp) != 7: - logger.exception("'" + cmdname + ": " + " ".join(tmp) + "'" + " requires at exactly seven parameters") + logger.exception( + "'" + + cmdname + + ": " + + " ".join(tmp) + + "'" + + " requires at exactly seven parameters" + ) raise ValueError soil = SoilPeplinski( sand_fraction=float(tmp[0]), @@ -328,14 +467,23 @@ def process_multicmds(multicmds): for cmdinstance in multicmds[cmdname]: tmp = cmdinstance.split() if len(tmp) != 11: - logger.exception("'" + cmdname + ": " + " ".join(tmp) + "'" + " requires exactly eleven parameters") + logger.exception( + "'" + + cmdname + + ": " + + " ".join(tmp) + + "'" + + " requires exactly eleven parameters" + ) raise ValueError p1 = float(tmp[0]), float(tmp[1]), float(tmp[2]) p2 = float(tmp[3]), float(tmp[4]), float(tmp[5]) dl = float(tmp[6]), float(tmp[7]), float(tmp[8]) - geometry_view = GeometryView(p1=p1, p2=p2, dl=dl, filename=tmp[9], output_type=tmp[10]) + geometry_view = GeometryView( + p1=p1, p2=p2, dl=dl, filename=tmp[9], output_type=tmp[10] + ) scene_objects.append(geometry_view) cmdname = "#geometry_objects_write" @@ -343,7 +491,14 @@ def process_multicmds(multicmds): for cmdinstance in multicmds[cmdname]: tmp = cmdinstance.split() if len(tmp) != 7: - logger.exception("'" + cmdname + ": " + " ".join(tmp) + "'" + " requires exactly seven parameters") + logger.exception( + "'" + + cmdname + + ": " + + " ".join(tmp) + + "'" + + " requires exactly seven parameters" + ) raise ValueError p1 = float(tmp[0]), float(tmp[1]), float(tmp[2]) @@ -357,7 +512,14 @@ def process_multicmds(multicmds): tmp = cmdinstance.split() if len(tmp) != 9: - logger.exception("'" + cmdname + ": " + " ".join(tmp) + "'" + " requires at exactly nine parameters") + logger.exception( + "'" + + cmdname + + ": " + + " ".join(tmp) + + "'" + + " requires at exactly nine parameters" + ) raise ValueError material_range = MaterialRange( er_lower=float(tmp[0]), @@ -378,7 +540,14 @@ def process_multicmds(multicmds): tmp = cmdinstance.split() if len(tmp) < 2: - logger.exception("'" + cmdname + ": " + " ".join(tmp) + "'" + " requires at least two parameters") + logger.exception( + "'" + + cmdname + + ": " + + " ".join(tmp) + + "'" + + " requires at least two parameters" + ) raise ValueError tokens = len(tmp) @@ -395,7 +564,14 @@ def process_multicmds(multicmds): tmp = cmdinstance.split() if len(tmp) != 12: - logger.exception("'" + cmdname + ": " + " ".join(tmp) + "'" + " requires exactly twelve parameters") + logger.exception( + "'" + + cmdname + + ": " + + " ".join(tmp) + + "'" + + " requires exactly twelve parameters" + ) raise ValueError pml_cfs = PMLCFS( diff --git a/gprMax/hash_cmds_singleuse.py b/gprMax/hash_cmds_singleuse.py index 78a683f6..fabd5231 100644 --- a/gprMax/hash_cmds_singleuse.py +++ b/gprMax/hash_cmds_singleuse.py @@ -18,9 +18,18 @@ import logging -from .cmds_singleuse import (Discretisation, Domain, OMPThreads, OutputDir, - PMLProps, RxSteps, SrcSteps, - TimeStepStabilityFactor, TimeWindow, Title) +from .cmds_singleuse import ( + Discretisation, + Domain, + OMPThreads, + OutputDir, + PMLProps, + RxSteps, + SrcSteps, + TimeStepStabilityFactor, + TimeWindow, + Title, +) logger = logging.getLogger(__name__) @@ -54,7 +63,9 @@ def process_singlecmds(singlecmds): if singlecmds[cmd] is not None: tmp = tuple(int(x) for x in singlecmds[cmd].split()) if len(tmp) != 1: - logger.exception(f"{cmd} requires exactly one parameter to specify the number of CPU OpenMP threads to use") + logger.exception( + f"{cmd} requires exactly one parameter to specify the number of CPU OpenMP threads to use" + ) raise ValueError omp_threads = OMPThreads(n=tmp[0]) @@ -144,7 +155,12 @@ def process_singlecmds(singlecmds): pml_props = PMLProps(thickness=int(tmp[0])) else: pml_props = PMLProps( - x0=int(tmp[0]), y0=int(tmp[1]), z0=int(tmp[2]), xmax=int(tmp[3]), ymax=int(tmp[4]), zmax=int(tmp[5]) + x0=int(tmp[0]), + y0=int(tmp[1]), + z0=int(tmp[2]), + xmax=int(tmp[3]), + ymax=int(tmp[4]), + zmax=int(tmp[5]), ) scene_objects.append(pml_props) diff --git a/gprMax/materials.py b/gprMax/materials.py index 9b5af7a2..0af8ffdc 100644 --- a/gprMax/materials.py +++ b/gprMax/materials.py @@ -168,7 +168,9 @@ class DispersiveMaterial(Material): # tau for Lorentz materials are pole frequencies # alpha for Lorentz materials are the damping coefficients wp2 = (2 * np.pi * self.tau[x]) ** 2 - self.w[x] = -1j * ((wp2 * self.deltaer[x]) / np.sqrt(wp2 - self.alpha[x] ** 2)) + self.w[x] = -1j * ( + (wp2 * self.deltaer[x]) / np.sqrt(wp2 - self.alpha[x] ** 2) + ) self.q[x] = -self.alpha[x] + (1j * np.sqrt(wp2 - self.alpha[x] ** 2)) elif "drude" in self.type: # tau for Drude materials are pole frequencies @@ -239,7 +241,15 @@ class PeplinskiSoil: by Peplinski (http://dx.doi.org/10.1109/36.387598). """ - def __init__(self, ID, sandfraction, clayfraction, bulkdensity, sandpartdensity, watervolfraction): + def __init__( + self, + ID, + sandfraction, + clayfraction, + bulkdensity, + sandpartdensity, + watervolfraction, + ): """ Args: ID: string for name of the soil. @@ -282,7 +292,9 @@ class PeplinskiSoil: erealw = watereri + (waterdeltaer / (1 + (w * watertau) ** 2)) a = 0.65 # Experimentally derived constant - es = (1.01 + 0.44 * self.rs) ** 2 - 0.062 #  Relative permittivity of sand particles + es = ( + 1.01 + 0.44 * self.rs + ) ** 2 - 0.062 #  Relative permittivity of sand particles b1 = 1.2748 - 0.519 * self.S - 0.152 * self.C b2 = 1.33797 - 0.603 * self.S - 0.166 * self.C @@ -304,7 +316,12 @@ class PeplinskiSoil: muiter = np.nditer(mumaterials, flags=["c_index"]) while not muiter.finished: # Real part for frequencies in the range 1.4GHz to 18GHz - er = (1 + (self.rb / self.rs) * ((es**a) - 1) + (muiter[0] ** b1 * erealw**a) - muiter[0]) ** (1 / a) + er = ( + 1 + + (self.rb / self.rs) * ((es**a) - 1) + + (muiter[0] ** b1 * erealw**a) + - muiter[0] + ) ** (1 / a) # Real part for frequencies in the range 0.3GHz to 1.3GHz (linear # correction to 1.4-18GHz value) er = 1.15 * er - 0.68 @@ -313,7 +330,9 @@ class PeplinskiSoil: eri = er - (muiter[0] ** (b2 / a) * waterdeltaer) # Effective conductivity - sig = muiter[0] ** (b2 / a) * ((sigf * (self.rs - self.rb)) / (self.rs * muiter[0])) + sig = muiter[0] ** (b2 / a) * ( + (sigf * (self.rs - self.rb)) / (self.rs * muiter[0]) + ) # Create individual materials m = DispersiveMaterial(len(G.materials), None) @@ -406,7 +425,9 @@ class RangeMaterial: sm = romaterials[iter] # Check to see if the material already exists before creating a new one - requiredID = f"|{float(er):.4f}+{float(se):.4f}+{float(mr):.4f}+{float(sm):.4f}|" + requiredID = ( + f"|{float(er):.4f}+{float(se):.4f}+{float(mr):.4f}+{float(sm):.4f}|" + ) material = next((x for x in G.materials if x.ID == requiredID), None) if iter == 0 and material: self.matID.append(material.numID) @@ -456,7 +477,9 @@ class ListMaterial: self.matID.append(material.numID) if not material: - logger.exception(self.__str__() + f" material(s) {material} do not exist") + logger.exception( + self.__str__() + f" material(s) {material} do not exist" + ) raise ValueError @@ -495,11 +518,16 @@ def calculate_water_properties(T=25, S=0): # Properties of water from: https://doi.org/10.1109/JOE.1977.1145319 eri = 4.9 er = 88.045 - 0.4147 * T + 6.295e-4 * T**2 + 1.075e-5 * T**3 - tau = (1 / (2 * np.pi)) * (1.1109e-10 - 3.824e-12 * T + 6.938e-14 * T**2 - 5.096e-16 * T**3) + tau = (1 / (2 * np.pi)) * ( + 1.1109e-10 - 3.824e-12 * T + 6.938e-14 * T**2 - 5.096e-16 * T**3 + ) delta = 25 - T beta = ( - 2.033e-2 + 1.266e-4 * delta + 2.464e-6 * delta**2 - S * (1.849e-5 - 2.551e-7 * delta + 2.551e-8 * delta**2) + 2.033e-2 + + 1.266e-4 * delta + + 2.464e-6 * delta**2 + - S * (1.849e-5 - 2.551e-7 * delta + 2.551e-8 * delta**2) ) sig_25s = S * (0.182521 - 1.46192e-3 * S + 2.09324e-5 * S**2 - 1.28205e-7 * S**3) sig = sig_25s * np.exp(-delta * beta) @@ -608,8 +636,20 @@ def process_materials(G): material.calculate_update_coeffsH(G) # Add update coefficients to overall storage for all materials - G.updatecoeffsE[material.numID, :] = material.CA, material.CBx, material.CBy, material.CBz, material.srce - G.updatecoeffsH[material.numID, :] = material.DA, material.DBx, material.DBy, material.DBz, material.srcm + G.updatecoeffsE[material.numID, :] = ( + material.CA, + material.CBx, + material.CBy, + material.CBz, + material.srce, + ) + G.updatecoeffsH[material.numID, :] = ( + material.DA, + material.DBx, + material.DBy, + material.DBz, + material.srcm, + ) # Add update coefficients to overall storage for dispersive materials if hasattr(material, "poles"): @@ -632,11 +672,15 @@ def process_materials(G): ] if config.get_model_config().materials["maxpoles"] > 0: if "debye" in material.type: - materialtext.append("\n".join(f"{deltaer:g}" for deltaer in material.deltaer)) + materialtext.append( + "\n".join(f"{deltaer:g}" for deltaer in material.deltaer) + ) materialtext.append("\n".join(f"{tau:g}" for tau in material.tau)) materialtext.extend(["", "", ""]) elif "lorentz" in material.type: - materialtext.append(", ".join(f"{deltaer:g}" for deltaer in material.deltaer)) + materialtext.append( + ", ".join(f"{deltaer:g}" for deltaer in material.deltaer) + ) materialtext.append("") materialtext.append(", ".join(f"{tau:g}" for tau in material.tau)) materialtext.append(", ".join(f"{alpha:g}" for alpha in material.alpha)) @@ -649,7 +693,9 @@ def process_materials(G): else: materialtext.extend(["", "", "", "", ""]) - materialtext.extend((f"{material.mr:g}", f"{material.sm:g}", material.averagable)) + materialtext.extend( + (f"{material.mr:g}", f"{material.sm:g}", material.averagable) + ) materialsdata.append(materialtext) return materialsdata diff --git a/gprMax/model_build_run.py b/gprMax/model_build_run.py index 04e50bda..8f8be85d 100644 --- a/gprMax/model_build_run.py +++ b/gprMax/model_build_run.py @@ -33,8 +33,7 @@ from tqdm import tqdm import gprMax.config as config -from .cython.yee_cell_build import (build_electric_components, - build_magnetic_components) +from .cython.yee_cell_build import build_electric_components, build_magnetic_components from .fields_outputs import write_hdf5_outputfile from .geometry_outputs import save_geometry_views from .grid import dispersion_analysis @@ -43,8 +42,7 @@ from .materials import process_materials from .pml import CFS, build_pml, print_pml_info from .scene import Scene from .snapshots import save_snapshots -from .utilities.host_info import (mem_check_build_all, mem_check_run_all, - set_omp_threads) +from .utilities.host_info import mem_check_build_all, mem_check_run_all, set_omp_threads from .utilities.utilities import get_terminal_width logger = logging.getLogger(__name__) @@ -62,7 +60,9 @@ class ModelBuildRun: # used with threaded model building methods, e.g. fractals. Can be # changed by the user via #num_threads command in input file or via API # later for use with CPU solver. - config.get_model_config().ompthreads = set_omp_threads(config.get_model_config().ompthreads) + config.get_model_config().ompthreads = set_omp_threads( + config.get_model_config().ompthreads + ) def build(self): """Builds the Yee cells for a model.""" @@ -75,7 +75,9 @@ class ModelBuildRun: # Normal model reading/building process; bypassed if geometry information to be reused self.reuse_geometry() if config.get_model_config().reuse_geometry else self.build_geometry() - logger.info(f"\nOutput directory: {config.get_model_config().output_file_path.parent.resolve()}") + logger.info( + f"\nOutput directory: {config.get_model_config().output_file_path.parent.resolve()}" + ) # Adjust position of simple sources and receivers if required if G.srcsteps[0] != 0 or G.srcsteps[1] != 0 or G.srcsteps[2] != 0: @@ -83,13 +85,20 @@ class ModelBuildRun: if config.model_num == 0: if ( source.xcoord + G.srcsteps[0] * config.sim_config.model_end < 0 - or source.xcoord + G.srcsteps[0] * config.sim_config.model_end > G.nx - or source.ycoord + G.srcsteps[1] * config.sim_config.model_end < 0 - or source.ycoord + G.srcsteps[1] * config.sim_config.model_end > G.ny - or source.zcoord + G.srcsteps[2] * config.sim_config.model_end < 0 - or source.zcoord + G.srcsteps[2] * config.sim_config.model_end > G.nz + or source.xcoord + G.srcsteps[0] * config.sim_config.model_end + > G.nx + or source.ycoord + G.srcsteps[1] * config.sim_config.model_end + < 0 + or source.ycoord + G.srcsteps[1] * config.sim_config.model_end + > G.ny + or source.zcoord + G.srcsteps[2] * config.sim_config.model_end + < 0 + or source.zcoord + G.srcsteps[2] * config.sim_config.model_end + > G.nz ): - logger.exception("Source(s) will be stepped to a position outside the domain.") + logger.exception( + "Source(s) will be stepped to a position outside the domain." + ) raise ValueError source.xcoord = source.xcoordorigin + config.model_num * G.srcsteps[0] source.ycoord = source.ycoordorigin + config.model_num * G.srcsteps[1] @@ -99,21 +108,38 @@ class ModelBuildRun: if config.model_num == 0: if ( receiver.xcoord + G.rxsteps[0] * config.sim_config.model_end < 0 - or receiver.xcoord + G.rxsteps[0] * config.sim_config.model_end > G.nx - or receiver.ycoord + G.rxsteps[1] * config.sim_config.model_end < 0 - or receiver.ycoord + G.rxsteps[1] * config.sim_config.model_end > G.ny - or receiver.zcoord + G.rxsteps[2] * config.sim_config.model_end < 0 - or receiver.zcoord + G.rxsteps[2] * config.sim_config.model_end > G.nz + or receiver.xcoord + G.rxsteps[0] * config.sim_config.model_end + > G.nx + or receiver.ycoord + G.rxsteps[1] * config.sim_config.model_end + < 0 + or receiver.ycoord + G.rxsteps[1] * config.sim_config.model_end + > G.ny + or receiver.zcoord + G.rxsteps[2] * config.sim_config.model_end + < 0 + or receiver.zcoord + G.rxsteps[2] * config.sim_config.model_end + > G.nz ): - logger.exception("Receiver(s) will be stepped to a position outside the domain.") + logger.exception( + "Receiver(s) will be stepped to a position outside the domain." + ) raise ValueError - receiver.xcoord = receiver.xcoordorigin + config.model_num * G.rxsteps[0] - receiver.ycoord = receiver.ycoordorigin + config.model_num * G.rxsteps[1] - receiver.zcoord = receiver.zcoordorigin + config.model_num * G.rxsteps[2] + receiver.xcoord = ( + receiver.xcoordorigin + config.model_num * G.rxsteps[0] + ) + receiver.ycoord = ( + receiver.ycoordorigin + config.model_num * G.rxsteps[1] + ) + receiver.zcoord = ( + receiver.zcoordorigin + config.model_num * G.rxsteps[2] + ) # Write files for any geometry views and geometry object outputs gvs = G.geometryviews + [gv for sg in G.subgrids for gv in sg.geometryviews] - if not gvs and not G.geometryobjectswrite and config.sim_config.args.geometry_only: + if ( + not gvs + and not G.geometryobjectswrite + and config.sim_config.args.geometry_only + ): logger.exception("\nNo geometry views or geometry objects found.") raise ValueError save_geometry_views(gvs) @@ -154,7 +180,11 @@ class ModelBuildRun: for grid in grids: if config.get_model_config().materials["maxpoles"] != 0: config.get_model_config().materials["drudelorentz"] = any( - [m for m in grid.materials if "drude" in m.type or "lorentz" in m.type] + [ + m + for m in grid.materials + if "drude" in m.type or "lorentz" in m.type + ] ) # Set data type if any dispersive materials (must be done before memory checks) @@ -171,13 +201,13 @@ class ModelBuildRun: if total_mem_build > total_mem_run: logger.info( - f'\nMemory required (estimated): {" + ".join(mem_strs_build)} + ' + f"\nMemory required (estimated): {' + '.join(mem_strs_build)} + " f"~{humanize.naturalsize(config.get_model_config().mem_overhead)} " f"overhead = {humanize.naturalsize(total_mem_build)}" ) else: logger.info( - f'\nMemory required (estimated): {" + ".join(mem_strs_run)} + ' + f"\nMemory required (estimated): {' + '.join(mem_strs_run)} + " f"~{humanize.naturalsize(config.get_model_config().mem_overhead)} " f"overhead = {humanize.naturalsize(total_mem_run)}" ) @@ -206,9 +236,13 @@ class ModelBuildRun: results = dispersion_analysis(gb.grid) if results["error"]: logger.warning( - f"\nNumerical dispersion analysis [{gb.grid.name}] " f"not carried out as {results['error']}" + f"\nNumerical dispersion analysis [{gb.grid.name}] " + f"not carried out as {results['error']}" ) - elif results["N"] < config.get_model_config().numdispersion["mingridsampling"]: + elif ( + results["N"] + < config.get_model_config().numdispersion["mingridsampling"] + ): logger.exception( f"\nNon-physical wave propagation in [{gb.grid.name}] " f"detected. Material '{results['material'].ID}' " @@ -220,7 +254,8 @@ class ModelBuildRun: raise ValueError elif ( results["deltavp"] - and np.abs(results["deltavp"]) > config.get_model_config().numdispersion["maxnumericaldisp"] + and np.abs(results["deltavp"]) + > config.get_model_config().numdispersion["maxnumericaldisp"] ): logger.warning( f"\n[{gb.grid.name}] has potentially significant " @@ -248,7 +283,9 @@ class ModelBuildRun: f"{config.sim_config.input_file_path}" ) config.get_model_config().inputfilestr = ( - Fore.GREEN + f"{s} {'-' * (get_terminal_width() - 1 - len(s))}\n" + Style.RESET_ALL + Fore.GREEN + + f"{s} {'-' * (get_terminal_width() - 1 - len(s))}\n" + + Style.RESET_ALL ) logger.basic(config.get_model_config().inputfilestr) for grid in [self.G] + self.G.subgrids: @@ -280,7 +317,9 @@ class ModelBuildRun: sg_rxs = [True for sg in self.G.subgrids if sg.rxs] sg_tls = [True for sg in self.G.subgrids if sg.transmissionlines] if self.G.rxs or sg_rxs or self.G.transmissionlines or sg_tls: - write_hdf5_outputfile(config.get_model_config().output_file_path_ext, self.G) + write_hdf5_outputfile( + config.get_model_config().output_file_path_ext, self.G + ) # Write any snapshots to file for each grid for grid in [self.G] + self.G.subgrids: @@ -301,7 +340,10 @@ class ModelBuildRun: f"on {config.sim_config.hostinfo['hostname']} " f"with OpenMP backend using {config.get_model_config().ompthreads} thread(s)" ) - if config.get_model_config().ompthreads > config.sim_config.hostinfo["physicalcores"]: + if ( + config.get_model_config().ompthreads + > config.sim_config.hostinfo["physicalcores"] + ): logger.warning( f"You have specified more threads ({config.get_model_config().ompthreads}) " f"than available physical CPU cores ({config.sim_config.hostinfo['physicalcores']}). " @@ -310,17 +352,22 @@ class ModelBuildRun: elif config.sim_config.general["solver"] in ["cuda", "opencl"]: if config.sim_config.general["solver"] == "opencl": solvername = "OpenCL" - platformname = " ".join(config.get_model_config().device["dev"].platform.name.split()) + " with " + platformname = ( + " ".join( + config.get_model_config().device["dev"].platform.name.split() + ) + + " with " + ) devicename = ( - f'Device {config.get_model_config().device["deviceID"]}: ' - f'{" ".join(config.get_model_config().device["dev"].name.split())}' + f"Device {config.get_model_config().device['deviceID']}: " + f"{' '.join(config.get_model_config().device['dev'].name.split())}" ) else: solvername = "CUDA" platformname = "" devicename = ( - f'Device {config.get_model_config().device["deviceID"]}: ' - f'{" ".join(config.get_model_config().device["dev"].name().split())}' + f"Device {config.get_model_config().device['deviceID']}: " + f"{' '.join(config.get_model_config().device['dev'].name().split())}" ) logger.basic( @@ -353,11 +400,15 @@ class ModelBuildRun: if config.sim_config.general["solver"] == "cuda": mem_str = f" host + ~{humanize.naturalsize(solver.memused)} device" elif config.sim_config.general["solver"] == "opencl": - mem_str = f" host + unknown for device" + mem_str = f" host + unknown for device" - logger.info(f"\nMemory used (estimated): " + f"~{humanize.naturalsize(self.p.memory_full_info().uss)}{mem_str}") logger.info( - f"Time taken: " + f"{humanize.precisedelta(datetime.timedelta(seconds=solver.solvetime), format='%0.4f')}" + f"\nMemory used (estimated): " + + f"~{humanize.naturalsize(self.p.memory_full_info().uss)}{mem_str}" + ) + logger.info( + f"Time taken: " + + f"{humanize.precisedelta(datetime.timedelta(seconds=solver.solvetime), format='%0.4f')}" ) @@ -390,9 +441,13 @@ class GridBuilder: file=sys.stdout, 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 + ) pbar.update() - build_magnetic_components(self.grid.solid, self.grid.rigidH, self.grid.ID, self.grid) + build_magnetic_components( + self.grid.solid, self.grid.rigidH, self.grid.ID, self.grid + ) pbar.update() pbar.close() diff --git a/gprMax/mpi.py b/gprMax/mpi.py index 6a625946..bbfd9c2a 100644 --- a/gprMax/mpi.py +++ b/gprMax/mpi.py @@ -165,7 +165,9 @@ class MPIExecutor(object): self.busy = [False] * len(self.workers) if self.is_master(): - logger.basic(f"\n({self.comm.name}) - Master: {self.master}, Workers: {self.workers}") + logger.basic( + f"\n({self.comm.name}) - Master: {self.master}, Workers: {self.workers}" + ) def __enter__(self): """Context manager enter. Only the master returns an executor, all other @@ -222,7 +224,9 @@ class MPIExecutor(object): """Joins the workers.""" if not self.is_master(): return - logger.debug(f"({self.comm.name}) - Terminating. Sending sentinel to all workers.") + logger.debug( + f"({self.comm.name}) - Terminating. Sending sentinel to all workers." + ) # Send sentinel to all workers for worker in self.workers: self.comm.send(None, dest=worker, tag=Tags.EXIT) @@ -269,7 +273,9 @@ class MPIExecutor(object): for i, worker in enumerate(self.workers): if self.comm.Iprobe(source=worker, tag=Tags.DONE): job_idx, result = self.comm.recv(source=worker, tag=Tags.DONE) - logger.debug(f"({self.comm.name}) - Received finished job {job_idx} from worker {worker:d}.") + logger.debug( + f"({self.comm.name}) - Received finished job {job_idx} from worker {worker:d}." + ) results[job_idx] = result self.busy[i] = False elif self.comm.Iprobe(source=worker, tag=Tags.READY): @@ -277,10 +283,16 @@ class MPIExecutor(object): self.comm.recv(source=worker, tag=Tags.READY) self.busy[i] = True job_idx = num_jobs - len(my_jobs) - logger.debug(f"({self.comm.name}) - Sending job {job_idx} to worker {worker:d}.") - self.comm.send((job_idx, my_jobs.pop(0)), dest=worker, tag=Tags.START) + logger.debug( + f"({self.comm.name}) - Sending job {job_idx} to worker {worker:d}." + ) + self.comm.send( + (job_idx, my_jobs.pop(0)), dest=worker, tag=Tags.START + ) elif self.comm.Iprobe(source=worker, tag=Tags.EXIT): - logger.debug(f"({self.comm.name}) - Worker on rank {worker:d} has terminated.") + logger.debug( + f"({self.comm.name}) - Worker on rank {worker:d} has terminated." + ) self.comm.recv(source=worker, tag=Tags.EXIT) self.busy[i] = False @@ -304,16 +316,22 @@ class MPIExecutor(object): while True: self.comm.send(None, dest=self.master, tag=Tags.READY) - logger.debug(f"({self.comm.name}) - Worker on rank {self.rank} waiting for job.") + logger.debug( + f"({self.comm.name}) - Worker on rank {self.rank} waiting for job." + ) data = self.comm.recv(source=self.master, tag=MPI.ANY_TAG, status=status) tag = status.tag if tag == Tags.START: job_idx, work = data - logger.debug(f"({self.comm.name}) - Received job {job_idx} (work={work}).") + logger.debug( + f"({self.comm.name}) - Received job {job_idx} (work={work})." + ) result = self.__guarded_work(work) - logger.debug(f"({self.comm.name}) - Finished job. Sending results to master.") + logger.debug( + f"({self.comm.name}) - Finished job. Sending results to master." + ) self.comm.send((job_idx, result), dest=self.master, tag=Tags.DONE) elif tag == Tags.EXIT: logger.debug(f"({self.comm.name}) - Received sentinel from master.") diff --git a/gprMax/pml.py b/gprMax/pml.py index eef8a822..f20ab34b 100644 --- a/gprMax/pml.py +++ b/gprMax/pml.py @@ -45,7 +45,15 @@ class CFSParameter: } scalingdirections = ["forward", "reverse"] - def __init__(self, ID=None, scaling="polynomial", scalingprofile=None, scalingdirection="forward", min=0, max=0): + def __init__( + self, + ID=None, + scaling="polynomial", + scalingprofile=None, + scalingdirection="forward", + min=0, + max=0, + ): """ Args: ID: string identifier for CFS parameter, can be: 'alpha', 'kappa' or @@ -95,7 +103,9 @@ class CFS: # Calculation of the maximum value of sigma from http://dx.doi.org/10.1109/8.546249 m = CFSParameter.scalingprofiles[self.sigma.scalingprofile] - self.sigma.max = (0.8 * (m + 1)) / (config.sim_config.em_consts["z0"] * d * np.sqrt(er * mr)) + self.sigma.max = (0.8 * (m + 1)) / ( + config.sim_config.em_consts["z0"] * d * np.sqrt(er * mr) + ) def scaling_polynomial(self, order, Evalues, Hvalues): """Applies the polynomial to be used for the scaling profile for @@ -115,7 +125,10 @@ class CFS: magnetic PML update. """ - tmp = (np.linspace(0, (len(Evalues) - 1) + 0.5, num=2 * len(Evalues)) / (len(Evalues) - 1)) ** order + tmp = ( + np.linspace(0, (len(Evalues) - 1) + 0.5, num=2 * len(Evalues)) + / (len(Evalues) - 1) + ) ** order Evalues = tmp[0:-1:2] Hvalues = tmp[1::2] @@ -138,8 +151,12 @@ class CFS: # Extra cell of thickness added to allow correct scaling of electric and # magnetic values - Evalues = np.zeros(thickness + 1, dtype=config.sim_config.dtypes["float_or_double"]) - Hvalues = np.zeros(thickness + 1, dtype=config.sim_config.dtypes["float_or_double"]) + Evalues = np.zeros( + thickness + 1, dtype=config.sim_config.dtypes["float_or_double"] + ) + Hvalues = np.zeros( + thickness + 1, dtype=config.sim_config.dtypes["float_or_double"] + ) if parameter.scalingprofile == "constant": Evalues += parameter.max @@ -237,7 +254,10 @@ class PML: kappamin = sum(cfs.kappa.min for cfs in self.CFS) if kappamin < 1: - logger.exception(f"Sum of kappamin value(s) for PML is {kappamin} " "and must be greater than one.") + logger.exception( + f"Sum of kappamin value(s) for PML is {kappamin} " + "and must be greater than one." + ) raise ValueError def initialise_field_arrays(self): @@ -245,42 +265,54 @@ class PML: if self.direction[0] == "x": self.EPhi1 = np.zeros( - (len(self.CFS), self.nx + 1, self.ny, self.nz + 1), dtype=config.sim_config.dtypes["float_or_double"] + (len(self.CFS), self.nx + 1, self.ny, self.nz + 1), + dtype=config.sim_config.dtypes["float_or_double"], ) self.EPhi2 = np.zeros( - (len(self.CFS), self.nx + 1, self.ny + 1, self.nz), dtype=config.sim_config.dtypes["float_or_double"] + (len(self.CFS), self.nx + 1, self.ny + 1, self.nz), + dtype=config.sim_config.dtypes["float_or_double"], ) self.HPhi1 = np.zeros( - (len(self.CFS), self.nx, self.ny + 1, self.nz), dtype=config.sim_config.dtypes["float_or_double"] + (len(self.CFS), self.nx, self.ny + 1, self.nz), + dtype=config.sim_config.dtypes["float_or_double"], ) self.HPhi2 = np.zeros( - (len(self.CFS), self.nx, self.ny, self.nz + 1), dtype=config.sim_config.dtypes["float_or_double"] + (len(self.CFS), self.nx, self.ny, self.nz + 1), + dtype=config.sim_config.dtypes["float_or_double"], ) elif self.direction[0] == "y": self.EPhi1 = np.zeros( - (len(self.CFS), self.nx, self.ny + 1, self.nz + 1), dtype=config.sim_config.dtypes["float_or_double"] + (len(self.CFS), self.nx, self.ny + 1, self.nz + 1), + dtype=config.sim_config.dtypes["float_or_double"], ) self.EPhi2 = np.zeros( - (len(self.CFS), self.nx + 1, self.ny + 1, self.nz), dtype=config.sim_config.dtypes["float_or_double"] + (len(self.CFS), self.nx + 1, self.ny + 1, self.nz), + dtype=config.sim_config.dtypes["float_or_double"], ) self.HPhi1 = np.zeros( - (len(self.CFS), self.nx + 1, self.ny, self.nz), dtype=config.sim_config.dtypes["float_or_double"] + (len(self.CFS), self.nx + 1, self.ny, self.nz), + dtype=config.sim_config.dtypes["float_or_double"], ) self.HPhi2 = np.zeros( - (len(self.CFS), self.nx, self.ny, self.nz + 1), dtype=config.sim_config.dtypes["float_or_double"] + (len(self.CFS), self.nx, self.ny, self.nz + 1), + dtype=config.sim_config.dtypes["float_or_double"], ) elif self.direction[0] == "z": self.EPhi1 = np.zeros( - (len(self.CFS), self.nx, self.ny + 1, self.nz + 1), dtype=config.sim_config.dtypes["float_or_double"] + (len(self.CFS), self.nx, self.ny + 1, self.nz + 1), + dtype=config.sim_config.dtypes["float_or_double"], ) self.EPhi2 = np.zeros( - (len(self.CFS), self.nx + 1, self.ny, self.nz + 1), dtype=config.sim_config.dtypes["float_or_double"] + (len(self.CFS), self.nx + 1, self.ny, self.nz + 1), + dtype=config.sim_config.dtypes["float_or_double"], ) self.HPhi1 = np.zeros( - (len(self.CFS), self.nx + 1, self.ny, self.nz), dtype=config.sim_config.dtypes["float_or_double"] + (len(self.CFS), self.nx + 1, self.ny, self.nz), + dtype=config.sim_config.dtypes["float_or_double"], ) self.HPhi2 = np.zeros( - (len(self.CFS), self.nx, self.ny + 1, self.nz), dtype=config.sim_config.dtypes["float_or_double"] + (len(self.CFS), self.nx, self.ny + 1, self.nz), + dtype=config.sim_config.dtypes["float_or_double"], ) def calculate_update_coeffs(self, er, mr): @@ -291,14 +323,38 @@ class PML: mr: float of average permeability of underlying material """ - self.ERA = np.zeros((len(self.CFS), self.thickness), dtype=config.sim_config.dtypes["float_or_double"]) - self.ERB = np.zeros((len(self.CFS), self.thickness), dtype=config.sim_config.dtypes["float_or_double"]) - self.ERE = np.zeros((len(self.CFS), self.thickness), dtype=config.sim_config.dtypes["float_or_double"]) - self.ERF = np.zeros((len(self.CFS), self.thickness), dtype=config.sim_config.dtypes["float_or_double"]) - self.HRA = np.zeros((len(self.CFS), self.thickness), dtype=config.sim_config.dtypes["float_or_double"]) - self.HRB = np.zeros((len(self.CFS), self.thickness), dtype=config.sim_config.dtypes["float_or_double"]) - self.HRE = np.zeros((len(self.CFS), self.thickness), dtype=config.sim_config.dtypes["float_or_double"]) - self.HRF = np.zeros((len(self.CFS), self.thickness), dtype=config.sim_config.dtypes["float_or_double"]) + self.ERA = np.zeros( + (len(self.CFS), self.thickness), + dtype=config.sim_config.dtypes["float_or_double"], + ) + self.ERB = np.zeros( + (len(self.CFS), self.thickness), + dtype=config.sim_config.dtypes["float_or_double"], + ) + self.ERE = np.zeros( + (len(self.CFS), self.thickness), + dtype=config.sim_config.dtypes["float_or_double"], + ) + self.ERF = np.zeros( + (len(self.CFS), self.thickness), + dtype=config.sim_config.dtypes["float_or_double"], + ) + self.HRA = np.zeros( + (len(self.CFS), self.thickness), + dtype=config.sim_config.dtypes["float_or_double"], + ) + self.HRB = np.zeros( + (len(self.CFS), self.thickness), + dtype=config.sim_config.dtypes["float_or_double"], + ) + self.HRE = np.zeros( + (len(self.CFS), self.thickness), + dtype=config.sim_config.dtypes["float_or_double"], + ) + self.HRF = np.zeros( + (len(self.CFS), self.thickness), + dtype=config.sim_config.dtypes["float_or_double"], + ) for x, cfs in enumerate(self.CFS): if not cfs.sigma.max: @@ -310,20 +366,30 @@ class PML: # Define different parameters depending on PML formulation if self.G.pmls["formulation"] == "HORIPML": # HORIPML electric update coefficients - tmp = (2 * config.sim_config.em_consts["e0"] * Ekappa) + self.G.dt * (Ealpha * Ekappa + Esigma) - self.ERA[x, :] = (2 * config.sim_config.em_consts["e0"] + self.G.dt * Ealpha) / tmp + tmp = (2 * config.sim_config.em_consts["e0"] * Ekappa) + self.G.dt * ( + Ealpha * Ekappa + Esigma + ) + self.ERA[x, :] = ( + 2 * config.sim_config.em_consts["e0"] + self.G.dt * Ealpha + ) / tmp self.ERB[x, :] = (2 * config.sim_config.em_consts["e0"] * Ekappa) / tmp self.ERE[x, :] = ( - (2 * config.sim_config.em_consts["e0"] * Ekappa) - self.G.dt * (Ealpha * Ekappa + Esigma) + (2 * config.sim_config.em_consts["e0"] * Ekappa) + - self.G.dt * (Ealpha * Ekappa + Esigma) ) / tmp self.ERF[x, :] = (2 * Esigma * self.G.dt) / (Ekappa * tmp) # HORIPML magnetic update coefficients - tmp = (2 * config.sim_config.em_consts["e0"] * Hkappa) + self.G.dt * (Halpha * Hkappa + Hsigma) - self.HRA[x, :] = (2 * config.sim_config.em_consts["e0"] + self.G.dt * Halpha) / tmp + tmp = (2 * config.sim_config.em_consts["e0"] * Hkappa) + self.G.dt * ( + Halpha * Hkappa + Hsigma + ) + self.HRA[x, :] = ( + 2 * config.sim_config.em_consts["e0"] + self.G.dt * Halpha + ) / tmp self.HRB[x, :] = (2 * config.sim_config.em_consts["e0"] * Hkappa) / tmp self.HRE[x, :] = ( - (2 * config.sim_config.em_consts["e0"] * Hkappa) - self.G.dt * (Halpha * Hkappa + Hsigma) + (2 * config.sim_config.em_consts["e0"] * Hkappa) + - self.G.dt * (Halpha * Hkappa + Hsigma) ) / tmp self.HRF[x, :] = (2 * Hsigma * self.G.dt) / (Hkappa * tmp) @@ -332,14 +398,18 @@ class PML: tmp = 2 * config.sim_config.em_consts["e0"] + self.G.dt * Ealpha self.ERA[x, :] = Ekappa + (self.G.dt * Esigma) / tmp self.ERB[x, :] = (2 * config.sim_config.em_consts["e0"]) / tmp - self.ERE[x, :] = ((2 * config.sim_config.em_consts["e0"]) - self.G.dt * Ealpha) / tmp + self.ERE[x, :] = ( + (2 * config.sim_config.em_consts["e0"]) - self.G.dt * Ealpha + ) / tmp self.ERF[x, :] = (2 * Esigma * self.G.dt) / tmp # MRIPML magnetic update coefficients tmp = 2 * config.sim_config.em_consts["e0"] + self.G.dt * Halpha self.HRA[x, :] = Hkappa + (self.G.dt * Hsigma) / tmp self.HRB[x, :] = (2 * config.sim_config.em_consts["e0"]) / tmp - self.HRE[x, :] = ((2 * config.sim_config.em_consts["e0"]) - self.G.dt * Halpha) / tmp + self.HRE[x, :] = ( + (2 * config.sim_config.em_consts["e0"]) - self.G.dt * Halpha + ) / tmp self.HRF[x, :] = (2 * Hsigma * self.G.dt) / tmp def update_electric(self): @@ -348,7 +418,10 @@ class PML: """ pmlmodule = "gprMax.cython.pml_updates_electric_" + self.G.pmls["formulation"] - func = getattr(import_module(pmlmodule), "order" + str(len(self.CFS)) + "_" + self.direction) + func = getattr( + import_module(pmlmodule), + "order" + str(len(self.CFS)) + "_" + self.direction, + ) func( self.xs, self.xf, @@ -380,7 +453,10 @@ class PML: """ pmlmodule = "gprMax.cython.pml_updates_magnetic_" + self.G.pmls["formulation"] - func = getattr(import_module(pmlmodule), "order" + str(len(self.CFS)) + "_" + self.direction) + func = getattr( + import_module(pmlmodule), + "order" + str(len(self.CFS)) + "_" + self.direction, + ) func( self.xs, self.xf, @@ -439,7 +515,11 @@ class CUDAPML(PML): self.bpg = ( int( np.ceil( - ((self.EPhi1_dev.shape[1] + 1) * (self.EPhi1_dev.shape[2] + 1) * (self.EPhi1_dev.shape[3] + 1)) + ( + (self.EPhi1_dev.shape[1] + 1) + * (self.EPhi1_dev.shape[2] + 1) + * (self.EPhi1_dev.shape[3] + 1) + ) / self.G.tpb[0] ) ), @@ -631,7 +711,9 @@ def print_pml_info(G): if all(value == 0 for value in G.pmls["thickness"].values()): return f"\nPML boundaries [{G.name}]: switched off" - if all(value == G.pmls["thickness"]["x0"] for value in G.pmls["thickness"].values()): + if all( + value == G.pmls["thickness"]["x0"] for value in G.pmls["thickness"].values() + ): pmlinfo = str(G.pmls["thickness"]["x0"]) else: pmlinfo = "" @@ -673,29 +755,104 @@ def build_pml(G, pml_ID, thickness): pml_type = OpenCLPML if pml_ID == "x0": - pml = pml_type(G, ID=pml_ID, direction="xminus", xs=0, xf=thickness, ys=0, yf=G.ny, zs=0, zf=G.nz) + pml = pml_type( + G, + ID=pml_ID, + direction="xminus", + xs=0, + xf=thickness, + ys=0, + yf=G.ny, + zs=0, + zf=G.nz, + ) elif pml_ID == "xmax": - pml = pml_type(G, ID=pml_ID, direction="xplus", xs=G.nx - thickness, xf=G.nx, ys=0, yf=G.ny, zs=0, zf=G.nz) + pml = pml_type( + G, + ID=pml_ID, + direction="xplus", + xs=G.nx - thickness, + xf=G.nx, + ys=0, + yf=G.ny, + zs=0, + zf=G.nz, + ) elif pml_ID == "y0": - pml = pml_type(G, ID=pml_ID, direction="yminus", xs=0, xf=G.nx, ys=0, yf=thickness, zs=0, zf=G.nz) + pml = pml_type( + G, + ID=pml_ID, + direction="yminus", + xs=0, + xf=G.nx, + ys=0, + yf=thickness, + zs=0, + zf=G.nz, + ) elif pml_ID == "ymax": - pml = pml_type(G, ID=pml_ID, direction="yplus", xs=0, xf=G.nx, ys=G.ny - thickness, yf=G.ny, zs=0, zf=G.nz) + pml = pml_type( + G, + ID=pml_ID, + direction="yplus", + xs=0, + xf=G.nx, + ys=G.ny - thickness, + yf=G.ny, + zs=0, + zf=G.nz, + ) elif pml_ID == "z0": - pml = pml_type(G, ID=pml_ID, direction="zminus", xs=0, xf=G.nx, ys=0, yf=G.ny, zs=0, zf=thickness) + pml = pml_type( + G, + ID=pml_ID, + direction="zminus", + xs=0, + xf=G.nx, + ys=0, + yf=G.ny, + zs=0, + zf=thickness, + ) elif pml_ID == "zmax": - pml = pml_type(G, ID=pml_ID, direction="zplus", xs=0, xf=G.nx, ys=0, yf=G.ny, zs=G.nz - thickness, zf=G.nz) + pml = pml_type( + G, + ID=pml_ID, + direction="zplus", + xs=0, + xf=G.nx, + ys=0, + yf=G.ny, + zs=G.nz - thickness, + zf=G.nz, + ) if pml_ID[0] == "x": averageer, averagemr = pml_average_er_mr( - G.ny, G.nz, config.get_model_config().ompthreads, G.solid[pml.xs, :, :], ers, mrs + G.ny, + G.nz, + config.get_model_config().ompthreads, + G.solid[pml.xs, :, :], + ers, + mrs, ) elif pml_ID[0] == "y": averageer, averagemr = pml_average_er_mr( - G.nx, G.nz, config.get_model_config().ompthreads, G.solid[:, pml.ys, :], ers, mrs + G.nx, + G.nz, + config.get_model_config().ompthreads, + G.solid[:, pml.ys, :], + ers, + mrs, ) elif pml_ID[0] == "z": averageer, averagemr = pml_average_er_mr( - G.nx, G.ny, config.get_model_config().ompthreads, G.solid[:, :, pml.zs], ers, mrs + G.nx, + G.ny, + config.get_model_config().ompthreads, + G.solid[:, :, pml.zs], + ers, + mrs, ) pml.calculate_update_coeffs(averageer, averagemr) diff --git a/gprMax/receivers.py b/gprMax/receivers.py index e010a285..f170e48d 100644 --- a/gprMax/receivers.py +++ b/gprMax/receivers.py @@ -63,7 +63,8 @@ def htod_rx_arrays(G, queue=None): # Array to store field components for receivers on compute device - # rows are field components; columns are iterations; pages are receivers rxs = np.zeros( - (len(Rx.allowableoutputs_dev), G.iterations, len(G.rxs)), dtype=config.sim_config.dtypes["float_or_double"] + (len(Rx.allowableoutputs_dev), G.iterations, len(G.rxs)), + dtype=config.sim_config.dtypes["float_or_double"], ) # Copy arrays to compute device @@ -102,4 +103,6 @@ def dtoh_rx_array(rxs_dev, rxcoords_dev, G): and rx.zcoord == rxcoords_dev[rxd, 2] ): for output in rx.outputs.keys(): - rx.outputs[output] = rxs_dev[Rx.allowableoutputs_dev.index(output), :, rxd] + rx.outputs[output] = rxs_dev[ + Rx.allowableoutputs_dev.index(output), :, rxd + ] diff --git a/gprMax/scene.py b/gprMax/scene.py index 72adf3cb..cd700efe 100644 --- a/gprMax/scene.py +++ b/gprMax/scene.py @@ -24,8 +24,7 @@ from gprMax.cmds_geometry.add_surface_water import AddSurfaceWater from gprMax.cmds_geometry.cmds_geometry import UserObjectGeometry from gprMax.cmds_geometry.fractal_box import FractalBox from gprMax.cmds_multiuse import UserObjectMulti -from gprMax.cmds_singleuse import (Discretisation, Domain, TimeWindow, - UserObjectSingle) +from gprMax.cmds_singleuse import Discretisation, Domain, TimeWindow, UserObjectSingle from gprMax.materials import create_built_in_materials from gprMax.subgrids.user_objects import SubGridBase as SubGridUserBase from gprMax.user_inputs import create_user_input_points @@ -106,7 +105,9 @@ class Scene: # Check for fractal boxes and modifications and pre-process them first proc_cmds = [] for obj in commands: - if isinstance(obj, (FractalBox, AddGrass, AddSurfaceRoughness, AddSurfaceWater)): + if isinstance( + obj, (FractalBox, AddGrass, AddSurfaceRoughness, AddSurfaceWater) + ): self.build_obj(obj, grid) if isinstance(obj, (FractalBox)): proc_cmds.append(obj) diff --git a/gprMax/snapshots.py b/gprMax/snapshots.py index 94cca7f2..1ac11d0d 100644 --- a/gprMax/snapshots.py +++ b/gprMax/snapshots.py @@ -55,7 +55,9 @@ def save_snapshots(grid): leave=True, unit="byte", unit_scale=True, - desc=f"Writing snapshot file {i + 1} " f"of {len(grid.snapshots)}, " f"{snap.filename.name}", + desc=f"Writing snapshot file {i + 1} " + f"of {len(grid.snapshots)}, " + f"{snap.filename.name}", ncols=get_terminal_width() - 1, file=sys.stdout, disable=not config.sim_config.general["progressbars"], @@ -68,7 +70,14 @@ def save_snapshots(grid): class Snapshot: """Snapshots of the electric and magnetic field values.""" - allowableoutputs = {"Ex": None, "Ey": None, "Ez": None, "Hx": None, "Hy": None, "Hz": None} + allowableoutputs = { + "Ex": None, + "Ey": None, + "Ez": None, + "Hx": None, + "Hy": None, + "Hz": None, + } # Snapshots can be output as VTK ImageData (.vti) format or # HDF5 format (.h5) files @@ -136,13 +145,16 @@ class Snapshot: for k, v in self.outputs.items(): if v: self.snapfields[k] = np.zeros( - (self.nx, self.ny, self.nz), dtype=config.sim_config.dtypes["float_or_double"] + (self.nx, self.ny, self.nz), + dtype=config.sim_config.dtypes["float_or_double"], ) self.nbytes += self.snapfields[k].nbytes else: # If output is not required for snapshot just use a mimimal # size of array - still required to pass to Cython function - self.snapfields[k] = np.zeros((1, 1, 1), dtype=config.sim_config.dtypes["float_or_double"]) + self.snapfields[k] = np.zeros( + (1, 1, 1), dtype=config.sim_config.dtypes["float_or_double"] + ) def store(self, G): """Store (in memory) electric and magnetic field values for snapshot. @@ -208,11 +220,19 @@ class Snapshot: G: FDTDGrid class describing a grid in a model. """ - celldata = {k: self.snapfields[k] for k in ["Ex", "Ey", "Ez", "Hx", "Hy", "Hz"] if self.outputs.get(k)} + celldata = { + k: self.snapfields[k] + for k in ["Ex", "Ey", "Ez", "Hx", "Hy", "Hz"] + if self.outputs.get(k) + } imageToVTK( str(self.filename.with_suffix("")), - origin=((self.xs * self.dx * G.dx), (self.ys * self.dy * G.dy), (self.zs * self.dz * G.dz)), + origin=( + (self.xs * self.dx * G.dx), + (self.ys * self.dy * G.dy), + (self.zs * self.dz * G.dz), + ), spacing=((self.dx * G.dx), (self.dy * G.dy), (self.dz * G.dz)), cellData=celldata, ) @@ -271,35 +291,52 @@ def htod_snapshot_array(G, queue=None): if config.sim_config.general["solver"] == "cuda": # Blocks per grid - according to largest requested snapshot Snapshot.bpg = ( - int(np.ceil(((Snapshot.nx_max) * (Snapshot.ny_max) * (Snapshot.nz_max)) / Snapshot.tpb[0])), + int( + np.ceil( + ((Snapshot.nx_max) * (Snapshot.ny_max) * (Snapshot.nz_max)) + / Snapshot.tpb[0] + ) + ), 1, 1, ) elif config.sim_config.general["solver"] == "opencl": # Workgroup size - according to largest requested snapshot - Snapshot.wgs = (int(np.ceil(((Snapshot.nx_max) * (Snapshot.ny_max) * (Snapshot.nz_max)))), 1, 1) + Snapshot.wgs = ( + int(np.ceil(((Snapshot.nx_max) * (Snapshot.ny_max) * (Snapshot.nz_max)))), + 1, + 1, + ) # 4D arrays to store snapshots on GPU, e.g. snapEx(time, x, y, z); # if snapshots are not being stored on the GPU during the simulation then # they are copied back to the host after each iteration, hence numsnaps = 1 - numsnaps = 1 if config.get_model_config().device["snapsgpu2cpu"] else len(G.snapshots) + numsnaps = ( + 1 if config.get_model_config().device["snapsgpu2cpu"] else len(G.snapshots) + ) snapEx = np.zeros( - (numsnaps, Snapshot.nx_max, Snapshot.ny_max, Snapshot.nz_max), dtype=config.sim_config.dtypes["float_or_double"] + (numsnaps, Snapshot.nx_max, Snapshot.ny_max, Snapshot.nz_max), + dtype=config.sim_config.dtypes["float_or_double"], ) snapEy = np.zeros( - (numsnaps, Snapshot.nx_max, Snapshot.ny_max, Snapshot.nz_max), dtype=config.sim_config.dtypes["float_or_double"] + (numsnaps, Snapshot.nx_max, Snapshot.ny_max, Snapshot.nz_max), + dtype=config.sim_config.dtypes["float_or_double"], ) snapEz = np.zeros( - (numsnaps, Snapshot.nx_max, Snapshot.ny_max, Snapshot.nz_max), dtype=config.sim_config.dtypes["float_or_double"] + (numsnaps, Snapshot.nx_max, Snapshot.ny_max, Snapshot.nz_max), + dtype=config.sim_config.dtypes["float_or_double"], ) snapHx = np.zeros( - (numsnaps, Snapshot.nx_max, Snapshot.ny_max, Snapshot.nz_max), dtype=config.sim_config.dtypes["float_or_double"] + (numsnaps, Snapshot.nx_max, Snapshot.ny_max, Snapshot.nz_max), + dtype=config.sim_config.dtypes["float_or_double"], ) snapHy = np.zeros( - (numsnaps, Snapshot.nx_max, Snapshot.ny_max, Snapshot.nz_max), dtype=config.sim_config.dtypes["float_or_double"] + (numsnaps, Snapshot.nx_max, Snapshot.ny_max, Snapshot.nz_max), + dtype=config.sim_config.dtypes["float_or_double"], ) snapHz = np.zeros( - (numsnaps, Snapshot.nx_max, Snapshot.ny_max, Snapshot.nz_max), dtype=config.sim_config.dtypes["float_or_double"] + (numsnaps, Snapshot.nx_max, Snapshot.ny_max, Snapshot.nz_max), + dtype=config.sim_config.dtypes["float_or_double"], ) # Copy arrays to compute device @@ -326,7 +363,9 @@ def htod_snapshot_array(G, queue=None): return snapEx_dev, snapEy_dev, snapEz_dev, snapHx_dev, snapHy_dev, snapHz_dev -def dtoh_snapshot_array(snapEx_dev, snapEy_dev, snapEz_dev, snapHx_dev, snapHy_dev, snapHz_dev, i, snap): +def dtoh_snapshot_array( + snapEx_dev, snapEy_dev, snapEz_dev, snapHx_dev, snapHy_dev, snapHz_dev, i, snap +): """Copies snapshot array used on compute device back to snapshot objects and store in format for Paraview. @@ -336,9 +375,21 @@ def dtoh_snapshot_array(snapEx_dev, snapEy_dev, snapEz_dev, snapHx_dev, snapHy_d snap: Snapshot class instance """ - snap.snapfields["Ex"] = snapEx_dev[i, snap.xs : snap.xf, snap.ys : snap.yf, snap.zs : snap.zf] - snap.snapfields["Ey"] = snapEy_dev[i, snap.xs : snap.xf, snap.ys : snap.yf, snap.zs : snap.zf] - snap.snapfields["Ez"] = snapEz_dev[i, snap.xs : snap.xf, snap.ys : snap.yf, snap.zs : snap.zf] - snap.snapfields["Hx"] = snapHx_dev[i, snap.xs : snap.xf, snap.ys : snap.yf, snap.zs : snap.zf] - snap.snapfields["Hy"] = snapHy_dev[i, snap.xs : snap.xf, snap.ys : snap.yf, snap.zs : snap.zf] - snap.snapfields["Hz"] = snapHz_dev[i, snap.xs : snap.xf, snap.ys : snap.yf, snap.zs : snap.zf] + snap.snapfields["Ex"] = snapEx_dev[ + i, snap.xs : snap.xf, snap.ys : snap.yf, snap.zs : snap.zf + ] + snap.snapfields["Ey"] = snapEy_dev[ + i, snap.xs : snap.xf, snap.ys : snap.yf, snap.zs : snap.zf + ] + snap.snapfields["Ez"] = snapEz_dev[ + i, snap.xs : snap.xf, snap.ys : snap.yf, snap.zs : snap.zf + ] + snap.snapfields["Hx"] = snapHx_dev[ + i, snap.xs : snap.xf, snap.ys : snap.yf, snap.zs : snap.zf + ] + snap.snapfields["Hy"] = snapHy_dev[ + i, snap.xs : snap.xf, snap.ys : snap.yf, snap.zs : snap.zf + ] + snap.snapfields["Hz"] = snapHz_dev[ + i, snap.xs : snap.xf, snap.ys : snap.yf, snap.zs : snap.zf + ] diff --git a/gprMax/sources.py b/gprMax/sources.py index cd1e5194..efa38d61 100755 --- a/gprMax/sources.py +++ b/gprMax/sources.py @@ -42,7 +42,7 @@ class Source: self.stop = None self.waveformID = None # Waveform values for sources that need to be calculated on whole timesteps - self.waveformvalues_wholedt = None + self.waveformvalues_wholedt = None # Waveform values for sources that need to be calculated on half timesteps self.waveformvalues_halfdt = None @@ -66,7 +66,7 @@ class VoltageSource(Source): """ # Check if a source matches existing source in terms of waveform and - # does not have a customised start/stop time. If so, use its + # does not have a customised start/stop time. If so, use its # pre-calculated waveform values, otherwise calculate them. src_match = False @@ -77,8 +77,10 @@ class VoltageSource(Source): self.waveformvalues_wholedt = src.waveformvalues_wholedt if not src_match: - waveform = next(x for x in G.waveforms if x.ID == self.waveformID) - self.waveformvalues_wholedt = np.zeros((G.iterations), dtype=config.sim_config.dtypes["float_or_double"]) + waveform = next(x for x in G.waveforms if x.ID == self.waveformID) + self.waveformvalues_wholedt = np.zeros( + (G.iterations), dtype=config.sim_config.dtypes["float_or_double"] + ) for iteration in range(G.iterations): time = G.dt * iteration @@ -86,7 +88,9 @@ class VoltageSource(Source): # Set the time of the waveform evaluation to account for any # delay in the start time -= self.start - self.waveformvalues_wholedt[iteration] = waveform.calculate_value(time, G.dt) + self.waveformvalues_wholedt[iteration] = waveform.calculate_value( + time, G.dt + ) def update_electric(self, iteration, updatecoeffsE, ID, Ex, Ey, Ez, G): """Updates electric field values for a voltage source. @@ -158,7 +162,9 @@ class VoltageSource(Source): newmaterial.ID = f"{material.ID}+{self.ID}" newmaterial.numID = len(G.materials) newmaterial.averagable = False - newmaterial.type += ",\nvoltage-source" if newmaterial.type else "voltage-source" + newmaterial.type += ( + ",\nvoltage-source" if newmaterial.type else "voltage-source" + ) # Add conductivity of voltage source to underlying conductivity if self.polarisation == "x": @@ -187,7 +193,7 @@ class HertzianDipole(Source): """ # Check if a source matches existing source in terms of waveform and - # does not have a customised start/stop time. If so, use its + # does not have a customised start/stop time. If so, use its # pre-calculated waveform values, otherwise calculate them. src_match = False @@ -198,8 +204,10 @@ class HertzianDipole(Source): self.waveformvalues_halfdt = src.waveformvalues_halfdt if not src_match: - waveform = next(x for x in G.waveforms if x.ID == self.waveformID) - self.waveformvalues_halfdt = np.zeros((G.iterations), dtype=config.sim_config.dtypes["float_or_double"]) + waveform = next(x for x in G.waveforms if x.ID == self.waveformID) + self.waveformvalues_halfdt = np.zeros( + (G.iterations), dtype=config.sim_config.dtypes["float_or_double"] + ) for iteration in range(G.iterations): time = G.dt * iteration @@ -207,7 +215,9 @@ class HertzianDipole(Source): # Set the time of the waveform evaluation to account for any # delay in the start time -= self.start - self.waveformvalues_halfdt[iteration] = waveform.calculate_value(time + 0.5 * G.dt, G.dt) + self.waveformvalues_halfdt[iteration] = waveform.calculate_value( + time + 0.5 * G.dt, G.dt + ) def update_electric(self, iteration, updatecoeffsE, ID, Ex, Ey, Ez, G): """Updates electric field values for a Hertzian dipole. @@ -263,7 +273,7 @@ class MagneticDipole(Source): """ # Check if a source matches existing source in terms of waveform and - # does not have a customised start/stop time. If so, use its + # does not have a customised start/stop time. If so, use its # pre-calculated waveform values, otherwise calculate them. src_match = False @@ -274,8 +284,10 @@ class MagneticDipole(Source): self.waveformvalues_wholedt = src.waveformvalues_wholedt if not src_match: - waveform = next(x for x in G.waveforms if x.ID == self.waveformID) - self.waveformvalues_wholedt = np.zeros((G.iterations), dtype=config.sim_config.dtypes["float_or_double"]) + waveform = next(x for x in G.waveforms if x.ID == self.waveformID) + self.waveformvalues_wholedt = np.zeros( + (G.iterations), dtype=config.sim_config.dtypes["float_or_double"] + ) for iteration in range(G.iterations): time = G.dt * iteration @@ -283,7 +295,9 @@ class MagneticDipole(Source): # Set the time of the waveform evaluation to account for any # delay in the start time -= self.start - self.waveformvalues_wholedt[iteration] = waveform.calculate_value(time, G.dt) + self.waveformvalues_wholedt[iteration] = waveform.calculate_value( + time, G.dt + ) def update_magnetic(self, iteration, updatecoeffsH, ID, Hx, Hy, Hz, G): """Updates magnetic field values for a magnetic dipole. @@ -344,8 +358,12 @@ def htod_src_arrays(sources, G, queue=None): """ srcinfo1 = np.zeros((len(sources), 4), dtype=np.int32) - srcinfo2 = np.zeros((len(sources)), dtype=config.sim_config.dtypes["float_or_double"]) - srcwaves = np.zeros((len(sources), G.iterations), dtype=config.sim_config.dtypes["float_or_double"]) + srcinfo2 = np.zeros( + (len(sources)), dtype=config.sim_config.dtypes["float_or_double"] + ) + srcwaves = np.zeros( + (len(sources), G.iterations), dtype=config.sim_config.dtypes["float_or_double"] + ) for i, src in enumerate(sources): srcinfo1[i, 0] = src.xcoord srcinfo1[i, 1] = src.ycoord @@ -422,12 +440,24 @@ class TransmissionLine(Source): # Cell position of where line connects to antenna/main grid self.antpos = 10 - self.voltage = np.zeros(self.nl, dtype=config.sim_config.dtypes["float_or_double"]) - self.current = np.zeros(self.nl, dtype=config.sim_config.dtypes["float_or_double"]) - self.Vinc = np.zeros(G.iterations, dtype=config.sim_config.dtypes["float_or_double"]) - self.Iinc = np.zeros(G.iterations, dtype=config.sim_config.dtypes["float_or_double"]) - self.Vtotal = np.zeros(G.iterations, dtype=config.sim_config.dtypes["float_or_double"]) - self.Itotal = np.zeros(G.iterations, dtype=config.sim_config.dtypes["float_or_double"]) + self.voltage = np.zeros( + self.nl, dtype=config.sim_config.dtypes["float_or_double"] + ) + self.current = np.zeros( + self.nl, dtype=config.sim_config.dtypes["float_or_double"] + ) + self.Vinc = np.zeros( + G.iterations, dtype=config.sim_config.dtypes["float_or_double"] + ) + self.Iinc = np.zeros( + G.iterations, dtype=config.sim_config.dtypes["float_or_double"] + ) + self.Vtotal = np.zeros( + G.iterations, dtype=config.sim_config.dtypes["float_or_double"] + ) + self.Itotal = np.zeros( + G.iterations, dtype=config.sim_config.dtypes["float_or_double"] + ) def calculate_waveform_values(self, G): """Calculates all waveform values for source for duration of simulation. @@ -437,7 +467,7 @@ class TransmissionLine(Source): """ # Check if a source matches existing source in terms of waveform and - # does not have a customised start/stop time. If so, use its + # does not have a customised start/stop time. If so, use its # pre-calculated waveform values, otherwise calculate them. src_match = False @@ -449,9 +479,13 @@ class TransmissionLine(Source): self.waveformvalues_halfdt = src.waveformvalues_halfdt if not src_match: - waveform = next(x for x in G.waveforms if x.ID == self.waveformID) - self.waveformvalues_wholedt = np.zeros((G.iterations), dtype=config.sim_config.dtypes["float_or_double"]) - self.waveformvalues_halfdt = np.zeros((G.iterations), dtype=config.sim_config.dtypes["float_or_double"]) + waveform = next(x for x in G.waveforms if x.ID == self.waveformID) + self.waveformvalues_wholedt = np.zeros( + (G.iterations), dtype=config.sim_config.dtypes["float_or_double"] + ) + self.waveformvalues_halfdt = np.zeros( + (G.iterations), dtype=config.sim_config.dtypes["float_or_double"] + ) for iteration in range(G.iterations): time = G.dt * iteration @@ -459,8 +493,12 @@ class TransmissionLine(Source): # Set the time of the waveform evaluation to account for any # delay in the start time -= self.start - self.waveformvalues_wholedt[iteration] = waveform.calculate_value(time, G.dt) - self.waveformvalues_halfdt[iteration] = waveform.calculate_value(time + 0.5 * G.dt, G.dt) + self.waveformvalues_wholedt[iteration] = waveform.calculate_value( + time, G.dt + ) + self.waveformvalues_halfdt[iteration] = waveform.calculate_value( + time + 0.5 * G.dt, G.dt + ) def calculate_incident_V_I(self, G): """Calculates the incident voltage and current with a long length @@ -503,11 +541,15 @@ class TransmissionLine(Source): # Update all the voltage values along the line self.voltage[1 : self.nl] -= ( - self.resistance * (config.c * G.dt / self.dl) * (self.current[1 : self.nl] - self.current[0 : self.nl - 1]) + self.resistance + * (config.c * G.dt / self.dl) + * (self.current[1 : self.nl] - self.current[0 : self.nl - 1]) ) # Update the voltage at the position of the one-way injector excitation - self.voltage[self.srcpos] += (config.c * G.dt / self.dl) * self.waveformvalues_halfdt[iteration] + self.voltage[self.srcpos] += ( + config.c * G.dt / self.dl + ) * self.waveformvalues_halfdt[iteration] # Update ABC before updating current self.update_abc(G) @@ -529,7 +571,9 @@ class TransmissionLine(Source): # Update the current one cell before the position of the one-way injector excitation self.current[self.srcpos - 1] += ( - (1 / self.resistance) * (config.c * G.dt / self.dl) * self.waveformvalues_wholedt[iteration] + (1 / self.resistance) + * (config.c * G.dt / self.dl) + * self.waveformvalues_wholedt[iteration] ) def update_electric(self, iteration, updatecoeffsE, ID, Ex, Ey, Ez, G): diff --git a/gprMax/subgrids/precursor_nodes.py b/gprMax/subgrids/precursor_nodes.py index 151489dc..b162c457 100644 --- a/gprMax/subgrids/precursor_nodes.py +++ b/gprMax/subgrids/precursor_nodes.py @@ -233,7 +233,9 @@ class PrecursorNodesBase: def interpolate_to_sub_grid(self, field, coords): x, z, x_sg, z_sg = coords - interp_f = interpolate.RectBivariateSpline(x, z, field, kx=self.interpolation, ky=self.interpolation) + interp_f = interpolate.RectBivariateSpline( + x, z, field, kx=self.interpolation, ky=self.interpolation + ) f_i = interp_f(x_sg, z_sg) return f_i @@ -417,18 +419,78 @@ class PrecursorNodes(PrecursorNodesBase): # Spatially interpolate nodes slices = [ - ["ex_front_1", True, (slice(i0, i1, 1), self.j0, slice(k0, k1 + 1, 1)), self.Ex], - ["ex_back_1", True, (slice(i0, i1, 1), self.j1, slice(k0, k1 + 1, 1)), self.Ex], - ["ez_front_1", False, (slice(i0, i1 + 1, 1), self.j0, slice(k0, k1, 1)), self.Ez], - ["ez_back_1", False, (slice(i0, i1 + 1, 1), self.j1, slice(k0, k1, 1)), self.Ez], - ["ey_left_1", True, (self.i0, slice(j0, j1, 1), slice(k0, k1 + 1, 1)), self.Ey], - ["ey_right_1", True, (self.i1, slice(j0, j1, 1), slice(k0, k1 + 1, 1)), self.Ey], - ["ez_left_1", False, (self.i0, slice(j0, j1 + 1, 1), slice(k0, k1, 1)), self.Ez], - ["ez_right_1", False, (self.i1, slice(j0, j1 + 1, 1), slice(k0, k1, 1)), self.Ez], - ["ex_bottom_1", True, (slice(i0, i1, 1), slice(j0, j1 + 1, 1), self.k0), self.Ex], - ["ex_top_1", True, (slice(i0, i1, 1), slice(j0, j1 + 1, 1), self.k1), self.Ex], - ["ey_bottom_1", False, (slice(i0, i1 + 1, 1), slice(j0, j1, 1), self.k0), self.Ey], - ["ey_top_1", False, (slice(i0, i1 + 1, 1), slice(j0, j1, 1), self.k1), self.Ey], + [ + "ex_front_1", + True, + (slice(i0, i1, 1), self.j0, slice(k0, k1 + 1, 1)), + self.Ex, + ], + [ + "ex_back_1", + True, + (slice(i0, i1, 1), self.j1, slice(k0, k1 + 1, 1)), + self.Ex, + ], + [ + "ez_front_1", + False, + (slice(i0, i1 + 1, 1), self.j0, slice(k0, k1, 1)), + self.Ez, + ], + [ + "ez_back_1", + False, + (slice(i0, i1 + 1, 1), self.j1, slice(k0, k1, 1)), + self.Ez, + ], + [ + "ey_left_1", + True, + (self.i0, slice(j0, j1, 1), slice(k0, k1 + 1, 1)), + self.Ey, + ], + [ + "ey_right_1", + True, + (self.i1, slice(j0, j1, 1), slice(k0, k1 + 1, 1)), + self.Ey, + ], + [ + "ez_left_1", + False, + (self.i0, slice(j0, j1 + 1, 1), slice(k0, k1, 1)), + self.Ez, + ], + [ + "ez_right_1", + False, + (self.i1, slice(j0, j1 + 1, 1), slice(k0, k1, 1)), + self.Ez, + ], + [ + "ex_bottom_1", + True, + (slice(i0, i1, 1), slice(j0, j1 + 1, 1), self.k0), + self.Ex, + ], + [ + "ex_top_1", + True, + (slice(i0, i1, 1), slice(j0, j1 + 1, 1), self.k1), + self.Ex, + ], + [ + "ey_bottom_1", + False, + (slice(i0, i1 + 1, 1), slice(j0, j1, 1), self.k0), + self.Ey, + ], + [ + "ey_top_1", + False, + (slice(i0, i1 + 1, 1), slice(j0, j1, 1), self.k1), + self.Ey, + ], ] for obj in slices: diff --git a/gprMax/subgrids/subgrid_hsg.py b/gprMax/subgrids/subgrid_hsg.py index 1d224ed0..7ec3ba48 100644 --- a/gprMax/subgrids/subgrid_hsg.py +++ b/gprMax/subgrids/subgrid_hsg.py @@ -20,7 +20,11 @@ import logging import gprMax.config as config -from ..cython.fields_updates_hsg import update_electric_os, update_is, update_magnetic_os +from ..cython.fields_updates_hsg import ( + update_electric_os, + update_is, + update_magnetic_os, +) from .grid import SubGridBaseGrid logger = logging.getLogger(__name__) @@ -673,16 +677,27 @@ class SubGridHSG(SubGridBaseGrid): # Working region xs, ys, zs = self.round_to_grid( - (self.i0 * self.dx * self.ratio, self.j0 * self.dy * self.ratio, self.k0 * self.dz * self.ratio) + ( + self.i0 * self.dx * self.ratio, + self.j0 * self.dy * self.ratio, + self.k0 * self.dz * self.ratio, + ) ) xf, yf, zf = self.round_to_grid( - (self.i1 * self.dx * self.ratio, self.j1 * self.dy * self.ratio, self.k1 * self.dz * self.ratio) + ( + self.i1 * self.dx * self.ratio, + self.j1 * self.dy * self.ratio, + self.k1 * self.dz * self.ratio, + ) ) logger.info("") logger.debug(f"[{self.name}] Type: {self.__class__.__name__}") logger.info(f"[{self.name}] Ratio: 1:{self.ratio}") - logger.info(f"[{self.name}] Spatial discretisation: {self.dx:g} x " + f"{self.dy:g} x {self.dz:g}m") + logger.info( + f"[{self.name}] Spatial discretisation: {self.dx:g} x " + + f"{self.dy:g} x {self.dz:g}m" + ) logger.info( f"[{self.name}] Extent (working region): {xs}m, {ys}m, {zs}m to {xf}m, {yf}m, {zf}m " + f"(({self.nwx} x {self.nwy} x {self.nwz} = {self.nwx * self.nwy * self.nwz} cells)" diff --git a/gprMax/subgrids/user_objects.py b/gprMax/subgrids/user_objects.py index a4031c79..456f7a84 100644 --- a/gprMax/subgrids/user_objects.py +++ b/gprMax/subgrids/user_objects.py @@ -131,7 +131,9 @@ class SubGridBase(UserObjectMulti): # Don't mix and match different subgrid types for sg_made in grid.subgrids: if type(sg) != type(sg_made): - logger.exception(f"{self.__str__()} please only use one type of subgrid") + logger.exception( + f"{self.__str__()} please only use one type of subgrid" + ) raise ValueError # Reference the subgrid under the main grid to which it belongs diff --git a/gprMax/updates.py b/gprMax/updates.py index 4d5e511f..d77a4ee5 100644 --- a/gprMax/updates.py +++ b/gprMax/updates.py @@ -25,12 +25,14 @@ from jinja2 import Environment, PackageLoader import gprMax.config as config -from .cuda_opencl import (knl_fields_updates, knl_snapshots, - knl_source_updates, knl_store_outputs) -from .cython.fields_updates_normal import \ - update_electric as update_electric_cpu -from .cython.fields_updates_normal import \ - update_magnetic as update_magnetic_cpu +from .cuda_opencl import ( + knl_fields_updates, + knl_snapshots, + knl_source_updates, + knl_store_outputs, +) +from .cython.fields_updates_normal import update_electric as update_electric_cpu +from .cython.fields_updates_normal import update_magnetic as update_magnetic_cpu from .fields_outputs import store_outputs as store_outputs_cpu from .receivers import dtoh_rx_array, htod_rx_arrays from .snapshots import Snapshot, dtoh_snapshot_array, htod_snapshot_array @@ -151,7 +153,11 @@ class CPUUpdates: """Updates electric field components from sources - update any Hertzian dipole sources last. """ - for source in self.grid.voltagesources + self.grid.transmissionlines + self.grid.hertziandipoles: + for source in ( + self.grid.voltagesources + + self.grid.transmissionlines + + self.grid.hertziandipoles + ): source.update_electric( self.grid.iteration, self.grid.updatecoeffsE, @@ -191,10 +197,13 @@ class CPUUpdates: """Sets dispersive update functions.""" poles = "multi" if config.get_model_config().materials["maxpoles"] > 1 else "1" - precision = "float" if config.sim_config.general["precision"] == "single" else "double" + precision = ( + "float" if config.sim_config.general["precision"] == "single" else "double" + ) dispersion = ( "complex" - if config.get_model_config().materials["dispersivedtype"] == config.sim_config.dtypes["complex"] + if config.get_model_config().materials["dispersivedtype"] + == config.sim_config.dtypes["complex"] else "real" ) @@ -202,8 +211,12 @@ class CPUUpdates: disp_a = update_f.format(poles, "A", precision, dispersion) disp_b = update_f.format(poles, "B", precision, dispersion) - disp_a_f = getattr(import_module("gprMax.cython.fields_updates_dispersive"), disp_a) - disp_b_f = getattr(import_module("gprMax.cython.fields_updates_dispersive"), disp_b) + disp_a_f = getattr( + import_module("gprMax.cython.fields_updates_dispersive"), disp_a + ) + disp_b_f = getattr( + import_module("gprMax.cython.fields_updates_dispersive"), disp_b + ) self.dispersive_update_a = disp_a_f self.dispersive_update_b = disp_b_f @@ -271,7 +284,11 @@ class CUDAUpdates: self._set_pml_knls() if self.grid.rxs: self._set_rx_knl() - if self.grid.voltagesources + self.grid.hertziandipoles + self.grid.magneticdipoles: + if ( + self.grid.voltagesources + + self.grid.hertziandipoles + + self.grid.magneticdipoles + ): self._set_src_knls() if self.grid.snapshots: self._set_snapshot_knl() @@ -344,11 +361,15 @@ class CUDAUpdates: gets kernel functions. """ - bld = self._build_knl(knl_fields_updates.update_electric, self.subs_name_args, self.subs_func) + bld = self._build_knl( + knl_fields_updates.update_electric, self.subs_name_args, self.subs_func + ) knlE = self.source_module(bld, options=config.sim_config.devices["nvcc_opts"]) self.update_electric_dev = knlE.get_function("update_electric") - bld = self._build_knl(knl_fields_updates.update_magnetic, self.subs_name_args, self.subs_func) + bld = self._build_knl( + knl_fields_updates.update_magnetic, self.subs_name_args, self.subs_func + ) knlH = self.source_module(bld, options=config.sim_config.devices["nvcc_opts"]) self.update_magnetic_dev = knlH.get_function("update_magnetic") @@ -367,13 +388,25 @@ class CUDAUpdates: } ) - bld = self._build_knl(knl_fields_updates.update_electric_dispersive_A, self.subs_name_args, self.subs_func) - knl = self.source_module(bld, options=config.sim_config.devices["nvcc_opts"]) + bld = self._build_knl( + knl_fields_updates.update_electric_dispersive_A, + self.subs_name_args, + self.subs_func, + ) + knl = self.source_module( + bld, options=config.sim_config.devices["nvcc_opts"] + ) self.dispersive_update_a = knl.get_function("update_electric_dispersive_A") self._copy_mat_coeffs(knl, knl) - bld = self._build_knl(knl_fields_updates.update_electric_dispersive_B, self.subs_name_args, self.subs_func) - knl = self.source_module(bld, options=config.sim_config.devices["nvcc_opts"]) + bld = self._build_knl( + knl_fields_updates.update_electric_dispersive_B, + self.subs_name_args, + self.subs_func, + ) + knl = self.source_module( + bld, options=config.sim_config.devices["nvcc_opts"] + ) self.dispersive_update_b = knl.get_function("update_electric_dispersive_B") self._copy_mat_coeffs(knl, knl) @@ -387,10 +420,12 @@ class CUDAUpdates: def _set_pml_knls(self): """PMLS - prepares kernels and gets kernel functions.""" knl_pml_updates_electric = import_module( - "gprMax.cuda_opencl.knl_pml_updates_electric_" + self.grid.pmls["formulation"] + "gprMax.cuda_opencl.knl_pml_updates_electric_" + + self.grid.pmls["formulation"] ) knl_pml_updates_magnetic = import_module( - "gprMax.cuda_opencl.knl_pml_updates_magnetic_" + self.grid.pmls["formulation"] + "gprMax.cuda_opencl.knl_pml_updates_magnetic_" + + self.grid.pmls["formulation"] ) # Initialise arrays on GPU, set block per grid, and get kernel functions @@ -402,12 +437,16 @@ class CUDAUpdates: knl_electric = getattr(knl_pml_updates_electric, knl_name) bld = self._build_knl(knl_electric, self.subs_name_args, self.subs_func) - knlE = self.source_module(bld, options=config.sim_config.devices["nvcc_opts"]) + knlE = self.source_module( + bld, options=config.sim_config.devices["nvcc_opts"] + ) pml.update_electric_dev = knlE.get_function(knl_name) knl_magnetic = getattr(knl_pml_updates_magnetic, knl_name) bld = self._build_knl(knl_magnetic, self.subs_name_args, self.subs_func) - knlH = self.source_module(bld, options=config.sim_config.devices["nvcc_opts"]) + knlH = self.source_module( + bld, options=config.sim_config.devices["nvcc_opts"] + ) pml.update_magnetic_dev = knlH.get_function(knl_name) # Copy material coefficient arrays to constant memory of GPU - must @@ -430,7 +469,9 @@ class CUDAUpdates: } ) - bld = self._build_knl(knl_store_outputs.store_outputs, self.subs_name_args, self.subs_func) + bld = self._build_knl( + knl_store_outputs.store_outputs, self.subs_name_args, self.subs_func + ) knl = self.source_module(bld, options=config.sim_config.devices["nvcc_opts"]) self.store_outputs_dev = knl.get_function("store_outputs") @@ -441,25 +482,49 @@ class CUDAUpdates: self.subs_func.update({"NY_SRCINFO": 4, "NY_SRCWAVES": self.grid.iteration}) if self.grid.hertziandipoles: - self.srcinfo1_hertzian_dev, self.srcinfo2_hertzian_dev, self.srcwaves_hertzian_dev = htod_src_arrays( - self.grid.hertziandipoles, self.grid + ( + self.srcinfo1_hertzian_dev, + self.srcinfo2_hertzian_dev, + self.srcwaves_hertzian_dev, + ) = htod_src_arrays(self.grid.hertziandipoles, self.grid) + bld = self._build_knl( + knl_source_updates.update_hertzian_dipole, + self.subs_name_args, + self.subs_func, + ) + knl = self.source_module( + bld, options=config.sim_config.devices["nvcc_opts"] ) - bld = self._build_knl(knl_source_updates.update_hertzian_dipole, self.subs_name_args, self.subs_func) - knl = self.source_module(bld, options=config.sim_config.devices["nvcc_opts"]) self.update_hertzian_dipole_dev = knl.get_function("update_hertzian_dipole") if self.grid.magneticdipoles: - self.srcinfo1_magnetic_dev, self.srcinfo2_magnetic_dev, self.srcwaves_magnetic_dev = htod_src_arrays( - self.grid.magneticdipoles, self.grid + ( + self.srcinfo1_magnetic_dev, + self.srcinfo2_magnetic_dev, + self.srcwaves_magnetic_dev, + ) = htod_src_arrays(self.grid.magneticdipoles, self.grid) + bld = self._build_knl( + knl_source_updates.update_magnetic_dipole, + self.subs_name_args, + self.subs_func, + ) + knl = self.source_module( + bld, options=config.sim_config.devices["nvcc_opts"] ) - bld = self._build_knl(knl_source_updates.update_magnetic_dipole, self.subs_name_args, self.subs_func) - knl = self.source_module(bld, options=config.sim_config.devices["nvcc_opts"]) self.update_magnetic_dipole_dev = knl.get_function("update_magnetic_dipole") if self.grid.voltagesources: - self.srcinfo1_voltage_dev, self.srcinfo2_voltage_dev, self.srcwaves_voltage_dev = htod_src_arrays( - self.grid.voltagesources, self.grid + ( + self.srcinfo1_voltage_dev, + self.srcinfo2_voltage_dev, + self.srcwaves_voltage_dev, + ) = htod_src_arrays(self.grid.voltagesources, self.grid) + bld = self._build_knl( + knl_source_updates.update_voltage_source, + self.subs_name_args, + self.subs_func, + ) + knl = self.source_module( + bld, options=config.sim_config.devices["nvcc_opts"] ) - bld = self._build_knl(knl_source_updates.update_voltage_source, self.subs_name_args, self.subs_func) - knl = self.source_module(bld, options=config.sim_config.devices["nvcc_opts"]) self.update_voltage_source_dev = knl.get_function("update_voltage_source") self._copy_mat_coeffs(knl, knl) @@ -486,7 +551,9 @@ class CUDAUpdates: } ) - bld = self._build_knl(knl_snapshots.store_snapshot, self.subs_name_args, self.subs_func) + bld = self._build_knl( + knl_snapshots.store_snapshot, self.subs_name_args, self.subs_func + ) knl = self.source_module(bld, options=config.sim_config.devices["nvcc_opts"]) self.store_snapshot_dev = knl.get_function("store_snapshot") @@ -805,12 +872,16 @@ class OpenCLUpdates: # Import pyopencl module self.cl = import_module("pyopencl") - self.elwiseknl = getattr(import_module("pyopencl.elementwise"), "ElementwiseKernel") + self.elwiseknl = getattr( + import_module("pyopencl.elementwise"), "ElementwiseKernel" + ) # Select device, create context and command queue self.dev = config.get_model_config().device["dev"] self.ctx = self.cl.Context(devices=[self.dev]) - self.queue = self.cl.CommandQueue(self.ctx, properties=self.cl.command_queue_properties.PROFILING_ENABLE) + self.queue = self.cl.CommandQueue( + self.ctx, properties=self.cl.command_queue_properties.PROFILING_ENABLE + ) # Enviroment for templating kernels self.env = Environment(loader=PackageLoader("gprMax", "cuda_opencl")) @@ -822,7 +893,11 @@ class OpenCLUpdates: self._set_pml_knls() if self.grid.rxs: self._set_rx_knl() - if self.grid.voltagesources + self.grid.hertziandipoles + self.grid.magneticdipoles: + if ( + self.grid.voltagesources + + self.grid.hertziandipoles + + self.grid.magneticdipoles + ): self._set_src_knls() if self.grid.snapshots: self._set_snapshot_knl() @@ -872,7 +947,7 @@ class OpenCLUpdates: def _set_field_knls(self): """Electric and magnetic field updates - prepares kernels, and - gets kernel functions. + gets kernel functions. """ subs = { @@ -884,7 +959,7 @@ class OpenCLUpdates: "NY_ID": self.grid.ID.shape[2], "NZ_ID": self.grid.ID.shape[3], } - + self.update_electric_dev = self.elwiseknl( self.ctx, knl_fields_updates.update_electric["args_opencl"].substitute( @@ -924,30 +999,42 @@ class OpenCLUpdates: "NY_T": self.grid.Tx.shape[2], "NZ_T": self.grid.Tx.shape[3], } - + self.dispersive_update_a = self.elwiseknl( self.ctx, - knl_fields_updates.update_electric_dispersive_A["args_opencl"].substitute( + knl_fields_updates.update_electric_dispersive_A[ + "args_opencl" + ].substitute( { "REAL": config.sim_config.dtypes["C_float_or_double"], - "COMPLEX": config.get_model_config().materials["dispersiveCdtype"], + "COMPLEX": config.get_model_config().materials[ + "dispersiveCdtype" + ], } ), - knl_fields_updates.update_electric_dispersive_A["func"].substitute(subs), + knl_fields_updates.update_electric_dispersive_A["func"].substitute( + subs + ), "update_electric_dispersive_A", preamble=self.knl_common, options=config.sim_config.devices["compiler_opts"], ) - + self.dispersive_update_b = self.elwiseknl( self.ctx, - knl_fields_updates.update_electric_dispersive_B["args_opencl"].substitute( + knl_fields_updates.update_electric_dispersive_B[ + "args_opencl" + ].substitute( { "REAL": config.sim_config.dtypes["C_float_or_double"], - "COMPLEX": config.get_model_config().materials["dispersiveCdtype"], + "COMPLEX": config.get_model_config().materials[ + "dispersiveCdtype" + ], } ), - knl_fields_updates.update_electric_dispersive_B["func"].substitute(subs), + knl_fields_updates.update_electric_dispersive_B["func"].substitute( + subs + ), "update_electric_dispersive_B", preamble=self.knl_common, options=config.sim_config.devices["compiler_opts"], @@ -962,10 +1049,12 @@ class OpenCLUpdates: def _set_pml_knls(self): """PMLS - prepares kernels and gets kernel functions.""" knl_pml_updates_electric = import_module( - "gprMax.cuda_opencl.knl_pml_updates_electric_" + self.grid.pmls["formulation"] + "gprMax.cuda_opencl.knl_pml_updates_electric_" + + self.grid.pmls["formulation"] ) knl_pml_updates_magnetic = import_module( - "gprMax.cuda_opencl.knl_pml_updates_magnetic_" + self.grid.pmls["formulation"] + "gprMax.cuda_opencl.knl_pml_updates_magnetic_" + + self.grid.pmls["formulation"] ) subs = { @@ -990,7 +1079,9 @@ class OpenCLUpdates: pml.update_electric_dev = self.elwiseknl( self.ctx, - knl_electric_name["args_opencl"].substitute({"REAL": config.sim_config.dtypes["C_float_or_double"]}), + knl_electric_name["args_opencl"].substitute( + {"REAL": config.sim_config.dtypes["C_float_or_double"]} + ), knl_electric_name["func"].substitute(subs), f"pml_updates_electric_{knl_name}", preamble=self.knl_common, @@ -999,7 +1090,9 @@ class OpenCLUpdates: pml.update_magnetic_dev = self.elwiseknl( self.ctx, - knl_magnetic_name["args_opencl"].substitute({"REAL": config.sim_config.dtypes["C_float_or_double"]}), + knl_magnetic_name["args_opencl"].substitute( + {"REAL": config.sim_config.dtypes["C_float_or_double"]} + ), knl_magnetic_name["func"].substitute(subs), f"pml_updates_magnetic_{knl_name}", preamble=self.knl_common, @@ -1027,48 +1120,63 @@ class OpenCLUpdates: gets kernel function. """ if self.grid.hertziandipoles: - self.srcinfo1_hertzian_dev, self.srcinfo2_hertzian_dev, self.srcwaves_hertzian_dev = htod_src_arrays( - self.grid.hertziandipoles, self.grid, self.queue - ) + ( + self.srcinfo1_hertzian_dev, + self.srcinfo2_hertzian_dev, + self.srcwaves_hertzian_dev, + ) = htod_src_arrays(self.grid.hertziandipoles, self.grid, self.queue) self.update_hertzian_dipole_dev = self.elwiseknl( self.ctx, knl_source_updates.update_hertzian_dipole["args_opencl"].substitute( {"REAL": config.sim_config.dtypes["C_float_or_double"]} ), knl_source_updates.update_hertzian_dipole["func"].substitute( - {"CUDA_IDX": "", "REAL": config.sim_config.dtypes["C_float_or_double"]} + { + "CUDA_IDX": "", + "REAL": config.sim_config.dtypes["C_float_or_double"], + } ), "update_hertzian_dipole", preamble=self.knl_common, options=config.sim_config.devices["compiler_opts"], ) if self.grid.magneticdipoles: - self.srcinfo1_magnetic_dev, self.srcinfo2_magnetic_dev, self.srcwaves_magnetic_dev = htod_src_arrays( - self.grid.magneticdipoles, self.grid, self.queue - ) + ( + self.srcinfo1_magnetic_dev, + self.srcinfo2_magnetic_dev, + self.srcwaves_magnetic_dev, + ) = htod_src_arrays(self.grid.magneticdipoles, self.grid, self.queue) self.update_magnetic_dipole_dev = self.elwiseknl( self.ctx, knl_source_updates.update_magnetic_dipole["args_opencl"].substitute( {"REAL": config.sim_config.dtypes["C_float_or_double"]} ), knl_source_updates.update_magnetic_dipole["func"].substitute( - {"CUDA_IDX": "", "REAL": config.sim_config.dtypes["C_float_or_double"]} + { + "CUDA_IDX": "", + "REAL": config.sim_config.dtypes["C_float_or_double"], + } ), "update_magnetic_dipole", preamble=self.knl_common, options=config.sim_config.devices["compiler_opts"], ) if self.grid.voltagesources: - self.srcinfo1_voltage_dev, self.srcinfo2_voltage_dev, self.srcwaves_voltage_dev = htod_src_arrays( - self.grid.voltagesources, self.grid, self.queue - ) + ( + self.srcinfo1_voltage_dev, + self.srcinfo2_voltage_dev, + self.srcwaves_voltage_dev, + ) = htod_src_arrays(self.grid.voltagesources, self.grid, self.queue) self.update_voltage_source_dev = self.elwiseknl( self.ctx, knl_source_updates.update_voltage_source["args_opencl"].substitute( {"REAL": config.sim_config.dtypes["C_float_or_double"]} ), knl_source_updates.update_voltage_source["func"].substitute( - {"CUDA_IDX": "", "REAL": config.sim_config.dtypes["C_float_or_double"]} + { + "CUDA_IDX": "", + "REAL": config.sim_config.dtypes["C_float_or_double"], + } ), "update_voltage_source", preamble=self.knl_common, @@ -1093,7 +1201,12 @@ class OpenCLUpdates: {"REAL": config.sim_config.dtypes["C_float_or_double"]} ), knl_snapshots.store_snapshot["func"].substitute( - {"CUDA_IDX": "", "NX_SNAPS": Snapshot.nx_max, "NY_SNAPS": Snapshot.ny_max, "NZ_SNAPS": Snapshot.nz_max} + { + "CUDA_IDX": "", + "NX_SNAPS": Snapshot.nx_max, + "NY_SNAPS": Snapshot.ny_max, + "NZ_SNAPS": Snapshot.nz_max, + } ), "store_snapshot", preamble=self.knl_common, @@ -1238,7 +1351,7 @@ class OpenCLUpdates: self.grid.Ty_dev, self.grid.Tz_dev, ) - + def update_electric_pml(self): """Updates electric field components with the PML correction.""" for pml in self.grid.pmls["slabs"]: @@ -1311,7 +1424,7 @@ class OpenCLUpdates: self.event_marker1.wait() def calculate_memory_used(self, iteration): - """Calculates memory used on last iteration. + """Calculates memory used on last iteration. Args: iteration: int for iteration number. diff --git a/gprMax/user_inputs.py b/gprMax/user_inputs.py index 87f7202c..8b8e5a2b 100644 --- a/gprMax/user_inputs.py +++ b/gprMax/user_inputs.py @@ -108,7 +108,9 @@ class MainGridUserInput(UserInput): p = self.check_point(p, cmd_str, name) if self.grid.within_pml(p): - logger.warning(f"'{cmd_str}' sources and receivers should not normally be positioned within the PML.") + logger.warning( + f"'{cmd_str}' sources and receivers should not normally be positioned within the PML." + ) return p @@ -117,7 +119,9 @@ class MainGridUserInput(UserInput): p2 = self.check_point(p2, cmd_str, name="upper") if np.greater(p1, p2).any(): - logger.exception(f"'{cmd_str}' the lower coordinates should be less than the upper coordinates.") + logger.exception( + f"'{cmd_str}' the lower coordinates should be less than the upper coordinates." + ) raise ValueError return p1, p2 @@ -152,7 +156,9 @@ class SubgridUserInput(MainGridUserInput): super().__init__(grid) # Defines the region exposed to the user - self.inner_bound = np.array([grid.n_boundary_cells_x, grid.n_boundary_cells_y, grid.n_boundary_cells_z]) + self.inner_bound = np.array( + [grid.n_boundary_cells_x, grid.n_boundary_cells_y, grid.n_boundary_cells_z] + ) self.outer_bound = np.subtract([grid.nx, grid.ny, grid.nz], self.inner_bound) @@ -185,8 +191,13 @@ class SubgridUserInput(MainGridUserInput): # Provide user within a warning if they have placed objects within # the OS non-working region. - if np.less(p_t, self.inner_bound).any() or np.greater(p_t, self.outer_bound).any(): - logger.warning(f"'{cmd_str}' this object traverses the Outer Surface. This is an advanced feature.") + if ( + np.less(p_t, self.inner_bound).any() + or np.greater(p_t, self.outer_bound).any() + ): + logger.warning( + f"'{cmd_str}' this object traverses the Outer Surface. This is an advanced feature." + ) return p_t def discretise_static_point(self, p): diff --git a/gprMax/waveforms.py b/gprMax/waveforms.py index 3ef312c0..539c71e8 100644 --- a/gprMax/waveforms.py +++ b/gprMax/waveforms.py @@ -66,7 +66,13 @@ class Waveform: waveforms. """ - if self.type in ["gaussian", "gaussiandot", "gaussiandotnorm", "gaussianprime", "gaussiandoubleprime"]: + if self.type in [ + "gaussian", + "gaussiandot", + "gaussiandotnorm", + "gaussianprime", + "gaussiandoubleprime", + ]: self.chi = 1 / self.freq self.zeta = 2 * np.pi**2 * self.freq**2 elif self.type in ["gaussiandotdot", "gaussiandotdotnorm", "ricker"]: @@ -98,22 +104,41 @@ class Waveform: elif self.type == "gaussiandotnorm": delay = time - self.chi normalise = np.sqrt(np.exp(1) / (2 * self.zeta)) - ampvalue = -2 * self.zeta * delay * np.exp(-self.zeta * delay**2) * normalise + ampvalue = ( + -2 * self.zeta * delay * np.exp(-self.zeta * delay**2) * normalise + ) elif self.type in ["gaussiandotdot", "gaussiandoubleprime"]: delay = time - self.chi - ampvalue = 2 * self.zeta * (2 * self.zeta * delay**2 - 1) * np.exp(-self.zeta * delay**2) + ampvalue = ( + 2 + * self.zeta + * (2 * self.zeta * delay**2 - 1) + * np.exp(-self.zeta * delay**2) + ) elif self.type == "gaussiandotdotnorm": delay = time - self.chi normalise = 1 / (2 * self.zeta) - ampvalue = 2 * self.zeta * (2 * self.zeta * delay**2 - 1) * np.exp(-self.zeta * delay**2) * normalise + ampvalue = ( + 2 + * self.zeta + * (2 * self.zeta * delay**2 - 1) + * np.exp(-self.zeta * delay**2) + * normalise + ) elif self.type == "ricker": delay = time - self.chi normalise = 1 / (2 * self.zeta) ampvalue = -( - (2 * self.zeta * (2 * self.zeta * delay**2 - 1) * np.exp(-self.zeta * delay**2)) * normalise + ( + 2 + * self.zeta + * (2 * self.zeta * delay**2 - 1) + * np.exp(-self.zeta * delay**2) + ) + * normalise ) elif self.type == "sine": diff --git a/setup.py b/setup.py index 30034446..118eff18 100644 --- a/setup.py +++ b/setup.py @@ -33,11 +33,15 @@ from setuptools import Extension, find_packages, setup # Check Python version MIN_PYTHON_VERSION = (3, 7) if sys.version_info[:2] < MIN_PYTHON_VERSION: - sys.exit("\nExited: Requires Python {MIN_PYTHON_VERSION[0]}.{MIN_PYTHON_VERSION[1]} or newer!\n") + sys.exit( + "\nExited: Requires Python {MIN_PYTHON_VERSION[0]}.{MIN_PYTHON_VERSION[1]} or newer!\n" + ) # Importing gprMax _version__.py before building can cause issues. with open("gprMax/_version.py", "r") as fd: - version = re.search(r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', fd.read(), re.MULTILINE)[1] + version = re.search( + r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', fd.read(), re.MULTILINE + )[1] def build_dispersive_material_templates(): @@ -108,7 +112,9 @@ def build_dispersive_material_templates(): ] ) - with open(os.path.join("gprMax", "cython", "fields_updates_dispersive.pyx"), "w") as f: + with open( + os.path.join("gprMax", "cython", "fields_updates_dispersive.pyx"), "w" + ) as f: f.write(r) @@ -144,9 +150,9 @@ if "cleanall" in sys.argv: except OSError: print(f"Could not remove: {filebase}.c") # Remove compiled Cython modules - libfile = glob.glob(os.path.join(os.getcwd(), os.path.splitext(file)[0]) + "*.pyd") + glob.glob( - os.path.join(os.getcwd(), os.path.splitext(file)[0]) + "*.so" - ) + libfile = glob.glob( + os.path.join(os.getcwd(), os.path.splitext(file)[0]) + "*.pyd" + ) + glob.glob(os.path.join(os.getcwd(), os.path.splitext(file)[0]) + "*.so") if libfile: libfile = libfile[0] try: @@ -182,7 +188,11 @@ else: elif sys.platform == "darwin": # Check for Intel or Apple M series CPU cpuID = ( - subprocess.check_output("sysctl -n machdep.cpu.brand_string", shell=True, stderr=subprocess.STDOUT) + subprocess.check_output( + "sysctl -n machdep.cpu.brand_string", + shell=True, + stderr=subprocess.STDOUT, + ) .decode("utf-8") .strip() ) @@ -196,7 +206,11 @@ else: # Use newest gcc found os.environ["CC"] = gccpath[-1].split(os.sep)[-1] if "Apple" in cpuID: - rpath = "/opt/homebrew/opt/gcc/lib/gcc/" + gccpath[-1].split(os.sep)[-1][-1] + "/" + rpath = ( + "/opt/homebrew/opt/gcc/lib/gcc/" + + gccpath[-1].split(os.sep)[-1][-1] + + "/" + ) else: raise ( f"Cannot find gcc in {gccbasepath}. gprMax requires gcc " @@ -214,7 +228,13 @@ else: pass os.environ["MIN_SUPPORTED_MACOSX_DEPLOYMENT_TARGET"] = MIN_MACOS_VERSION # Sometimes worth testing with '-fstrict-aliasing', '-fno-common' - compile_args = ["-O3", "-w", "-fopenmp", "-march=native", f"-mmacosx-version-min={MIN_MACOS_VERSION}"] + compile_args = [ + "-O3", + "-w", + "-fopenmp", + "-march=native", + f"-mmacosx-version-min={MIN_MACOS_VERSION}", + ] linker_args = ["-fopenmp", f"-mmacosx-version-min={MIN_MACOS_VERSION}"] libraries = ["gomp"] @@ -261,7 +281,8 @@ else: version=version, author="Craig Warren, Antonis Giannopoulos, and John Hartley", url="http://www.gprmax.com", - description="Electromagnetic Modelling Software based on the " + "Finite-Difference Time-Domain (FDTD) method", + description="Electromagnetic Modelling Software based on the " + + "Finite-Difference Time-Domain (FDTD) method", long_description=long_description, long_description_content_type="text/x-rst", license="GPLv3+", diff --git a/testing/analytical_solutions.py b/testing/analytical_solutions.py index 162c7e01..4cbc7686 100644 --- a/testing/analytical_solutions.py +++ b/testing/analytical_solutions.py @@ -157,14 +157,18 @@ def hertzian_dipole_fs(iterations, dt, dxdydz, rx): ) # Hx - fields[timestep, 3] = -(Hx_y / (4 * np.pi * Hr_x**3)) * (f_Hx + (tau_Hx * fdot_Hx)) + fields[timestep, 3] = -(Hx_y / (4 * np.pi * Hr_x**3)) * ( + f_Hx + (tau_Hx * fdot_Hx) + ) # Hy try: tmp = Hy_x / Hy_y except ZeroDivisionError: tmp = 0 - fields[timestep, 4] = -tmp * (-(Hy_y / (4 * np.pi * Hr_y**3)) * (f_Hy + (tau_Hy * fdot_Hy))) + fields[timestep, 4] = -tmp * ( + -(Hy_y / (4 * np.pi * Hr_y**3)) * (f_Hy + (tau_Hy * fdot_Hy)) + ) # Hz fields[timestep, 5] = 0 diff --git a/testing/benchmarking/bench_simple.py b/testing/benchmarking/bench_simple.py index 7898aaa2..26f8bcd8 100644 --- a/testing/benchmarking/bench_simple.py +++ b/testing/benchmarking/bench_simple.py @@ -1,9 +1,8 @@ """A series of models with different domain sizes used for benchmarking. - The domain is free space with a simple source (Hertzian Dipole) and - receiver at the centre. +The domain is free space with a simple source (Hertzian Dipole) and +receiver at the centre. """ - import itertools from pathlib import Path @@ -36,7 +35,9 @@ for d, threads in itertools.product(domains, ompthreads): dxdydz = gprMax.Discretisation(p1=(dl, dl, dl)) time_window = gprMax.TimeWindow(time=3e-9) wv = gprMax.Waveform(wave_type="gaussiandotnorm", amp=1, freq=900e6, id="MySource") - src = gprMax.HertzianDipole(p1=(x / 2, y / 2, z / 2), polarisation="x", waveform_id="MySource") + src = gprMax.HertzianDipole( + p1=(x / 2, y / 2, z / 2), polarisation="x", waveform_id="MySource" + ) omp = gprMax.OMPThreads(n=threads) diff --git a/testing/diff_output_files.py b/testing/diff_output_files.py index de1be507..eb766444 100644 --- a/testing/diff_output_files.py +++ b/testing/diff_output_files.py @@ -60,9 +60,13 @@ def diff_output_files(filename1, filename2): # Arrays for storing time time1 = np.zeros((file1.attrs["Iterations"]), dtype=floattype1) - time1 = np.linspace(0, (file1.attrs["Iterations"] - 1), num=file1.attrs["Iterations"]) + time1 = np.linspace( + 0, (file1.attrs["Iterations"] - 1), num=file1.attrs["Iterations"] + ) time2 = np.zeros((file2.attrs["Iterations"]), dtype=floattype2) - time2 = np.linspace(0, (file2.attrs["Iterations"] - 1), num=file2.attrs["Iterations"]) + time2 = np.linspace( + 0, (file2.attrs["Iterations"] - 1), num=file2.attrs["Iterations"] + ) # Arrays for storing field data data1 = np.zeros((file1.attrs["Iterations"], len(outputs1)), dtype=floattype1) @@ -82,7 +86,10 @@ def diff_output_files(filename1, filename2): for i in range(len(outputs2)): maxi = np.amax(np.abs(data1[:, i])) datadiffs[:, i] = np.divide( - np.abs(data2[:, i] - data1[:, i]), maxi, out=np.zeros_like(data1[:, i]), where=maxi != 0 + np.abs(data2[:, i] - data1[:, i]), + maxi, + out=np.zeros_like(data1[:, i]), + where=maxi != 0, ) # Replace any division by zero with zero # Calculate power (ignore warning from taking a log of any zero values) diff --git a/testing/models_pmls/pml_3D_pec_plate/plot_pml_comparison.py b/testing/models_pmls/pml_3D_pec_plate/plot_pml_comparison.py index d6e449d1..6a60cbf0 100644 --- a/testing/models_pmls/pml_3D_pec_plate/plot_pml_comparison.py +++ b/testing/models_pmls/pml_3D_pec_plate/plot_pml_comparison.py @@ -46,12 +46,16 @@ maxerrors = [] testmodels = [basename + "_" + s for s in PMLIDs] fig, ax = plt.subplots( - subplot_kw=dict(xlabel="Iterations", ylabel="Error [dB]"), figsize=(20, 10), facecolor="w", edgecolor="w" + subplot_kw=dict(xlabel="Iterations", ylabel="Error [dB]"), + figsize=(20, 10), + facecolor="w", + edgecolor="w", ) for x, model in enumerate(testmodels): time, datadiffs = diff_output_files( - fn.parent.joinpath(basename + "_ref.h5"), fn.parent.joinpath(basename + str(x + 1) + ".h5") + fn.parent.joinpath(basename + "_ref.h5"), + fn.parent.joinpath(basename + str(x + 1) + ".h5"), ) # Print maximum error value @@ -60,7 +64,14 @@ for x, model in enumerate(testmodels): logger.info(f"{model}: Max. error {maxerrors[x]}") # Plot diffs (select column to choose field component, 0-Ex, 1-Ey etc..) - ax.plot(time[start::], datadiffs[start::, 1], color=next(colors), lw=2, ls=next(lines), label=model) + ax.plot( + time[start::], + datadiffs[start::, 1], + color=next(colors), + lw=2, + ls=next(lines), + label=model, + ) ax.set_xticks(np.arange(0, 2200, step=100)) ax.set_xlim([0, 2100]) ax.set_yticks(np.arange(-160, 0, step=20)) diff --git a/testing/models_pmls/pml_3D_pec_plate/pml_3D_pec_plate.py b/testing/models_pmls/pml_3D_pec_plate/pml_3D_pec_plate.py index cac3b3e3..033c1ba5 100755 --- a/testing/models_pmls/pml_3D_pec_plate/pml_3D_pec_plate.py +++ b/testing/models_pmls/pml_3D_pec_plate/pml_3D_pec_plate.py @@ -21,11 +21,17 @@ dxdydz = gprMax.Discretisation(p1=(dl, dl, dl)) time_window = gprMax.TimeWindow(iterations=2100) tssf = gprMax.TimeStepStabilityFactor(f=0.99) -waveform = gprMax.Waveform(wave_type="gaussiandotnorm", amp=1, freq=9.42e9, id="mypulse") -hertzian_dipole = gprMax.HertzianDipole(polarisation="z", p1=(0.013, 0.013, 0.014), waveform_id="mypulse") +waveform = gprMax.Waveform( + wave_type="gaussiandotnorm", amp=1, freq=9.42e9, id="mypulse" +) +hertzian_dipole = gprMax.HertzianDipole( + polarisation="z", p1=(0.013, 0.013, 0.014), waveform_id="mypulse" +) rx = gprMax.Rx(p1=(0.038, 0.114, 0.013)) -plate = gprMax.Plate(p1=(0.013, 0.013, 0.013), p2=(0.038, 0.113, 0.013), material_id="pec") +plate = gprMax.Plate( + p1=(0.013, 0.013, 0.013), p2=(0.038, 0.113, 0.013), material_id="pec" +) gv1 = gprMax.GeometryView( p1=(0, 0, 0), diff --git a/testing/models_pmls/pml_3D_pec_plate/pml_3D_pec_plate_ref.py b/testing/models_pmls/pml_3D_pec_plate/pml_3D_pec_plate_ref.py index 97e2d75c..8d06ea71 100755 --- a/testing/models_pmls/pml_3D_pec_plate/pml_3D_pec_plate_ref.py +++ b/testing/models_pmls/pml_3D_pec_plate/pml_3D_pec_plate_ref.py @@ -21,11 +21,17 @@ dxdydz = gprMax.Discretisation(p1=(dl, dl, dl)) time_window = gprMax.TimeWindow(iterations=2100) tssf = gprMax.TimeStepStabilityFactor(f=0.99) -waveform = gprMax.Waveform(wave_type="gaussiandotnorm", amp=1, freq=9.42e9, id="mypulse") -hertzian_dipole = gprMax.HertzianDipole(polarisation="z", p1=(0.088, 0.088, 0.089), waveform_id="mypulse") +waveform = gprMax.Waveform( + wave_type="gaussiandotnorm", amp=1, freq=9.42e9, id="mypulse" +) +hertzian_dipole = gprMax.HertzianDipole( + polarisation="z", p1=(0.088, 0.088, 0.089), waveform_id="mypulse" +) rx = gprMax.Rx(p1=(0.113, 0.189, 0.088)) -plate = gprMax.Plate(p1=(0.088, 0.088, 0.088), p2=(0.113, 0.188, 0.088), material_id="pec") +plate = gprMax.Plate( + p1=(0.088, 0.088, 0.088), p2=(0.113, 0.188, 0.088), material_id="pec" +) gv1 = gprMax.GeometryView( p1=(0, 0, 0), diff --git a/testing/models_pmls/pml_basic/plot_pml_comparison.py b/testing/models_pmls/pml_basic/plot_pml_comparison.py index b9c7b7f4..6a04d542 100644 --- a/testing/models_pmls/pml_basic/plot_pml_comparison.py +++ b/testing/models_pmls/pml_basic/plot_pml_comparison.py @@ -74,7 +74,13 @@ for x, PMLID in enumerate(PMLIDs): ax.set_ylabel(f"{output} error [dB]") # Save a PDF/PNG of the figure - fig.savefig(basename + "_diffs_" + PMLID + ".pdf", dpi=None, format="pdf", bbox_inches="tight", pad_inches=0.1) + fig.savefig( + basename + "_diffs_" + PMLID + ".pdf", + dpi=None, + format="pdf", + bbox_inches="tight", + pad_inches=0.1, + ) # fig.savefig(basename + "_diffs_" + PMLID + ".png", dpi=150, format='png', bbox_inches='tight', pad_inches=0.1) plt.show() diff --git a/testing/models_pmls/pml_basic/pml_basic.py b/testing/models_pmls/pml_basic/pml_basic.py index 0c29b4f1..a54492e8 100755 --- a/testing/models_pmls/pml_basic/pml_basic.py +++ b/testing/models_pmls/pml_basic/pml_basic.py @@ -18,7 +18,9 @@ dxdydz = gprMax.Discretisation(p1=(dl, dl, dl)) time_window = gprMax.TimeWindow(time=3e-9) waveform = gprMax.Waveform(wave_type="gaussian", amp=1, freq=1e9, id="mypulse") -hertzian_dipole = gprMax.HertzianDipole(polarisation="z", p1=(0.050, 0.050, 0.050), waveform_id="mypulse") +hertzian_dipole = gprMax.HertzianDipole( + polarisation="z", p1=(0.050, 0.050, 0.050), waveform_id="mypulse" +) rx = gprMax.Rx(p1=(0.070, 0.070, 0.070)) # PML cases diff --git a/testing/other_codes/vs_MoM_MATLAB/antenna_bowtie_fs/antenna_bowtie_fs.py b/testing/other_codes/vs_MoM_MATLAB/antenna_bowtie_fs/antenna_bowtie_fs.py index fd2cdbfc..8d1d666f 100644 --- a/testing/other_codes/vs_MoM_MATLAB/antenna_bowtie_fs/antenna_bowtie_fs.py +++ b/testing/other_codes/vs_MoM_MATLAB/antenna_bowtie_fs/antenna_bowtie_fs.py @@ -31,7 +31,10 @@ tx_pos = (x / 2, y / 2, z / 2) # Source excitation and type wave = gprMax.Waveform(wave_type="gaussian", amp=1, freq=1.5e9, id="mypulse") tl = gprMax.TransmissionLine( - p1=(tx_pos[0], tx_pos[1], tx_pos[2]), polarisation="x", resistance=50, waveform_id="mypulse" + p1=(tx_pos[0], tx_pos[1], tx_pos[2]), + polarisation="x", + resistance=50, + waveform_id="mypulse", ) scene.add(wave) scene.add(tl) @@ -59,8 +62,16 @@ scene.add(t2) # Detailed geometry view around bowtie and feed position gv1 = gprMax.GeometryView( - p1=(tx_pos[0] - bowtie_dims[0] - 2 * dl, tx_pos[1] - bowtie_dims[1] / 2 - 2 * dl, tx_pos[2] - 2 * dl), - p2=(tx_pos[0] + bowtie_dims[0] + 2 * dl, tx_pos[1] + bowtie_dims[1] / 2 + 2 * dl, tx_pos[2] + 2 * dl), + p1=( + tx_pos[0] - bowtie_dims[0] - 2 * dl, + tx_pos[1] - bowtie_dims[1] / 2 - 2 * dl, + tx_pos[2] - 2 * dl, + ), + p2=( + tx_pos[0] + bowtie_dims[0] + 2 * dl, + tx_pos[1] + bowtie_dims[1] / 2 + 2 * dl, + tx_pos[2] + 2 * dl, + ), dl=(dl, dl, dl), filename="antenna_bowtie_fs_pcb", output_type="f", diff --git a/testing/test_experimental.py b/testing/test_experimental.py index 3228a46b..f3b256c5 100644 --- a/testing/test_experimental.py +++ b/testing/test_experimental.py @@ -32,11 +32,14 @@ logger = logging.getLogger(__name__) # Parse command line arguments parser = argparse.ArgumentParser( - description="Plots a comparison of fields between " + "given simulation output and experimental data files.", + description="Plots a comparison of fields between " + + "given simulation output and experimental data files.", usage="cd gprMax; python -m testing.test_experimental modelfile realfile output", ) parser.add_argument("modelfile", help="name of model output file including path") -parser.add_argument("realfile", help="name of file containing experimental data including path") +parser.add_argument( + "realfile", help="name of file containing experimental data including path" +) parser.add_argument("output", help="output to be plotted, i.e. Ex Ey Ez", nargs="+") args = parser.parse_args() diff --git a/testing/test_models.py b/testing/test_models.py index e945adbd..afa5f3df 100644 --- a/testing/test_models.py +++ b/testing/test_models.py @@ -90,13 +90,19 @@ for i, model in enumerate(testmodels): # Arrays for storing time float_or_double = filetest[path + outputstest[0]].dtype timetest = ( - np.linspace(0, (filetest.attrs["Iterations"] - 1) * filetest.attrs["dt"], num=filetest.attrs["Iterations"]) + np.linspace( + 0, + (filetest.attrs["Iterations"] - 1) * filetest.attrs["dt"], + num=filetest.attrs["Iterations"], + ) / 1e-9 ) timeref = timetest # Arrays for storing field data - datatest = np.zeros((filetest.attrs["Iterations"], len(outputstest)), dtype=float_or_double) + datatest = np.zeros( + (filetest.attrs["Iterations"], len(outputstest)), dtype=float_or_double + ) for ID, name in enumerate(outputstest): datatest[:, ID] = filetest[path + str(name)][:] if np.any(np.isnan(datatest[:, ID])): @@ -106,11 +112,18 @@ for i, model in enumerate(testmodels): # Tx/Rx position to feed to analytical solution rxpos = filetest[path].attrs["Position"] txpos = filetest["/srcs/src1/"].attrs["Position"] - rxposrelative = ((rxpos[0] - txpos[0]), (rxpos[1] - txpos[1]), (rxpos[2] - txpos[2])) + rxposrelative = ( + (rxpos[0] - txpos[0]), + (rxpos[1] - txpos[1]), + (rxpos[2] - txpos[2]), + ) # Analytical solution of a dipole in free space dataref = hertzian_dipole_fs( - filetest.attrs["Iterations"], filetest.attrs["dt"], filetest.attrs["dx_dy_dz"], rxposrelative + filetest.attrs["Iterations"], + filetest.attrs["dt"], + filetest.attrs["dx_dy_dz"], + rxposrelative, ) filetest.close() @@ -143,18 +156,30 @@ for i, model in enumerate(testmodels): # Arrays for storing time timeref = np.zeros((fileref.attrs["Iterations"]), dtype=float_or_doubleref) timeref = ( - np.linspace(0, (fileref.attrs["Iterations"] - 1) * fileref.attrs["dt"], num=fileref.attrs["Iterations"]) + np.linspace( + 0, + (fileref.attrs["Iterations"] - 1) * fileref.attrs["dt"], + num=fileref.attrs["Iterations"], + ) / 1e-9 ) timetest = np.zeros((filetest.attrs["Iterations"]), dtype=float_or_doubletest) timetest = ( - np.linspace(0, (filetest.attrs["Iterations"] - 1) * filetest.attrs["dt"], num=filetest.attrs["Iterations"]) + np.linspace( + 0, + (filetest.attrs["Iterations"] - 1) * filetest.attrs["dt"], + num=filetest.attrs["Iterations"], + ) / 1e-9 ) # Arrays for storing field data - dataref = np.zeros((fileref.attrs["Iterations"], len(outputsref)), dtype=float_or_doubleref) - datatest = np.zeros((filetest.attrs["Iterations"], len(outputstest)), dtype=float_or_doubletest) + dataref = np.zeros( + (fileref.attrs["Iterations"], len(outputsref)), dtype=float_or_doubleref + ) + datatest = np.zeros( + (filetest.attrs["Iterations"], len(outputstest)), dtype=float_or_doubletest + ) for ID, name in enumerate(outputsref): dataref[:, ID] = fileref[path + str(name)][:] datatest[:, ID] = filetest[path + str(name)][:] @@ -170,7 +195,10 @@ for i, model in enumerate(testmodels): for i in range(len(outputstest)): maxi = np.amax(np.abs(dataref[:, i])) datadiffs[:, i] = np.divide( - np.abs(dataref[:, i] - datatest[:, i]), maxi, out=np.zeros_like(dataref[:, i]), where=maxi != 0 + np.abs(dataref[:, i] - datatest[:, i]), + maxi, + out=np.zeros_like(dataref[:, i]), + where=maxi != 0, ) # Replace any division by zero with zero # Calculate power (ignore warning from taking a log of any zero values) @@ -260,8 +288,20 @@ for i, model in enumerate(testmodels): # bbox_inches='tight', pad_inches=0.1) # fig2.savefig(savediffs.with_suffix('.pdf'), dpi=None, format='pdf', # bbox_inches='tight', pad_inches=0.1) - fig1.savefig(file.with_suffix(".png"), dpi=150, format="png", bbox_inches="tight", pad_inches=0.1) - fig2.savefig(filediffs.with_suffix(".png"), dpi=150, format="png", bbox_inches="tight", pad_inches=0.1) + fig1.savefig( + file.with_suffix(".png"), + dpi=150, + format="png", + bbox_inches="tight", + pad_inches=0.1, + ) + fig2.savefig( + filediffs.with_suffix(".png"), + dpi=150, + format="png", + bbox_inches="tight", + pad_inches=0.1, + ) # Summary of results for name, data in sorted(testresults.items()): diff --git a/toolboxes/AntennaPatterns/plot_fields.py b/toolboxes/AntennaPatterns/plot_fields.py index b43160ed..3ef8a397 100644 --- a/toolboxes/AntennaPatterns/plot_fields.py +++ b/toolboxes/AntennaPatterns/plot_fields.py @@ -100,12 +100,18 @@ if epsr: ax.plot([0, np.deg2rad(180 + thetac)], [min, 0], color="0.7", lw=2) ax.plot([np.deg2rad(270), np.deg2rad(90)], [0, 0], color="0.7", lw=2) ax.annotate("Air", xy=(np.deg2rad(270), 0), xytext=(8, 8), textcoords="offset points") -ax.annotate("Ground", xy=(np.deg2rad(270), 0), xytext=(8, -15), textcoords="offset points") +ax.annotate( + "Ground", xy=(np.deg2rad(270), 0), xytext=(8, -15), textcoords="offset points" +) # Plot patterns for patt in range(0, len(radii)): - pattplot = np.append(patterns[patt, :], patterns[patt, 0]) # Append start value to close circle - pattplot = pattplot / np.max(np.max(patterns)) # Normalise, based on set of patterns + pattplot = np.append( + patterns[patt, :], patterns[patt, 0] + ) # Append start value to close circle + pattplot = pattplot / np.max( + np.max(patterns) + ) # Normalise, based on set of patterns # Calculate power (ignore warning from taking a log of any zero values) with np.errstate(divide="ignore"): @@ -140,7 +146,11 @@ ax.set_yticklabels(yticks) ax.grid(True) handles, existlabels = ax.get_legend_handles_labels() leg = ax.legend( - [handles[0], handles[-1]], [existlabels[0], existlabels[-1]], ncol=2, loc=(0.27, -0.12), frameon=False + [handles[0], handles[-1]], + [existlabels[0], existlabels[-1]], + ncol=2, + loc=(0.27, -0.12), + frameon=False, ) # Plot just first and last legend entries # leg = ax.legend([handles[0], handles[-3], handles[-2], handles[-1]], [existlabels[0], existlabels[-3], existlabels[-2], existlabels[-1]], ncol=4, loc=(-0.13,-0.12), frameon=False) [legobj.set_linewidth(2) for legobj in leg.legendHandles] diff --git a/toolboxes/AustinManWoman/head_only_h5.py b/toolboxes/AustinManWoman/head_only_h5.py index ff4de7fb..0ff841dc 100644 --- a/toolboxes/AustinManWoman/head_only_h5.py +++ b/toolboxes/AustinManWoman/head_only_h5.py @@ -9,9 +9,13 @@ logger = logging.getLogger(__name__) # Parse command line arguments parser = argparse.ArgumentParser( - description="Writes a HDF5 file of AustinMan or AustinWoman head only.", usage="python head_only_hdf5 filename" + description="Writes a HDF5 file of AustinMan or AustinWoman head only.", + usage="python head_only_hdf5 filename", +) +parser.add_argument( + "filename", + help="name and path to (HDF5) file containing AustinMan or AustinWoman model", ) -parser.add_argument("filename", help="name and path to (HDF5) file containing AustinMan or AustinWoman model") args = parser.parse_args() # Read full body HDF5 file @@ -22,7 +26,9 @@ data = f["/data"][:, :, :] # Define head as last 1/8 of total body height nzhead = 7 * int(data.shape[2] / 8) -logger.info(f"Dimensions of head model: {data.shape[0]:g} x {data.shape[1]:g} x {data.shape[2] - nzhead:g} cells") +logger.info( + f"Dimensions of head model: {data.shape[0]:g} x {data.shape[1]:g} x {data.shape[2] - nzhead:g} cells" +) # Write HDF5 file headfile = os.path.splitext(args.filename)[0] + "_head.h5" diff --git a/toolboxes/DebyeFit/Debye_Fit.py b/toolboxes/DebyeFit/Debye_Fit.py index 47fa6aab..5057f387 100644 --- a/toolboxes/DebyeFit/Debye_Fit.py +++ b/toolboxes/DebyeFit/Debye_Fit.py @@ -100,7 +100,15 @@ class Relaxation(object): def check_inputs(self): """Check the validity of the inputs.""" try: - d = [float(i) for i in [self.number_of_debye_poles, self.sigma, self.mu, self.mu_sigma]] + d = [ + float(i) + for i in [ + self.number_of_debye_poles, + self.sigma, + self.mu, + self.mu_sigma, + ] + ] except ValueError: sys.exit("The inputs should be numeric.") if not isinstance(self.number_of_debye_poles, int): @@ -120,7 +128,9 @@ class Relaxation(object): Returns: s (str): Info about chosen function and its parameters. """ - print(f"Approximating {self.name}" f" using {self.number_of_debye_poles} Debye poles") + print( + f"Approximating {self.name} using {self.number_of_debye_poles} Debye poles" + ) print(f"{self.name} parameters: ") s = "".join(f"{k:10s} = {v}\n" for k, v in self.params.items()) print(s) @@ -172,7 +182,12 @@ class Relaxation(object): self.rl, self.im = q.real, q.imag if self.number_of_debye_poles == -1: - print("\n#########", "Try to automaticaly fit number of Debye poles, up to 20!", "##########\n", sep="") + print( + "\n#########", + "Try to automaticaly fit number of Debye poles, up to 20!", + "##########\n", + sep="", + ) error = np.infty # artificial best error starting value self.number_of_debye_poles = 1 iteration = 1 @@ -195,7 +210,11 @@ class Relaxation(object): # Print the results in gprMax format style properties = self.print_output(tau, weights, ee) - print(f"The average fractional error for:\n" f"- real part: {err_real}\n" f"- imaginary part: {err_imag}\n") + print( + f"The average fractional error for:\n" + f"- real part: {err_real}\n" + f"- imaginary part: {err_imag}\n" + ) if self.save: self.save_result(properties) # Plot the actual and the approximate dielectric properties @@ -220,16 +239,20 @@ class Relaxation(object): print(f" |{'e_inf':^14s}|{'De':^14s}|{'log(tau_0)':^25s}|") print("_" * 65) for i in range(0, len(tau)): - print(f"Debye {i + 1}|{ee / len(tau):^14.5f}|{weights[i]:^14.5f}|{tau[i]:^25.5f}|") + print( + f"Debye {i + 1}|{ee / len(tau):^14.5f}|{weights[i]:^14.5f}|{tau[i]:^25.5f}|" + ) print("_" * 65) # Print the Debye expnasion in a gprMax format material_prop = [] - material_prop.append(f"#material: {ee} {self.sigma} {self.mu} {self.mu_sigma} {self.material_name}\n") + material_prop.append( + f"#material: {ee} {self.sigma} {self.mu} {self.mu_sigma} {self.material_name}\n" + ) print(material_prop[0], end="") dispersion_prop = f"#add_dispersion_debye: {len(tau)}" for i in range(len(tau)): - dispersion_prop += f" {weights[i]} {10**tau[i]}" + dispersion_prop += f" {weights[i]} {10 ** tau[i]}" dispersion_prop += f" {self.material_name}" print(dispersion_prop) material_prop.append(dispersion_prop + "\n") @@ -251,10 +274,34 @@ class Relaxation(object): gs = gridspec.GridSpec(2, 1) ax = fig.add_subplot(gs[0]) ax.grid(b=True, which="major", linewidth=0.2, linestyle="--") - ax.semilogx(self.freq * 1e-6, rl_exp, "b-", linewidth=2.0, label="Debye Expansion: Real part") - ax.semilogx(self.freq * 1e-6, -im_exp, "k-", linewidth=2.0, label="Debye Expansion: Imaginary part") - ax.semilogx(self.freq * 1e-6, self.rl, "r.", linewidth=2.0, label=f"{self.name}: Real part") - ax.semilogx(self.freq * 1e-6, -self.im, "g.", linewidth=2.0, label=f"{self.name}: Imaginary part") + ax.semilogx( + self.freq * 1e-6, + rl_exp, + "b-", + linewidth=2.0, + label="Debye Expansion: Real part", + ) + ax.semilogx( + self.freq * 1e-6, + -im_exp, + "k-", + linewidth=2.0, + label="Debye Expansion: Imaginary part", + ) + ax.semilogx( + self.freq * 1e-6, + self.rl, + "r.", + linewidth=2.0, + label=f"{self.name}: Real part", + ) + ax.semilogx( + self.freq * 1e-6, + -self.im, + "g.", + linewidth=2.0, + label=f"{self.name}: Imaginary part", + ) ax.set_ylim([-1, np.max(np.concatenate([self.rl, -self.im])) + 1]) ax.legend() ax.set_xlabel("Frequency (MHz)") @@ -262,8 +309,20 @@ class Relaxation(object): ax = fig.add_subplot(gs[1]) ax.grid(b=True, which="major", linewidth=0.2, linestyle="--") - ax.semilogx(self.freq * 1e-6, (rl_exp - self.rl) / (self.rl + 1), "b-", linewidth=2.0, label="Real part") - ax.semilogx(self.freq * 1e-6, (-im_exp + self.im) / (self.im + 1), "k-", linewidth=2.0, label="Imaginary part") + ax.semilogx( + self.freq * 1e-6, + (rl_exp - self.rl) / (self.rl + 1), + "b-", + linewidth=2.0, + label="Real part", + ) + ax.semilogx( + self.freq * 1e-6, + (-im_exp + self.im) / (self.im + 1), + "k-", + linewidth=2.0, + label="Imaginary part", + ) ax.legend() ax.set_xlabel("Frequency (MHz)") ax.set_ylabel("Relative approximation error") @@ -284,8 +343,12 @@ class Relaxation(object): avg_err_imag (float): average fractional error for conductivity (imaginary part) """ - avg_err_real = np.sum(np.abs((rl_exp - self.rl) / (self.rl + 1)) * 100) / len(rl_exp) - avg_err_imag = np.sum(np.abs((-im_exp + self.im) / (self.im + 1)) * 100) / len(im_exp) + avg_err_real = np.sum(np.abs((rl_exp - self.rl) / (self.rl + 1)) * 100) / len( + rl_exp + ) + avg_err_imag = np.sum(np.abs((-im_exp + self.im) / (self.im + 1)) * 100) / len( + im_exp + ) return avg_err_real, avg_err_imag @staticmethod @@ -306,7 +369,10 @@ class Relaxation(object): elif os.path.isdir("user_libs/materials"): file_path = os.path.join("user_libs", "materials", "my_materials.txt") else: - sys.exit("Cannot save material properties " f"in {os.path.join(fdir, 'my_materials.txt')}!") + sys.exit( + "Cannot save material properties " + f"in {os.path.join(fdir, 'my_materials.txt')}!" + ) with open(file_path, "a") as fileH: fileH.write(f"## {output[0].split(' ')[-1]}") fileH.writelines(output) @@ -382,7 +448,13 @@ class HavriliakNegami(Relaxation): self.f_min, self.f_max = f_min, f_max # Choosing n frequencies logarithmicaly equally spaced between the bounds given self.set_freq(self.f_min, self.f_max, self.f_n) - self.e_inf, self.alpha, self.beta, self.de, self.tau_0 = e_inf, alpha, beta, de, tau_0 + self.e_inf, self.alpha, self.beta, self.de, self.tau_0 = ( + e_inf, + alpha, + beta, + de, + tau_0, + ) self.params = { "f_min": self.f_min, "f_max": self.f_max, @@ -412,7 +484,11 @@ class HavriliakNegami(Relaxation): def calculation(self): """Calculates the Havriliak-Negami function for the given parameters.""" - return self.e_inf + self.de / (1 + (1j * 2 * np.pi * self.freq * self.tau_0) ** self.alpha) ** self.beta + return ( + self.e_inf + + self.de + / (1 + (1j * 2 * np.pi * self.freq * self.tau_0) ** self.alpha) ** self.beta + ) class Jonscher(Relaxation): @@ -501,9 +577,9 @@ class Jonscher(Relaxation): def calculation(self): """Calculates the Q function for the given parameters""" - return self.e_inf + (self.a_p * (2 * np.pi * self.freq / self.omega_p) ** (self.n_p - 1)) * ( - 1 - 1j / np.tan(self.n_p * np.pi / 2) - ) + return self.e_inf + ( + self.a_p * (2 * np.pi * self.freq / self.omega_p) ** (self.n_p - 1) + ) * (1 - 1j / np.tan(self.n_p * np.pi / 2)) class Crim(Relaxation): @@ -583,7 +659,9 @@ class Crim(Relaxation): if (np.array(d) < 0).sum() != 0: sys.exit("The inputs should be positive.") if len(self.volumetric_fractions) != len(self.materials): - sys.exit("Number of volumetric volumes does not match the dielectric properties") + sys.exit( + "Number of volumetric volumes does not match the dielectric properties" + ) # Check if the materials are at least two if len(self.volumetric_fractions) < 2: sys.exit("The materials should be at least 2") @@ -604,7 +682,10 @@ class Crim(Relaxation): def print_info(self): """Print information about chosen approximation settings""" - print(f"Approximating Complex Refractive Index Model (CRIM)" f" using {self.number_of_debye_poles} Debye poles") + print( + f"Approximating Complex Refractive Index Model (CRIM)" + f" using {self.number_of_debye_poles} Debye poles" + ) print("CRIM parameters: ") for i in range(len(self.volumetric_fractions)): print(f"Material {i + 1}.:") @@ -617,7 +698,9 @@ class Crim(Relaxation): def calculation(self): """Calculates the Crim function for the given parameters""" return np.sum( - np.repeat(self.volumetric_fractions, len(self.freq)).reshape((-1, len(self.materials))) + np.repeat(self.volumetric_fractions, len(self.freq)).reshape( + (-1, len(self.materials)) + ) * ( self.materials[:, 0] + self.materials[:, 1] @@ -626,7 +709,9 @@ class Crim(Relaxation): + 1j * 2 * np.pi - * np.repeat(self.freq, len(self.materials)).reshape((-1, len(self.materials))) + * np.repeat(self.freq, len(self.materials)).reshape( + (-1, len(self.materials)) + ) * self.materials[:, 2] ) ) @@ -691,13 +776,19 @@ class Rawdata(Relaxation): # Read the file with open(self.filename) as f: try: - array = np.array([[float(x) for x in line.split(self.delimiter)] for line in f]) + array = np.array( + [[float(x) for x in line.split(self.delimiter)] for line in f] + ) except ValueError: sys.exit("Error: The inputs should be numeric") self.set_freq(min(array[:, 0]), max(array[:, 0]), self.f_n) - rl_interp = scipy.interpolate.interp1d(array[:, 0], array[:, 1], fill_value="extrapolate") - im_interp = scipy.interpolate.interp1d(array[:, 0], array[:, 2], fill_value="extrapolate") + rl_interp = scipy.interpolate.interp1d( + array[:, 0], array[:, 1], fill_value="extrapolate" + ) + im_interp = scipy.interpolate.interp1d( + array[:, 0], array[:, 2], fill_value="extrapolate" + ) return rl_interp(self.freq) - 1j * im_interp(self.freq) @@ -774,17 +865,63 @@ if __name__ == "__main__": setup.run() # Testing setup setup = Rawdata( - "examples/Test.txt", 0.1, 1, 0.1, "M1", number_of_debye_poles=3, plot=True, optimizer_options={"seed": 111} + "examples/Test.txt", + 0.1, + 1, + 0.1, + "M1", + number_of_debye_poles=3, + plot=True, + optimizer_options={"seed": 111}, ) setup.run() np.random.seed(111) - setup = HavriliakNegami(1e12, 1e-3, 0.5, 1, 10, 5, 1e-6, 0.1, 1, 0, "M2", number_of_debye_poles=6, plot=True) + setup = HavriliakNegami( + 1e12, + 1e-3, + 0.5, + 1, + 10, + 5, + 1e-6, + 0.1, + 1, + 0, + "M2", + number_of_debye_poles=6, + plot=True, + ) setup.run() - setup = Jonscher(1e6, 1e-5, 50, 1, 1e5, 0.7, 0.1, 1, 0.1, "M3", number_of_debye_poles=4, plot=True) + setup = Jonscher( + 1e6, + 1e-5, + 50, + 1, + 1e5, + 0.7, + 0.1, + 1, + 0.1, + "M3", + number_of_debye_poles=4, + plot=True, + ) setup.run() f = np.array([0.5, 0.5]) material1 = [3, 25, 1e6] material2 = [3, 0, 1e3] materials = np.array([material1, material2]) - setup = Crim(1 * 1e-1, 1e-9, 0.5, f, materials, 0.1, 1, 0, "M4", number_of_debye_poles=2, plot=True) + setup = Crim( + 1 * 1e-1, + 1e-9, + 0.5, + f, + materials, + 0.1, + 1, + 0, + "M4", + number_of_debye_poles=2, + plot=True, + ) setup.run() diff --git a/toolboxes/DebyeFit/optimization.py b/toolboxes/DebyeFit/optimization.py index 0953bb6f..63e6cdb3 100644 --- a/toolboxes/DebyeFit/optimization.py +++ b/toolboxes/DebyeFit/optimization.py @@ -121,7 +121,16 @@ class PSO_DLS(Optimizer): """ def __init__( - self, swarmsize=40, maxiter=50, omega=0.9, phip=0.9, phig=0.9, minstep=1e-8, minfun=1e-8, pflag=False, seed=None + self, + swarmsize=40, + maxiter=50, + omega=0.9, + phip=0.9, + phig=0.9, + minstep=1e-8, + minfun=1e-8, + pflag=False, + seed=None, ): super(PSO_DLS, self).__init__(maxiter, seed) self.swarmsize = swarmsize @@ -159,7 +168,9 @@ class PSO_DLS(Optimizer): assert hasattr(func, "__call__"), "Invalid function handle" lb = np.array(lb) ub = np.array(ub) - assert np.all(ub > lb), "All upper-bound values must be greater than lower-bound values" + assert np.all(ub > lb), ( + "All upper-bound values must be greater than lower-bound values" + ) vhigh = np.abs(ub - lb) vlow = -vhigh @@ -226,10 +237,16 @@ class PSO_DLS(Optimizer): tmp = x[i, :].copy() stepsize = np.sqrt(np.sum((g - tmp) ** 2)) if np.abs(fg - fx) <= self.minfun: - print(f"Stopping search: Swarm best objective " f"change less than {self.minfun}") + print( + f"Stopping search: Swarm best objective " + f"change less than {self.minfun}" + ) return tmp, fx elif stepsize <= self.minstep: - print(f"Stopping search: Swarm best position " f"change less than {self.minstep}") + print( + f"Stopping search: Swarm best position " + f"change less than {self.minstep}" + ) return tmp, fx else: g = tmp.copy() @@ -471,7 +488,10 @@ def DLS(logt, rl, im, freq): # Solving the overdetermined system y=Ax x = np.abs(np.linalg.lstsq(d.imag, im, rcond=None)[0]) # x - absolute damped least-squares solution - rp, ip = np.matmul(d.real, x[np.newaxis].T).T[0], np.matmul(d.imag, x[np.newaxis].T).T[0] + rp, ip = ( + np.matmul(d.real, x[np.newaxis].T).T[0], + np.matmul(d.imag, x[np.newaxis].T).T[0], + ) cost_i = np.sum(np.abs(ip - im)) / len(im) ee = np.mean(rl - rp) ee = max(ee, 1) diff --git a/toolboxes/GPRAntennaModels/GSSI.py b/toolboxes/GPRAntennaModels/GSSI.py index e21b700a..9ce8d952 100644 --- a/toolboxes/GPRAntennaModels/GSSI.py +++ b/toolboxes/GPRAntennaModels/GSSI.py @@ -64,7 +64,9 @@ def antenna_like_GSSI_1500(x, y, z, resolution=0.001, **kwargs): patchheight = 0.016 tx = x + 0.114, y + 0.052, z + skidthickness else: - logger.exception("This antenna module can only be used with a spatial discretisation of 1mm or 2mm") + logger.exception( + "This antenna module can only be used with a spatial discretisation of 1mm or 2mm" + ) raise ValueError # If using parameters from an optimisation @@ -80,8 +82,12 @@ def antenna_like_GSSI_1500(x, y, z, resolution=0.001, **kwargs): hdpesig = kwargs["hdpesig"] sourceresistance = 195 rxres = 50 - absorber1 = gprMax.Material(er=absorber1Er, se=absorber1sig, mr=1, sm=0, id="absorber1") - absorber2 = gprMax.Material(er=absorber2Er, se=absorber2sig, mr=1, sm=0, id="absorber2") + absorber1 = gprMax.Material( + er=absorber1Er, se=absorber1sig, mr=1, sm=0, id="absorber1" + ) + absorber2 = gprMax.Material( + er=absorber2Er, se=absorber2sig, mr=1, sm=0, id="absorber2" + ) pcb = gprMax.Material(er=pcbEr, se=pcbsig, mr=1, sm=0, id="pcb") hdpe = gprMax.Material(er=hdpeEr, se=hdpesig, mr=1, sm=0, id="hdpe") scene_objects.extend((absorber1, absorber2, pcb, hdpe)) @@ -95,19 +101,27 @@ def antenna_like_GSSI_1500(x, y, z, resolution=0.001, **kwargs): if optstate == "WarrenThesis": # Original optimised values from http://hdl.handle.net/1842/4074 excitationfreq = 1.71e9 - sourceresistance = 230 # Correction for old (< 123) GprMax3D bug (optimised to 4) + sourceresistance = ( + 230 # Correction for old (< 123) GprMax3D bug (optimised to 4) + ) rxres = 925 # Resistance at Rx bowtie absorber1 = gprMax.Material(er=1.58, se=0.428, mr=1, sm=0, id="absorber1") - absorber2 = gprMax.Material(er=3, se=0, mr=1, sm=0, id="absorber2") # Foam modelled as PCB material + absorber2 = gprMax.Material( + er=3, se=0, mr=1, sm=0, id="absorber2" + ) # Foam modelled as PCB material pcb = gprMax.Material(er=3, se=0, mr=1, sm=0, id="pcb") hdpe = gprMax.Material(er=2.35, se=0, mr=1, sm=0, id="hdpe") - rxres = gprMax.Material(er=3, se=(1 / rxres) * (dy / (dx * dz)), mr=1, sm=0, id="rxres") + rxres = gprMax.Material( + er=3, se=(1 / rxres) * (dy / (dx * dz)), mr=1, sm=0, id="rxres" + ) scene_objects.extend((absorber1, absorber2, pcb, hdpe, rxres)) elif optstate == "DebyeAbsorber": # Same values as WarrenThesis but uses dispersive absorber properties for Eccosorb LS22 excitationfreq = 1.71e9 - sourceresistance = 230 # Correction for old (< 123) GprMax3D bug (optimised to 4) + sourceresistance = ( + 230 # Correction for old (< 123) GprMax3D bug (optimised to 4) + ) rxres = 925 # Resistance at Rx bowtie absorber1 = gprMax.Material(er=1, se=0, mr=1, sm=0, id="absorber1") # Eccosorb LS22 3-pole Debye model (https://bitbucket.org/uoyaeg/aegboxts/wiki/Home) @@ -117,11 +131,17 @@ def antenna_like_GSSI_1500(x, y, z, resolution=0.001, **kwargs): tau=[1.00723e-11, 1.55686e-10, 3.44129e-10], material_ids=["absorber1"], ) - absorber2 = gprMax.Material(er=3, se=0, mr=1, sm=0, id="absorber2") # Foam modelled as PCB material + absorber2 = gprMax.Material( + er=3, se=0, mr=1, sm=0, id="absorber2" + ) # Foam modelled as PCB material pcb = gprMax.Material(er=3, se=0, mr=1, sm=0, id="pcb") hdpe = gprMax.Material(er=2.35, se=0, mr=1, sm=0, id="hdpe") - rxres = gprMax.Material(er=3, se=(1 / rxres) * (dy / (dx * dz)), mr=1, sm=0, id="rxres") - scene_objects.extend((absorber1, absorber1_disp, absorber2, pcb, hdpe, rxres)) + rxres = gprMax.Material( + er=3, se=(1 / rxres) * (dy / (dx * dz)), mr=1, sm=0, id="rxres" + ) + scene_objects.extend( + (absorber1, absorber1_disp, absorber2, pcb, hdpe, rxres) + ) elif optstate == "GiannakisPaper": # Further optimised values from https://doi.org/10.1109/TGRS.2018.2869027 @@ -152,13 +172,21 @@ def antenna_like_GSSI_1500(x, y, z, resolution=0.001, **kwargs): # Metallic enclosure b3 = gprMax.Box( p1=(x + 0.025, y + casethickness, z + skidthickness), - p2=(x + casesize[0] - 0.025, y + casesize[1] - casethickness, z + skidthickness + 0.027), + p2=( + x + casesize[0] - 0.025, + y + casesize[1] - casethickness, + z + skidthickness + 0.027, + ), material_id="pec", ) # Absorber material (absorber1) and foam (absorber2) around edge of absorber b4 = gprMax.Box( - p1=(x + 0.025 + shieldthickness, y + casethickness + shieldthickness, z + skidthickness), + p1=( + x + 0.025 + shieldthickness, + y + casethickness + shieldthickness, + z + skidthickness, + ), p2=( x + 0.025 + shieldthickness + 0.057, y + casesize[1] - casethickness - shieldthickness, @@ -299,12 +327,24 @@ def antenna_like_GSSI_1500(x, y, z, resolution=0.001, **kwargs): scene_objects.extend((p9, p10)) # Edges that represent wire between bowtie halves in 1mm model - e1 = gprMax.Edge(p1=(tx[0] - 0.059, tx[1] - dy, tx[2]), p2=(tx[0] - 0.059, tx[1], tx[2]), material_id="pec") - e2 = gprMax.Edge( - p1=(tx[0] - 0.059, tx[1] + dy, tx[2]), p2=(tx[0] - 0.059, tx[1] + 0.002, tx[2]), material_id="pec" + e1 = gprMax.Edge( + p1=(tx[0] - 0.059, tx[1] - dy, tx[2]), + p2=(tx[0] - 0.059, tx[1], tx[2]), + material_id="pec", + ) + e2 = gprMax.Edge( + p1=(tx[0] - 0.059, tx[1] + dy, tx[2]), + p2=(tx[0] - 0.059, tx[1] + 0.002, tx[2]), + material_id="pec", + ) + e3 = gprMax.Edge( + p1=(tx[0], tx[1] - dy, tx[2]), p2=(tx[0], tx[1], tx[2]), material_id="pec" + ) + e4 = gprMax.Edge( + p1=(tx[0], tx[1] + dz, tx[2]), + p2=(tx[0], tx[1] + 0.002, tx[2]), + material_id="pec", ) - e3 = gprMax.Edge(p1=(tx[0], tx[1] - dy, tx[2]), p2=(tx[0], tx[1], tx[2]), material_id="pec") - e4 = gprMax.Edge(p1=(tx[0], tx[1] + dz, tx[2]), p2=(tx[0], tx[1] + 0.002, tx[2]), material_id="pec") scene_objects.extend((e1, e2, e3, e4)) elif resolution == 0.002: @@ -359,13 +399,21 @@ def antenna_like_GSSI_1500(x, y, z, resolution=0.001, **kwargs): scene_objects.extend((p11, p12)) # Skid - b10 = gprMax.Box(p1=(x, y, z), p2=(x + casesize[0], y + casesize[1], z + skidthickness), material_id="hdpe") + b10 = gprMax.Box( + p1=(x, y, z), + p2=(x + casesize[0], y + casesize[1], z + skidthickness), + material_id="hdpe", + ) scene_objects.append(b10) # Geometry views gv1 = gprMax.GeometryView( p1=(x - dx, y - dy, z - dz), - p2=(x + casesize[0] + dx, y + casesize[1] + dy, z + skidthickness + casesize[2] + dz), + p2=( + x + casesize[0] + dx, + y + casesize[1] + dy, + z + skidthickness + casesize[2] + dz, + ), dl=(dx, dy, dz), filename="antenna_like_GSSI_1500", output_type="n", @@ -382,19 +430,29 @@ def antenna_like_GSSI_1500(x, y, z, resolution=0.001, **kwargs): # Excitation if optstate == "WarrenThesis" or optstate == "DebyeAbsorber": # Gaussian pulse - w1 = gprMax.Waveform(wave_type="gaussian", amp=1, freq=excitationfreq, id="my_gaussian") + w1 = gprMax.Waveform( + wave_type="gaussian", amp=1, freq=excitationfreq, id="my_gaussian" + ) vs1 = gprMax.VoltageSource( - polarisation="y", p1=(tx[0], tx[1], tx[2]), resistance=sourceresistance, waveform_id="my_gaussian" + polarisation="y", + p1=(tx[0], tx[1], tx[2]), + resistance=sourceresistance, + waveform_id="my_gaussian", ) scene_objects.extend((w1, vs1)) elif optstate == "GiannakisPaper": # Optimised custom pulse exc1 = gprMax.ExcitationFile( - filepath="toolboxes/GPRAntennaModels/GSSI_1500MHz_pulse.txt", kind="linear", fill_value="extrapolate" + filepath="toolboxes/GPRAntennaModels/GSSI_1500MHz_pulse.txt", + kind="linear", + fill_value="extrapolate", ) vs1 = gprMax.VoltageSource( - polarisation="y", p1=(tx[0], tx[1], tx[2]), resistance=sourceresistance, waveform_id="my_pulse" + polarisation="y", + p1=(tx[0], tx[1], tx[2]), + resistance=sourceresistance, + waveform_id="my_pulse", ) scene_objects.extend((exc1, vs1)) @@ -402,7 +460,9 @@ def antenna_like_GSSI_1500(x, y, z, resolution=0.001, **kwargs): if resolution == 0.001: if optstate == "WarrenThesis" or optstate == "DebyeAbsorber": e1 = gprMax.Edge( - p1=(tx[0] - 0.059, tx[1], tx[2]), p2=(tx[0] - 0.059, tx[1] + dy, tx[2]), material_id="rxres" + p1=(tx[0] - 0.059, tx[1], tx[2]), + p2=(tx[0] - 0.059, tx[1] + dy, tx[2]), + material_id="rxres", ) scene_objects.append(e1) r1 = gprMax.Rx(p1=(tx[0] - 0.059, tx[1], tx[2]), id="rxbowtie", outputs=["Ey"]) @@ -411,7 +471,9 @@ def antenna_like_GSSI_1500(x, y, z, resolution=0.001, **kwargs): elif resolution == 0.002: if optstate == "WarrenThesis" or optstate == "DebyeAbsorber": e1 = gprMax.Edge( - p1=(tx[0] - 0.060, tx[1], tx[2]), p2=(tx[0] - 0.060, tx[1] + dy, tx[2]), material_id="rxres" + p1=(tx[0] - 0.060, tx[1], tx[2]), + p2=(tx[0] - 0.060, tx[1] + dy, tx[2]), + material_id="rxres", ) scene_objects.append(e1) r1 = gprMax.Rx(p1=(tx[0] - 0.060, tx[1], tx[2]), id="rxbowtie", outputs=["Ey"]) @@ -492,7 +554,11 @@ def antenna_like_GSSI_400(x, y, z, resolution=0.002, **kwargs): dz = 0.002 foamsurroundthickness = 0.002 metalboxheight = 0.088 - tx = x + 0.01 + 0.004 + 0.056, y + casethickness + 0.005 + 0.143 - 0.002, z + skidthickness - 0.002 + tx = ( + x + 0.01 + 0.004 + 0.056, + y + casethickness + 0.005 + 0.143 - 0.002, + z + skidthickness - 0.002, + ) # Material definitions absorber = gprMax.Material(er=absorberEr, se=absorbersig, mr=1, sm=0, id="absorber") @@ -510,17 +576,28 @@ def antenna_like_GSSI_400(x, y, z, resolution=0.002, **kwargs): ) b2 = gprMax.Box( p1=(x + casethickness, y + casethickness, z + skidthickness - 0.002), - p2=(x + casesize[0] - casethickness, y + casesize[1] - casethickness, z + casesize[2] - casethickness), + p2=( + x + casesize[0] - casethickness, + y + casesize[1] - casethickness, + z + casesize[2] - casethickness, + ), material_id="free_space", ) # Metallic enclosure b3 = gprMax.Box( - p1=(x + casethickness, y + casethickness, z + skidthickness + (metalmiddleplateheight - metalboxheight)), + p1=( + x + casethickness, + y + casethickness, + z + skidthickness + (metalmiddleplateheight - metalboxheight), + ), p2=( x + casesize[0] - casethickness, y + casesize[1] - casethickness, - z + skidthickness + (metalmiddleplateheight - metalboxheight) + metalboxheight, + z + + skidthickness + + (metalmiddleplateheight - metalboxheight) + + metalboxheight, ), material_id="pec", ) @@ -552,7 +629,11 @@ def antenna_like_GSSI_400(x, y, z, resolution=0.002, **kwargs): # PCB b6 = gprMax.Box( - p1=(x + 0.01 + 0.005 + 0.017, y + casethickness + 0.005 + 0.021, z + skidthickness - 0.002), + p1=( + x + 0.01 + 0.005 + 0.017, + y + casethickness + 0.005 + 0.021, + z + skidthickness - 0.002, + ), p2=( x + 0.01 + 0.005 + 0.033 + bowtiebase, y + casethickness + 0.006 + 0.202 + patchheight, @@ -561,7 +642,11 @@ def antenna_like_GSSI_400(x, y, z, resolution=0.002, **kwargs): material_id="pcb", ) b7 = gprMax.Box( - p1=(x + 0.01 + 0.005 + 0.179, y + casethickness + 0.005 + 0.021, z + skidthickness - 0.002), + p1=( + x + 0.01 + 0.005 + 0.179, + y + casethickness + 0.005 + 0.021, + z + skidthickness - 0.002, + ), p2=( x + 0.01 + 0.005 + 0.195 + bowtiebase, y + casethickness + 0.006 + 0.202 + patchheight, @@ -581,18 +666,29 @@ def antenna_like_GSSI_400(x, y, z, resolution=0.002, **kwargs): ) b9 = gprMax.Box( p1=(x + casethickness, y + casethickness, z + skidthickness - 0.002), - p2=(x + casesize[0] - casethickness, y + casesize[1] - casethickness, z + casesize[2] - casethickness), + p2=( + x + casesize[0] - casethickness, + y + casesize[1] - casethickness, + z + casesize[2] - casethickness, + ), material_id="free_space", averaging="n", ) # Metallic enclosure b10 = gprMax.Box( - p1=(x + casethickness, y + casethickness, z + skidthickness + (metalmiddleplateheight - metalboxheight)), + p1=( + x + casethickness, + y + casethickness, + z + skidthickness + (metalmiddleplateheight - metalboxheight), + ), p2=( x + casesize[0] - casethickness, y + casesize[1] - casethickness, - z + skidthickness + (metalmiddleplateheight - metalboxheight) + metalboxheight, + z + + skidthickness + + (metalmiddleplateheight - metalboxheight) + + metalboxheight, ), material_id="pec", ) @@ -626,7 +722,11 @@ def antenna_like_GSSI_400(x, y, z, resolution=0.002, **kwargs): # PCB b13 = gprMax.Box( - p1=(x + 0.01 + 0.005 + 0.017, y + casethickness + 0.005 + 0.021, z + skidthickness - 0.002), + p1=( + x + 0.01 + 0.005 + 0.017, + y + casethickness + 0.005 + 0.021, + z + skidthickness - 0.002, + ), p2=( x + 0.01 + 0.005 + 0.033 + bowtiebase, y + casethickness + 0.006 + 0.202 + patchheight, @@ -636,7 +736,11 @@ def antenna_like_GSSI_400(x, y, z, resolution=0.002, **kwargs): averaging="n", ) b14 = gprMax.Box( - p1=(x + 0.01 + 0.005 + 0.179, y + casethickness + 0.005 + 0.021, z + skidthickness), + p1=( + x + 0.01 + 0.005 + 0.179, + y + casethickness + 0.005 + 0.021, + z + skidthickness, + ), p2=( x + 0.01 + 0.005 + 0.195 + bowtiebase, y + casethickness + 0.006 + 0.202 + patchheight, @@ -652,7 +756,11 @@ def antenna_like_GSSI_400(x, y, z, resolution=0.002, **kwargs): # "left" side # extension plates p1 = gprMax.Plate( - p1=(x + 0.01 + 0.005 + 0.025, y + casethickness + 0.005 + 0.021, z + skidthickness - 0.002), + p1=( + x + 0.01 + 0.005 + 0.025, + y + casethickness + 0.005 + 0.021, + z + skidthickness - 0.002, + ), p2=( x + 0.01 + 0.005 + 0.025 + bowtiebase, y + casethickness + 0.005 + 0.021 + patchheight, @@ -661,7 +769,11 @@ def antenna_like_GSSI_400(x, y, z, resolution=0.002, **kwargs): material_id="pec", ) p2 = gprMax.Plate( - p1=(x + 0.01 + 0.005 + 0.025, y + casethickness + 0.005 + 0.203, z + skidthickness - 0.002), + p1=( + x + 0.01 + 0.005 + 0.025, + y + casethickness + 0.005 + 0.203, + z + skidthickness - 0.002, + ), p2=( x + 0.01 + 0.005 + 0.025 + bowtiebase, y + casethickness + 0.005 + 0.203 + patchheight, @@ -671,8 +783,16 @@ def antenna_like_GSSI_400(x, y, z, resolution=0.002, **kwargs): ) # triangles t1 = gprMax.Triangle( - p1=(x + 0.01 + 0.005 + 0.025, y + casethickness + 0.005 + 0.081, z + skidthickness - 0.002), - p2=(x + 0.01 + 0.005 + 0.025 + bowtiebase, y + casethickness + 0.005 + 0.081, z + skidthickness - 0.002), + p1=( + x + 0.01 + 0.005 + 0.025, + y + casethickness + 0.005 + 0.081, + z + skidthickness - 0.002, + ), + p2=( + x + 0.01 + 0.005 + 0.025 + bowtiebase, + y + casethickness + 0.005 + 0.081, + z + skidthickness - 0.002, + ), p3=( x + 0.01 + 0.005 + 0.025 + (bowtiebase / 2), y + casethickness + 0.005 + 0.081 + bowtieheight, @@ -682,8 +802,16 @@ def antenna_like_GSSI_400(x, y, z, resolution=0.002, **kwargs): material_id="pec", ) t2 = gprMax.Triangle( - p1=(x + 0.01 + 0.005 + 0.025, y + casethickness + 0.005 + 0.203, z + skidthickness - 0.002), - p2=(x + 0.01 + 0.005 + 0.025 + bowtiebase, y + casethickness + 0.005 + 0.203, z + skidthickness - 0.002), + p1=( + x + 0.01 + 0.005 + 0.025, + y + casethickness + 0.005 + 0.203, + z + skidthickness - 0.002, + ), + p2=( + x + 0.01 + 0.005 + 0.025 + bowtiebase, + y + casethickness + 0.005 + 0.203, + z + skidthickness - 0.002, + ), p3=( x + 0.01 + 0.005 + 0.025 + (bowtiebase / 2), y + casethickness + 0.005 + 0.203 - bowtieheight, @@ -694,7 +822,11 @@ def antenna_like_GSSI_400(x, y, z, resolution=0.002, **kwargs): ) # "right" side p3 = gprMax.Plate( - p1=(x + 0.01 + 0.005 + 0.187, y + casethickness + 0.005 + 0.021, z + skidthickness - 0.002), + p1=( + x + 0.01 + 0.005 + 0.187, + y + casethickness + 0.005 + 0.021, + z + skidthickness - 0.002, + ), p2=( x + 0.01 + 0.005 + 0.187 + bowtiebase, y + casethickness + 0.005 + 0.021 + patchheight, @@ -703,7 +835,11 @@ def antenna_like_GSSI_400(x, y, z, resolution=0.002, **kwargs): material_id="pec", ) p4 = gprMax.Plate( - p1=(x + 0.01 + 0.005 + 0.187, y + casethickness + 0.005 + 0.203, z + skidthickness - 0.002), + p1=( + x + 0.01 + 0.005 + 0.187, + y + casethickness + 0.005 + 0.203, + z + skidthickness - 0.002, + ), p2=( x + 0.01 + 0.005 + 0.187 + bowtiebase, y + casethickness + 0.005 + 0.203 + patchheight, @@ -713,8 +849,16 @@ def antenna_like_GSSI_400(x, y, z, resolution=0.002, **kwargs): ) # triangles t3 = gprMax.Triangle( - p1=(x + 0.01 + 0.005 + 0.187, y + casethickness + 0.005 + 0.081, z + skidthickness - 0.002), - p2=(x + 0.01 + 0.005 + 0.187 + bowtiebase, y + casethickness + 0.005 + 0.081, z + skidthickness - 0.002), + p1=( + x + 0.01 + 0.005 + 0.187, + y + casethickness + 0.005 + 0.081, + z + skidthickness - 0.002, + ), + p2=( + x + 0.01 + 0.005 + 0.187 + bowtiebase, + y + casethickness + 0.005 + 0.081, + z + skidthickness - 0.002, + ), p3=( x + 0.01 + 0.005 + 0.187 + (bowtiebase / 2), y + casethickness + 0.005 + 0.081 + bowtieheight, @@ -724,8 +868,16 @@ def antenna_like_GSSI_400(x, y, z, resolution=0.002, **kwargs): material_id="pec", ) t4 = gprMax.Triangle( - p1=(x + 0.01 + 0.005 + 0.187, y + casethickness + 0.005 + 0.203, z + skidthickness - 0.002), - p2=(x + 0.01 + 0.005 + 0.187 + bowtiebase, y + casethickness + 0.005 + 0.203, z + skidthickness - 0.002), + p1=( + x + 0.01 + 0.005 + 0.187, + y + casethickness + 0.005 + 0.203, + z + skidthickness - 0.002, + ), + p2=( + x + 0.01 + 0.005 + 0.187 + bowtiebase, + y + casethickness + 0.005 + 0.203, + z + skidthickness - 0.002, + ), p3=( x + 0.01 + 0.005 + 0.187 + (bowtiebase / 2), y + casethickness + 0.005 + 0.203 - bowtieheight, @@ -736,12 +888,24 @@ def antenna_like_GSSI_400(x, y, z, resolution=0.002, **kwargs): ) # Edges that represent wire between bowtie halves in 2mm model - e1 = gprMax.Edge(p1=(tx[0] + 0.162, tx[1] - dy, tx[2]), p2=(tx[0] + 0.162, tx[1], tx[2]), material_id="pec") - e2 = gprMax.Edge( - p1=(tx[0] + 0.162, tx[1] + dy, tx[2]), p2=(tx[0] + 0.162, tx[1] + 2 * dy, tx[2]), material_id="pec" + e1 = gprMax.Edge( + p1=(tx[0] + 0.162, tx[1] - dy, tx[2]), + p2=(tx[0] + 0.162, tx[1], tx[2]), + material_id="pec", + ) + e2 = gprMax.Edge( + p1=(tx[0] + 0.162, tx[1] + dy, tx[2]), + p2=(tx[0] + 0.162, tx[1] + 2 * dy, tx[2]), + material_id="pec", + ) + e3 = gprMax.Edge( + p1=(tx[0], tx[1] - dy, tx[2]), p2=(tx[0], tx[1], tx[2]), material_id="pec" + ) + e4 = gprMax.Edge( + p1=(tx[0], tx[1] + dy, tx[2]), + p2=(tx[0], tx[1] + 2 * dy, tx[2]), + material_id="pec", ) - e3 = gprMax.Edge(p1=(tx[0], tx[1] - dy, tx[2]), p2=(tx[0], tx[1], tx[2]), material_id="pec") - e4 = gprMax.Edge(p1=(tx[0], tx[1] + dy, tx[2]), p2=(tx[0], tx[1] + 2 * dy, tx[2]), material_id="pec") scene_objects.extend((p1, p2, t1, t2, p3, p4, t3, t4, e1, e2, e3, e4)) # Metallic plate extension @@ -758,7 +922,9 @@ def antenna_like_GSSI_400(x, y, z, resolution=0.002, **kwargs): # Skid if smooth_dec == "yes": b16 = gprMax.Box( - p1=(x, y, z), p2=(x + casesize[0], y + casesize[1], z + skidthickness - 0.002), material_id="hdpe" + p1=(x, y, z), + p2=(x + casesize[0], y + casesize[1], z + skidthickness - 0.002), + material_id="hdpe", ) elif smooth_dec == "no": b16 = gprMax.Box( @@ -771,31 +937,48 @@ def antenna_like_GSSI_400(x, y, z, resolution=0.002, **kwargs): # Source if src_type == "voltage_source": - w1 = gprMax.Waveform(wave_type="gaussian", amp=1, freq=excitationfreq, id="my_gaussian") + w1 = gprMax.Waveform( + wave_type="gaussian", amp=1, freq=excitationfreq, id="my_gaussian" + ) vs1 = gprMax.VoltageSource( - polarisation="y", p1=(tx[0], tx[1], tx[2]), resistance=sourceresistance, waveform_id="my_gaussian" + polarisation="y", + p1=(tx[0], tx[1], tx[2]), + resistance=sourceresistance, + waveform_id="my_gaussian", ) scene_objects.extend((w1, vs1)) elif src_type == "transmission_line": - w1 = gprMax.Waveform(wave_type="gaussian", amp=1, freq=excitationfreq, id="my_gaussian") + w1 = gprMax.Waveform( + wave_type="gaussian", amp=1, freq=excitationfreq, id="my_gaussian" + ) tl1 = gprMax.TransmissionLine( - polarisation="y", p1=(tx[0], tx[1], tx[2]), resistance=sourceresistance, waveform_id="my_gaussian" + polarisation="y", + p1=(tx[0], tx[1], tx[2]), + resistance=sourceresistance, + waveform_id="my_gaussian", ) scene_objects.extend((w1, tl1)) else: # Optimised custom pulse exc1 = gprMax.ExcitationFile( - filepath="toolboxes/GPRAntennaModels/GSSI_400MHz_pulse.txt", kind="linear", fill_value="extrapolate" + filepath="toolboxes/GPRAntennaModels/GSSI_400MHz_pulse.txt", + kind="linear", + fill_value="extrapolate", ) vs1 = gprMax.VoltageSource( - polarisation="y", p1=(tx[0], tx[1], tx[2]), resistance=sourceresistance, waveform_id="my_pulse" + polarisation="y", + p1=(tx[0], tx[1], tx[2]), + resistance=sourceresistance, + waveform_id="my_pulse", ) scene_objects.extend((exc1, vs1)) # Receiver if src_type == "transmission_line": # Zero waveform to use with transmission line at receiver output - w2 = gprMax.Waveform(wave_type="gaussian", amp=0, freq=excitationfreq, id="my_zero_wave") + w2 = gprMax.Waveform( + wave_type="gaussian", amp=0, freq=excitationfreq, id="my_zero_wave" + ) tl2 = gprMax.TransmissionLine( polarisation="y", p1=(tx[0] + 0.162, tx[1], tx[2]), @@ -810,7 +993,11 @@ def antenna_like_GSSI_400(x, y, z, resolution=0.002, **kwargs): # Geometry views gv1 = gprMax.GeometryView( p1=(x - dx, y - dy, z - dz), - p2=(x + casesize[0] + dx, y + casesize[1] + dy, z + skidthickness + casesize[2] + dz), + p2=( + x + casesize[0] + dx, + y + casesize[1] + dy, + z + skidthickness + casesize[2] + dz, + ), dl=(dx, dy, dz), filename="antenna_like_GSSI_400", output_type="n", diff --git a/toolboxes/GPRAntennaModels/MALA.py b/toolboxes/GPRAntennaModels/MALA.py index cbee5f06..aaaea366 100644 --- a/toolboxes/GPRAntennaModels/MALA.py +++ b/toolboxes/GPRAntennaModels/MALA.py @@ -84,20 +84,30 @@ def antenna_like_MALA_1200(x, y, z, resolution=0.001, **kwargs): bowtieheight = 0.024 tx = x + 0.062, y + 0.052, z + skidthickness else: - logger.exception("This antenna module can only be used with a spatial resolution of 1mm or 2mm") + logger.exception( + "This antenna module can only be used with a spatial resolution of 1mm or 2mm" + ) raise ValueError # SMD resistors - 3 on each Tx & Rx bowtie arm txres = 470 # Ohms txrescellupper = txres / 3 # Resistor over 3 cells - txsigupper = ((1 / txrescellupper) * (dy / (dx * dz))) / 2 # Divide by number of parallel edges per resistor + txsigupper = ( + (1 / txrescellupper) * (dy / (dx * dz)) + ) / 2 # Divide by number of parallel edges per resistor txrescelllower = txres / 4 # Resistor over 4 cells - txsiglower = ((1 / txrescelllower) * (dy / (dx * dz))) / 2 # Divide by number of parallel edges per resistor + txsiglower = ( + (1 / txrescelllower) * (dy / (dx * dz)) + ) / 2 # Divide by number of parallel edges per resistor rxres = 150 # Ohms rxrescellupper = rxres / 3 # Resistor over 3 cells - rxsigupper = ((1 / rxrescellupper) * (dy / (dx * dz))) / 2 # Divide by number of parallel edges per resistor + rxsigupper = ( + (1 / rxrescellupper) * (dy / (dx * dz)) + ) / 2 # Divide by number of parallel edges per resistor rxrescelllower = rxres / 4 # Resistor over 4 cells - rxsiglower = ((1 / rxrescelllower) * (dy / (dx * dz))) / 2 # Divide by number of parallel edges per resistor + rxsiglower = ( + (1 / rxrescelllower) * (dy / (dx * dz)) + ) / 2 # Divide by number of parallel edges per resistor # Material definitions absorber = gprMax.Material(er=absorberEr, se=absorbersig, mr=1, sm=0, id="absorber") @@ -108,7 +118,18 @@ def antenna_like_MALA_1200(x, y, z, resolution=0.001, **kwargs): txresupper = gprMax.Material(er=3, se=txsigupper, mr=1, sm=0, id="txresupper") rxreslower = gprMax.Material(er=3, se=rxsiglower, mr=1, sm=0, id="rxreslower") rxresupper = gprMax.Material(er=3, se=rxsigupper, mr=1, sm=0, id="rxresupper") - scene_objects.extend((absorber, pcb, hdpe, polypropylene, txreslower, txresupper, rxreslower, rxresupper)) + scene_objects.extend( + ( + absorber, + pcb, + hdpe, + polypropylene, + txreslower, + txresupper, + rxreslower, + rxresupper, + ) + ) # Antenna geometry # Shield - metallic enclosure @@ -119,19 +140,31 @@ def antenna_like_MALA_1200(x, y, z, resolution=0.001, **kwargs): ) b2 = gprMax.Box( p1=(x + 0.020, y + casethickness, z + skidthickness), - p2=(x + 0.100, y + casesize[1] - casethickness, z + skidthickness + casethickness), + p2=( + x + 0.100, + y + casesize[1] - casethickness, + z + skidthickness + casethickness, + ), material_id="free_space", ) b3 = gprMax.Box( p1=(x + 0.100, y + casethickness, z + skidthickness), - p2=(x + casesize[0] - casethickness, y + casesize[1] - casethickness, z + skidthickness + casethickness), + p2=( + x + casesize[0] - casethickness, + y + casesize[1] - casethickness, + z + skidthickness + casethickness, + ), material_id="free_space", ) # Absorber material b4 = gprMax.Box( p1=(x + 0.020, y + casethickness, z + skidthickness), - p2=(x + 0.100, y + casesize[1] - casethickness, z + skidthickness + casesize[2] - casethickness), + p2=( + x + 0.100, + y + casesize[1] - casethickness, + z + skidthickness + casesize[2] - casethickness, + ), material_id="absorber", ) b5 = gprMax.Box( @@ -148,7 +181,11 @@ def antenna_like_MALA_1200(x, y, z, resolution=0.001, **kwargs): # Shield - cylindrical sections c1 = gprMax.Cylinder( p1=(x + 0.055, y + casesize[1] - 0.008, z + skidthickness), - p2=(x + 0.055, y + casesize[1] - 0.008, z + skidthickness + casesize[2] - casethickness), + p2=( + x + 0.055, + y + casesize[1] - 0.008, + z + skidthickness + casesize[2] - casethickness, + ), r=0.008, material_id="pec", ) @@ -160,7 +197,11 @@ def antenna_like_MALA_1200(x, y, z, resolution=0.001, **kwargs): ) c3 = gprMax.Cylinder( p1=(x + 0.147, y + casesize[1] - 0.008, z + skidthickness), - p2=(x + 0.147, y + casesize[1] - 0.008, z + skidthickness + casesize[2] - casethickness), + p2=( + x + 0.147, + y + casesize[1] - 0.008, + z + skidthickness + casesize[2] - casethickness, + ), r=0.008, material_id="pec", ) @@ -172,7 +213,11 @@ def antenna_like_MALA_1200(x, y, z, resolution=0.001, **kwargs): ) c5 = gprMax.Cylinder( p1=(x + 0.055, y + casesize[1] - 0.008, z + skidthickness), - p2=(x + 0.055, y + casesize[1] - 0.008, z + skidthickness + casesize[2] - casethickness), + p2=( + x + 0.055, + y + casesize[1] - 0.008, + z + skidthickness + casesize[2] - casethickness, + ), r=0.007, material_id="free_space", ) @@ -184,7 +229,11 @@ def antenna_like_MALA_1200(x, y, z, resolution=0.001, **kwargs): ) c7 = gprMax.Cylinder( p1=(x + 0.147, y + casesize[1] - 0.008, z + skidthickness), - p2=(x + 0.147, y + casesize[1] - 0.008, z + skidthickness + casesize[2] - casethickness), + p2=( + x + 0.147, + y + casesize[1] - 0.008, + z + skidthickness + casesize[2] - casethickness, + ), r=0.007, material_id="free_space", ) @@ -196,7 +245,11 @@ def antenna_like_MALA_1200(x, y, z, resolution=0.001, **kwargs): ) b6 = gprMax.Box( p1=(x + 0.054, y + casesize[1] - 0.016, z + skidthickness), - p2=(x + 0.056, y + casesize[1] - 0.014, z + skidthickness + casesize[2] - casethickness), + p2=( + x + 0.056, + y + casesize[1] - 0.014, + z + skidthickness + casesize[2] - casethickness, + ), material_id="free_space", ) b7 = gprMax.Box( @@ -206,7 +259,11 @@ def antenna_like_MALA_1200(x, y, z, resolution=0.001, **kwargs): ) b8 = gprMax.Box( p1=(x + 0.146, y + casesize[1] - 0.016, z + skidthickness), - p2=(x + 0.148, y + casesize[1] - 0.014, z + skidthickness + casesize[2] - casethickness), + p2=( + x + 0.148, + y + casesize[1] - 0.014, + z + skidthickness + casesize[2] - casethickness, + ), material_id="free_space", ) b9 = gprMax.Box( @@ -219,18 +276,30 @@ def antenna_like_MALA_1200(x, y, z, resolution=0.001, **kwargs): # PCB b10 = gprMax.Box( p1=(x + 0.020, y + 0.018, z + skidthickness), - p2=(x + casesize[0] - casethickness, y + casesize[1] - 0.018, z + skidthickness + pcbthickness), + p2=( + x + casesize[0] - casethickness, + y + casesize[1] - 0.018, + z + skidthickness + pcbthickness, + ), material_id="pcb", ) # Shield - Tx & Rx cavities b11 = gprMax.Box( p1=(x + 0.032, y + 0.022, z + skidthickness), - p2=(x + 0.032 + cavitysize[0], y + 0.022 + cavitysize[1], z + skidthickness + cavitysize[2]), + p2=( + x + 0.032 + cavitysize[0], + y + 0.022 + cavitysize[1], + z + skidthickness + cavitysize[2], + ), material_id="pec", ) b12 = gprMax.Box( - p1=(x + 0.032 + cavitythickness, y + 0.022 + cavitythickness, z + skidthickness), + p1=( + x + 0.032 + cavitythickness, + y + 0.022 + cavitythickness, + z + skidthickness, + ), p2=( x + 0.032 + cavitysize[0] - cavitythickness, y + 0.022 + cavitysize[1] - cavitythickness, @@ -240,11 +309,19 @@ def antenna_like_MALA_1200(x, y, z, resolution=0.001, **kwargs): ) b13 = gprMax.Box( p1=(x + 0.108, y + 0.022, z + skidthickness), - p2=(x + 0.108 + cavitysize[0], y + 0.022 + cavitysize[1], z + skidthickness + cavitysize[2]), + p2=( + x + 0.108 + cavitysize[0], + y + 0.022 + cavitysize[1], + z + skidthickness + cavitysize[2], + ), material_id="pec", ) b14 = gprMax.Box( - p1=(x + 0.108 + cavitythickness, y + 0.022 + cavitythickness, z + skidthickness), + p1=( + x + 0.108 + cavitythickness, + y + 0.022 + cavitythickness, + z + skidthickness, + ), p2=( x + 0.108 + cavitysize[0] - cavitythickness, y + 0.022 + cavitysize[1] - cavitythickness, @@ -264,14 +341,22 @@ def antenna_like_MALA_1200(x, y, z, resolution=0.001, **kwargs): material_id="pec", ) b16 = gprMax.Box( - p1=(x + 0.032 + cavitysize[0], y + 0.022, z + skidthickness + cavitysize[2] - casethickness), + p1=( + x + 0.032 + cavitysize[0], + y + 0.022, + z + skidthickness + cavitysize[2] - casethickness, + ), p2=(x + 0.108, y + 0.022 + 0.006, z + skidthickness + cavitysize[2]), material_id="pec", ) # PCB - replace bits chopped by TX & Rx cavities b17 = gprMax.Box( - p1=(x + 0.032 + cavitythickness, y + 0.022 + cavitythickness, z + skidthickness), + p1=( + x + 0.032 + cavitythickness, + y + 0.022 + cavitythickness, + z + skidthickness, + ), p2=( x + 0.032 + cavitysize[0] - cavitythickness, y + 0.022 + cavitysize[1] - cavitythickness, @@ -280,7 +365,11 @@ def antenna_like_MALA_1200(x, y, z, resolution=0.001, **kwargs): material_id="pcb", ) b18 = gprMax.Box( - p1=(x + 0.108 + cavitythickness, y + 0.022 + cavitythickness, z + skidthickness), + p1=( + x + 0.108 + cavitythickness, + y + 0.022 + cavitythickness, + z + skidthickness, + ), p2=( x + 0.108 + cavitysize[0] - cavitythickness, y + 0.022 + cavitysize[1] - cavitythickness, @@ -300,7 +389,11 @@ def antenna_like_MALA_1200(x, y, z, resolution=0.001, **kwargs): thickness=0, material_id="pec", ) - e1 = gprMax.Edge(p1=(tx[0], tx[1] - 0.001, tx[2]), p2=(tx[0], tx[1], tx[2]), material_id="pec") + e1 = gprMax.Edge( + p1=(tx[0], tx[1] - 0.001, tx[2]), + p2=(tx[0], tx[1], tx[2]), + material_id="pec", + ) t2 = gprMax.Triangle( p1=(tx[0], tx[1] + 0.002, tx[2]), p2=(tx[0] - 0.026, tx[1] + bowtieheight + 0.002, tx[2]), @@ -308,7 +401,11 @@ def antenna_like_MALA_1200(x, y, z, resolution=0.001, **kwargs): thickness=0, material_id="pec", ) - e2 = gprMax.Edge(p1=(tx[0], tx[1] + 0.001, tx[2]), p2=(tx[0], tx[1] + 0.002, tx[2]), material_id="pec") + e2 = gprMax.Edge( + p1=(tx[0], tx[1] + 0.001, tx[2]), + p2=(tx[0], tx[1] + 0.002, tx[2]), + material_id="pec", + ) scene_objects.extend((t1, t2, e1, e2)) elif resolution == 0.002: t1 = gprMax.Triangle( @@ -336,7 +433,11 @@ def antenna_like_MALA_1200(x, y, z, resolution=0.001, **kwargs): thickness=0, material_id="pec", ) - e3 = gprMax.Edge(p1=(tx[0] + 0.076, tx[1] - 0.001, tx[2]), p2=(tx[0] + 0.076, tx[1], tx[2]), material_id="pec") + e3 = gprMax.Edge( + p1=(tx[0] + 0.076, tx[1] - 0.001, tx[2]), + p2=(tx[0] + 0.076, tx[1], tx[2]), + material_id="pec", + ) t4 = gprMax.Triangle( p1=(tx[0] + 0.076, tx[1] + 0.002, tx[2]), p2=(tx[0] + 0.076 - 0.026, tx[1] + bowtieheight + 0.002, tx[2]), @@ -345,7 +446,9 @@ def antenna_like_MALA_1200(x, y, z, resolution=0.001, **kwargs): material_id="pec", ) e4 = gprMax.Edge( - p1=(tx[0] + 0.076, tx[1] + 0.001, tx[2]), p2=(tx[0] + 0.076, tx[1] + 0.002, tx[2]), material_id="pec" + p1=(tx[0] + 0.076, tx[1] + 0.001, tx[2]), + p2=(tx[0] + 0.076, tx[1] + 0.002, tx[2]), + material_id="pec", ) scene_objects.extend((t3, e3, t4, e4)) elif resolution == 0.002: @@ -631,20 +734,31 @@ def antenna_like_MALA_1200(x, y, z, resolution=0.001, **kwargs): # Skid b19 = gprMax.Box( - p1=(x, y, z), p2=(x + casesize[0], y + casesize[1], z + polypropylenethickness), material_id="polypropylene" + p1=(x, y, z), + p2=(x + casesize[0], y + casesize[1], z + polypropylenethickness), + material_id="polypropylene", ) b20 = gprMax.Box( p1=(x, y, z + polypropylenethickness), - p2=(x + casesize[0], y + casesize[1], z + polypropylenethickness + hdpethickness), + p2=( + x + casesize[0], + y + casesize[1], + z + polypropylenethickness + hdpethickness, + ), material_id="hdpe", ) scene_objects.extend((b19, b20)) # Excitation - w2 = gprMax.Waveform(wave_type="gaussian", amp=1, freq=excitationfreq, id="my_gaussian") + w2 = gprMax.Waveform( + wave_type="gaussian", amp=1, freq=excitationfreq, id="my_gaussian" + ) scene_objects.append(w2) vs1 = gprMax.VoltageSource( - polarisation="y", p1=(tx[0], tx[1], tx[2]), resistance=sourceresistance, waveform_id="my_gaussian" + polarisation="y", + p1=(tx[0], tx[1], tx[2]), + resistance=sourceresistance, + waveform_id="my_gaussian", ) scene_objects.append(vs1) @@ -655,7 +769,11 @@ def antenna_like_MALA_1200(x, y, z, resolution=0.001, **kwargs): # Geometry views gv1 = gprMax.GeometryView( p1=(x - dx, y - dy, z - dz), - p2=(x + casesize[0] + dx, y + casesize[1] + dy, z + skidthickness + casesize[2] + dz), + p2=( + x + casesize[0] + dx, + y + casesize[1] + dy, + z + skidthickness + casesize[2] + dz, + ), dl=(dx, dy, dz), filename="antenna_like_MALA_1200", output_type="n", diff --git a/toolboxes/Plotting/plot_Ascan.py b/toolboxes/Plotting/plot_Ascan.py index 7d2942c6..ae66d138 100644 --- a/toolboxes/Plotting/plot_Ascan.py +++ b/toolboxes/Plotting/plot_Ascan.py @@ -81,7 +81,9 @@ def mpl_plot(filename, outputs=Rx.defaultoutputs, fft=False, save=False): # Check for single output component when doing a FFT if fft and not len(outputs) == 1: - logger.exception("A single output must be specified when using " + "the -fft option") + logger.exception( + "A single output must be specified when using " + "the -fft option" + ) raise ValueError # New plot for each receiver @@ -120,7 +122,11 @@ def mpl_plot(filename, outputs=Rx.defaultoutputs, fft=False, save=False): # Set plotting range to -60dB from maximum power or 4 times # frequency at maximum power try: - pltrange = np.where(power[freqmaxpower:] < -60)[0][0] + freqmaxpower + 1 + pltrange = ( + np.where(power[freqmaxpower:] < -60)[0][0] + + freqmaxpower + + 1 + ) except: pltrange = freqmaxpower * 4 @@ -160,20 +166,27 @@ def mpl_plot(filename, outputs=Rx.defaultoutputs, fft=False, save=False): plt.setp(line2, color="g") plt.setp(ax1, ylabel=outputtext + " field strength [A/m]") plt.setp(stemlines, "color", "g") - plt.setp(markerline, "markerfacecolor", "g", "markeredgecolor", "g") + plt.setp( + markerline, "markerfacecolor", "g", "markeredgecolor", "g" + ) elif "I" in outputs[0]: plt.setp(line1, color="b") plt.setp(line2, color="b") plt.setp(ax1, ylabel=outputtext + " current [A]") plt.setp(stemlines, "color", "b") - plt.setp(markerline, "markerfacecolor", "b", "markeredgecolor", "b") + plt.setp( + markerline, "markerfacecolor", "b", "markeredgecolor", "b" + ) plt.show() # Plotting if no FFT required else: fig, ax = plt.subplots( - subplot_kw=dict(xlabel="Time [s]", ylabel=outputtext + " field strength [V/m]"), + subplot_kw=dict( + xlabel="Time [s]", + ylabel=outputtext + " field strength [V/m]", + ), num=rxpath + " - " + f[rxpath].attrs["Name"], figsize=(20, 10), facecolor="w", @@ -279,7 +292,13 @@ def mpl_plot(filename, outputs=Rx.defaultoutputs, fft=False, save=False): if save: # Save a PDF of the figure - fig.savefig(filename[:-3] + ".pdf", dpi=None, format="pdf", bbox_inches="tight", pad_inches=0.1) + fig.savefig( + filename[:-3] + ".pdf", + dpi=None, + format="pdf", + bbox_inches="tight", + pad_inches=0.1, + ) # Save a PNG of the figure # fig.savefig(filename[:-3] + '.png', dpi=150, format='png', # bbox_inches='tight', pad_inches=0.1) @@ -322,9 +341,17 @@ if __name__ == "__main__": ], nargs="+", ) - parser.add_argument("-fft", action="store_true", default=False, help="plot FFT (single output must be specified)") parser.add_argument( - "-save", action="store_true", default=False, help="save plot directly to file, i.e. do not display" + "-fft", + action="store_true", + default=False, + help="plot FFT (single output must be specified)", + ) + parser.add_argument( + "-save", + action="store_true", + default=False, + help="save plot directly to file, i.e. do not display", ) args = parser.parse_args() diff --git a/toolboxes/Plotting/plot_Bscan.py b/toolboxes/Plotting/plot_Bscan.py index 719043e5..ba0d5a79 100644 --- a/toolboxes/Plotting/plot_Bscan.py +++ b/toolboxes/Plotting/plot_Bscan.py @@ -46,7 +46,12 @@ def mpl_plot(filename, outputdata, dt, rxnumber, rxcomponent, save=False): file = Path(filename) - fig = plt.figure(num=file.stem + " - rx" + str(rxnumber), figsize=(20, 10), facecolor="w", edgecolor="w") + fig = plt.figure( + num=file.stem + " - rx" + str(rxnumber), + figsize=(20, 10), + facecolor="w", + edgecolor="w", + ) plt.imshow( outputdata, extent=[0, outputdata.shape[1], outputdata.shape[0] * dt, 0], @@ -73,7 +78,13 @@ def mpl_plot(filename, outputdata, dt, rxnumber, rxcomponent, save=False): if save: # Save a PDF of the figure - fig.savefig(filename[:-3] + ".pdf", dpi=None, format="pdf", bbox_inches="tight", pad_inches=0.1) + fig.savefig( + filename[:-3] + ".pdf", + dpi=None, + format="pdf", + bbox_inches="tight", + pad_inches=0.1, + ) # Save a PNG of the figure # fig.savefig(filename[:-3] + '.png', dpi=150, format='png', # bbox_inches='tight', pad_inches=0.1) @@ -94,10 +105,16 @@ if __name__ == "__main__": choices=["Ex", "Ey", "Ez", "Hx", "Hy", "Hz", "Ix", "Iy", "Iz"], ) parser.add_argument( - "-gather", action="store_true", default=False, help="gather together all receiver outputs in file" + "-gather", + action="store_true", + default=False, + help="gather together all receiver outputs in file", ) parser.add_argument( - "-save", action="store_true", default=False, help="save plot directly to file, i.e. do not display" + "-save", + action="store_true", + default=False, + help="save plot directly to file, i.e. do not display", ) args = parser.parse_args() @@ -118,10 +135,14 @@ if __name__ == "__main__": rxsgather = outputdata rxsgather = np.column_stack((rxsgather, outputdata)) else: - plthandle = mpl_plot(args.outputfile, outputdata, dt, rx, args.rx_component, save=args.save) + plthandle = mpl_plot( + args.outputfile, outputdata, dt, rx, args.rx_component, save=args.save + ) # Plot all receivers from single output file together if required if args.gather: - plthandle = mpl_plot(args.outputfile, rxsgather, dt, rx, args.rx_component, save=args.save) + plthandle = mpl_plot( + args.outputfile, rxsgather, dt, rx, args.rx_component, save=args.save + ) plthandle.show() diff --git a/toolboxes/Plotting/plot_antenna_params.py b/toolboxes/Plotting/plot_antenna_params.py index 8f96e2d1..76871e0c 100644 --- a/toolboxes/Plotting/plot_antenna_params.py +++ b/toolboxes/Plotting/plot_antenna_params.py @@ -28,7 +28,9 @@ import numpy as np logger = logging.getLogger(__name__) -def calculate_antenna_params(filename, tltxnumber=1, tlrxnumber=None, rxnumber=None, rxcomponent=None): +def calculate_antenna_params( + filename, tltxnumber=1, tlrxnumber=None, rxnumber=None, rxcomponent=None +): """Calculates antenna parameters - incident, reflected and total volatges and currents; s11, (s21) and input impedance. @@ -160,7 +162,9 @@ def calculate_antenna_params(filename, tltxnumber=1, tlrxnumber=None, rxnumber=N "yin": yin, } if tlrxnumber or rxnumber: - with np.errstate(divide="ignore"): # Ignore warning from taking a log of any zero values + with np.errstate( + divide="ignore" + ): # Ignore warning from taking a log of any zero values s21 = 20 * np.log10(s21) s21[np.invert(np.isfinite(s21))] = 0 antennaparams["s21"] = s21 @@ -224,7 +228,10 @@ def mpl_plot( # Print some useful values from s11, and input impedance s11minfreq = np.where(s11[pltrange] == np.amin(s11[pltrange]))[0][0] - logger.info(f"s11 minimum: {np.amin(s11[pltrange]):g} dB at " + f"{freqs[s11minfreq + pltrangemin]:g} Hz") + logger.info( + f"s11 minimum: {np.amin(s11[pltrange]):g} dB at " + + f"{freqs[s11minfreq + pltrangemin]:g} Hz" + ) logger.info(f"At {freqs[s11minfreq + pltrangemin]:g} Hz...") logger.info( f"Input impedance: {np.abs(zin[s11minfreq + pltrangemin]):.1f}" @@ -236,7 +243,10 @@ def mpl_plot( # Figure 1 # Plot incident voltage fig1, ax = plt.subplots( - num="Transmitter transmission line parameters", figsize=(20, 12), facecolor="w", edgecolor="w" + num="Transmitter transmission line parameters", + figsize=(20, 12), + facecolor="w", + edgecolor="w", ) gs1 = gridspec.GridSpec(4, 2, hspace=0.7) ax = plt.subplot(gs1[0, 0]) @@ -368,7 +378,9 @@ def mpl_plot( # Figure 2 # Plot frequency spectra of s11 - fig2, ax = plt.subplots(num="Antenna parameters", figsize=(20, 12), facecolor="w", edgecolor="w") + fig2, ax = plt.subplots( + num="Antenna parameters", figsize=(20, 12), facecolor="w", edgecolor="w" + ) gs2 = gridspec.GridSpec(2, 2, hspace=0.3) ax = plt.subplot(gs2[0, 0]) markerline, stemlines, baseline = ax.stem(freqs[pltrange], s11[pltrange], "-.") @@ -463,8 +475,20 @@ def mpl_plot( savename2 = filename.stem + "_ant_params" savename2 = filename.parent / savename2 # Save a PDF of the figure - fig1.savefig(savename1.with_suffix(".pdf"), dpi=None, format="pdf", bbox_inches="tight", pad_inches=0.1) - fig2.savefig(savename2.with_suffix(".pdf"), dpi=None, format="pdf", bbox_inches="tight", pad_inches=0.1) + fig1.savefig( + savename1.with_suffix(".pdf"), + dpi=None, + format="pdf", + bbox_inches="tight", + pad_inches=0.1, + ) + fig2.savefig( + savename2.with_suffix(".pdf"), + dpi=None, + format="pdf", + bbox_inches="tight", + pad_inches=0.1, + ) # Save a PNG of the figure # fig1.savefig(savename1.with_suffix('.png'), dpi=150, format='png', # bbox_inches='tight', pad_inches=0.1) @@ -485,8 +509,15 @@ if __name__ == "__main__": usage="cd gprMax; python -m toolboxes.Plotting.plot_antenna_params outputfile", ) parser.add_argument("outputfile", help="name of output file including path") - parser.add_argument("--tltx-num", default=1, type=int, help="transmitter antenna - transmission line number") - parser.add_argument("--tlrx-num", type=int, help="receiver antenna - transmission line number") + parser.add_argument( + "--tltx-num", + default=1, + type=int, + help="transmitter antenna - transmission line number", + ) + parser.add_argument( + "--tlrx-num", type=int, help="receiver antenna - transmission line number" + ) parser.add_argument("--rx-num", type=int, help="receiver antenna - output number") parser.add_argument( "--rx-component", @@ -495,7 +526,10 @@ if __name__ == "__main__": choices=["Ex", "Ey", "Ez"], ) parser.add_argument( - "-save", action="store_true", default=False, help="save plot directly to file, i.e. do not display" + "-save", + action="store_true", + default=False, + help="save plot directly to file, i.e. do not display", ) args = parser.parse_args() diff --git a/toolboxes/Plotting/plot_source_wave.py b/toolboxes/Plotting/plot_source_wave.py index f8353601..bb5f84d1 100644 --- a/toolboxes/Plotting/plot_source_wave.py +++ b/toolboxes/Plotting/plot_source_wave.py @@ -90,7 +90,13 @@ def mpl_plot(w, timewindow, dt, iterations, fft=False, save=False): if w.freq and w.type != "gaussian" and w.type != "impulse": logging.info(f"Centre frequency: {w.freq:g} Hz") - if w.type in ["gaussian", "gaussiandot", "gaussiandotnorm", "gaussianprime", "gaussiandoubleprime"]: + if w.type in [ + "gaussian", + "gaussiandot", + "gaussiandotnorm", + "gaussianprime", + "gaussiandoubleprime", + ]: delay = 1 / w.freq logging.info(f"Time to centre of pulse: {delay:g} s") elif w.type in ["gaussiandotdot", "gaussiandotdotnorm", "ricker"]: @@ -113,7 +119,9 @@ def mpl_plot(w, timewindow, dt, iterations, fft=False, save=False): pltrange = np.where(freqs > 4 * w.freq)[0][0] pltrange = np.s_[0:pltrange] - fig, (ax1, ax2) = plt.subplots(nrows=1, ncols=2, num=w.type, figsize=(20, 10), facecolor="w", edgecolor="w") + fig, (ax1, ax2) = plt.subplots( + nrows=1, ncols=2, num=w.type, figsize=(20, 10), facecolor="w", edgecolor="w" + ) # Plot waveform ax1.plot(time, waveform, "r", lw=2) @@ -121,7 +129,9 @@ def mpl_plot(w, timewindow, dt, iterations, fft=False, save=False): ax1.set_ylabel("Amplitude") # Plot frequency spectra - markerline, stemlines, baseline = ax2.stem(freqs[pltrange], power[pltrange], "-.") + markerline, stemlines, baseline = ax2.stem( + freqs[pltrange], power[pltrange], "-." + ) plt.setp(baseline, "linewidth", 0) plt.setp(stemlines, "color", "r") plt.setp(markerline, "markerfacecolor", "r", "markeredgecolor", "r") @@ -130,7 +140,9 @@ def mpl_plot(w, timewindow, dt, iterations, fft=False, save=False): ax2.set_ylabel("Power [dB]") else: - fig, ax1 = plt.subplots(num=w.type, figsize=(10, 10), facecolor="w", edgecolor="w") + fig, ax1 = plt.subplots( + num=w.type, figsize=(10, 10), facecolor="w", edgecolor="w" + ) # Plot waveform ax1.plot(time, waveform, "r", lw=2) @@ -143,9 +155,21 @@ def mpl_plot(w, timewindow, dt, iterations, fft=False, save=False): if save: savefile = Path(__file__).parent / w.type # Save a PDF of the figure - fig.savefig(savefile.with_suffix(".pdf"), dpi=None, format="pdf", bbox_inches="tight", pad_inches=0.1) + fig.savefig( + savefile.with_suffix(".pdf"), + dpi=None, + format="pdf", + bbox_inches="tight", + pad_inches=0.1, + ) # Save a PNG of the figure - fig.savefig(savefile.with_suffix(".png"), dpi=150, format="png", bbox_inches="tight", pad_inches=0.1) + fig.savefig( + savefile.with_suffix(".png"), + dpi=150, + format="png", + bbox_inches="tight", + pad_inches=0.1, + ) return plt @@ -161,18 +185,27 @@ if __name__ == "__main__": parser.add_argument("freq", type=float, help="centre frequency of waveform") parser.add_argument("timewindow", help="time window to view waveform") parser.add_argument("dt", type=float, help="time step to view waveform") - parser.add_argument("-fft", action="store_true", default=False, help="plot FFT of waveform") parser.add_argument( - "-save", action="store_true", default=False, help="save plot directly to file, i.e. do not display" + "-fft", action="store_true", default=False, help="plot FFT of waveform" + ) + parser.add_argument( + "-save", + action="store_true", + default=False, + help="save plot directly to file, i.e. do not display", ) args = parser.parse_args() # Check waveform parameters if args.type.lower() not in Waveform.types: - logging.exception(f"The waveform must have one of the following types {', '.join(Waveform.types)}") + logging.exception( + f"The waveform must have one of the following types {', '.join(Waveform.types)}" + ) raise ValueError if args.freq <= 0: - logging.exception("The waveform requires an excitation frequency value of greater than zero") + logging.exception( + "The waveform requires an excitation frequency value of greater than zero" + ) raise ValueError # Create waveform instance @@ -182,5 +215,7 @@ if __name__ == "__main__": w.freq = args.freq timewindow, iterations = check_timewindow(args.timewindow, args.dt) - plthandle = mpl_plot(w, timewindow, args.dt, iterations, fft=args.fft, save=args.save) + plthandle = mpl_plot( + w, timewindow, args.dt, iterations, fft=args.fft, save=args.save + ) plthandle.show() diff --git a/toolboxes/STLtoVoxel/convert.py b/toolboxes/STLtoVoxel/convert.py index dfe02736..0e814a00 100644 --- a/toolboxes/STLtoVoxel/convert.py +++ b/toolboxes/STLtoVoxel/convert.py @@ -21,12 +21,20 @@ def convert_file(input_file_path, discretization, pad=1, parallel=False): return convert_files([input_file_path], discretization, pad=pad, parallel=parallel) -def convert_files(input_file_paths, discretization, colors=[(0, 0, 0)], pad=1, parallel=False): +def convert_files( + input_file_paths, discretization, colors=[(0, 0, 0)], pad=1, parallel=False +): meshes = [] for input_file_path in input_file_paths: mesh_obj = mesh.Mesh.from_file(input_file_path) - org_mesh = np.hstack((mesh_obj.v0[:, np.newaxis], mesh_obj.v1[:, np.newaxis], mesh_obj.v2[:, np.newaxis])) + org_mesh = np.hstack( + ( + mesh_obj.v0[:, np.newaxis], + mesh_obj.v1[:, np.newaxis], + mesh_obj.v2[:, np.newaxis], + ) + ) meshes.append(org_mesh) vol, scale, shift = convert_meshes(meshes, discretization, parallel) vol = np.transpose(vol) diff --git a/toolboxes/STLtoVoxel/perimeter.py b/toolboxes/STLtoVoxel/perimeter.py index 5952c1ec..506e5389 100644 --- a/toolboxes/STLtoVoxel/perimeter.py +++ b/toolboxes/STLtoVoxel/perimeter.py @@ -6,7 +6,9 @@ def lines_to_voxels(line_list, pixels): x = 0 for event_x, status, line_ind in generate_line_events(line_list): while event_x - x >= 0: - lines = reduce(lambda acc, cur: acc + [line_list[cur]], current_line_indices, []) + lines = reduce( + lambda acc, cur: acc + [line_list[cur]], current_line_indices, [] + ) paint_y_axis(lines, pixels, x) x += 1 diff --git a/toolboxes/STLtoVoxel/slice.py b/toolboxes/STLtoVoxel/slice.py index 96460d02..dc182527 100644 --- a/toolboxes/STLtoVoxel/slice.py +++ b/toolboxes/STLtoVoxel/slice.py @@ -19,7 +19,12 @@ def mesh_to_plane(mesh, bounding_box, parallel): current_mesh_indices = set() z = 0 - with tqdm(total=bounding_box[2], desc="Processing Layers", ncols=get_terminal_width() - 1, file=sys.stdout) as pbar: + with tqdm( + total=bounding_box[2], + desc="Processing Layers", + ncols=get_terminal_width() - 1, + file=sys.stdout, + ) as pbar: for event_z, status, tri_ind in generate_tri_events(mesh): while event_z - z >= 0: mesh_subset = [mesh[ind] for ind in current_mesh_indices] diff --git a/toolboxes/STLtoVoxel/stltovoxel.py b/toolboxes/STLtoVoxel/stltovoxel.py index 726f8076..bc323b98 100644 --- a/toolboxes/STLtoVoxel/stltovoxel.py +++ b/toolboxes/STLtoVoxel/stltovoxel.py @@ -17,9 +17,15 @@ if __name__ == "__main__": usage="cd gprMax; python -m toolboxes.STLtoVoxel.stltovoxel stlfilename -matindex -dxdydz", ) parser.add_argument( - "stlfiles", help="can be the filename of a single STL file, or the path to folder containing multiple STL files" + "stlfiles", + help="can be the filename of a single STL file, or the path to folder containing multiple STL files", + ) + parser.add_argument( + "-dxdydz", + type=float, + required=True, + help="discretisation to use in voxelisation process", ) - parser.add_argument("-dxdydz", type=float, required=True, help="discretisation to use in voxelisation process") args = parser.parse_args() if os.path.isdir(args.stlfiles): @@ -38,7 +44,9 @@ if __name__ == "__main__": newline = "\n\t" logger.info(f"\nConverting STL file(s): {newline.join(files)}") model_array = convert_files(files, dxdydz) - logger.info(f"Number of voxels: {model_array.shape[0]} x {model_array.shape[1]} x {model_array.shape[2]}") + logger.info( + f"Number of voxels: {model_array.shape[0]} x {model_array.shape[1]} x {model_array.shape[2]}" + ) logger.info(f"Spatial discretisation: {dxdydz[0]} x {dxdydz[1]} x {dxdydz[2]}m") # Write HDF5 file for gprMax using voxels diff --git a/toolboxes/Utilities/convert_png2h5.py b/toolboxes/Utilities/convert_png2h5.py index ffc93d97..32c84608 100644 --- a/toolboxes/Utilities/convert_png2h5.py +++ b/toolboxes/Utilities/convert_png2h5.py @@ -55,7 +55,9 @@ class Cursor(object): ) # Convert pixel values from float (0-1) to integer (0-255) match = pixel_match(materials, pixel) if match is False: - logger.info(f"x, y: {int(x)} {int(y)} px; RGB: {pixel[:-1]}; material ID: {len(self.materials)}") + logger.info( + f"x, y: {int(x)} {int(y)} px; RGB: {pixel[:-1]}; material ID: {len(self.materials)}" + ) materials.append(pixel) @@ -85,10 +87,17 @@ if __name__ == "__main__": ) parser.add_argument("imagefile", help="name of image file including path") parser.add_argument( - "dxdydz", type=float, action="append", nargs=3, help="spatial resolution of model, e.g. dx dy dz" + "dxdydz", + type=float, + action="append", + nargs=3, + help="spatial resolution of model, e.g. dx dy dz", ) parser.add_argument( - "-zcells", default=1, type=int, help="number of cells for domain in z-direction (infinite direction)" + "-zcells", + default=1, + type=int, + help="number of cells for domain in z-direction (infinite direction)", ) args = parser.parse_args() @@ -97,7 +106,9 @@ if __name__ == "__main__": # Store image data to use for creating geometry imdata = np.rot90(im, k=3) # Rotate 90CW - imdata = np.floor(imdata * 255).astype(np.int16) # Convert pixel values from float (0-1) to integer (0-255) + imdata = np.floor(imdata * 255).astype( + np.int16 + ) # Convert pixel values from float (0-1) to integer (0-255) logger.info(f"Reading PNG image file: {os.path.split(args.imagefile)[1]}") logger.info( diff --git a/toolboxes/Utilities/get_host_spec.py b/toolboxes/Utilities/get_host_spec.py index 2a3c03b7..98f2c7a8 100644 --- a/toolboxes/Utilities/get_host_spec.py +++ b/toolboxes/Utilities/get_host_spec.py @@ -33,7 +33,11 @@ logging.basicConfig(format="%(message)s", level=logging.INFO) # Host machine info. hostinfo = get_host_info() -hyperthreadingstr = f", {hostinfo['logicalcores']} cores with Hyper-Threading" if hostinfo["hyperthreading"] else "" +hyperthreadingstr = ( + f", {hostinfo['logicalcores']} cores with Hyper-Threading" + if hostinfo["hyperthreading"] + else "" +) hostname = f"\n=== {hostinfo['hostname']}" logging.info(f"{hostname} {'=' * (get_terminal_width() - len(hostname) - 1)}") logging.info(f"\n{'Mfr/model:':<12} {hostinfo['machineID']}") @@ -46,7 +50,8 @@ logging.info(f"{'OS/Version:':<12} {hostinfo['osversion']}") # OpenMP logging.info( - "\n\n=== OpenMP capabilities (gprMax will not use Hyper-Threading " + "as there is no performance advantage)\n" + "\n\n=== OpenMP capabilities (gprMax will not use Hyper-Threading " + + "as there is no performance advantage)\n" ) logging.info(f"{'OpenMP threads: '} {hostinfo['physicalcores']}") diff --git a/toolboxes/Utilities/outputfiles_merge.py b/toolboxes/Utilities/outputfiles_merge.py index 87458b3d..1ca79e29 100644 --- a/toolboxes/Utilities/outputfiles_merge.py +++ b/toolboxes/Utilities/outputfiles_merge.py @@ -107,7 +107,9 @@ def merge_files(outputfiles, removefiles=False): availableoutputs = list(fin[path].keys()) for output in availableoutputs: grp.create_dataset( - output, (fout.attrs["Iterations"], len(outputfiles)), dtype=fin[path + "/" + output].dtype + output, + (fout.attrs["Iterations"], len(outputfiles)), + dtype=fin[path + "/" + output].dtype, ) # For all receivers @@ -134,9 +136,14 @@ if __name__ == "__main__": + "optionally removes the series of output files.", usage="cd gprMax; python -m tools.outputfiles_merge basefilename", ) - parser.add_argument("basefilename", help="base name of output file series including path") parser.add_argument( - "--remove-files", action="store_true", default=False, help="flag to remove individual output files after merge" + "basefilename", help="base name of output file series including path" + ) + parser.add_argument( + "--remove-files", + action="store_true", + default=False, + help="flag to remove individual output files after merge", ) args = parser.parse_args()